Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/rawdb/freezer.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
return 0, err
}
}
// Truncating a table that holds nothing below the new head realigns its tail
// down as well, so pull the cached group tails along with it.
for _, cached := range f.tails {
if cached.Load() > items {
cached.Store(items)
}
}
f.head.Store(items)
return oitems, nil
}
Expand Down
8 changes: 5 additions & 3 deletions core/rawdb/freezer_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,11 @@ func (t *freezerTable) truncateHead(items uint64) error {
hidden := t.itemHidden.Load()

if items < hidden {
if existing == hidden {
// Empty table means that it is newly added. Its tail would be
// at the head, so we have to align the table down to the new head.
// A table whose tail sits exactly at its first stored item has never had

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this fix is correct.

The actual issue is that the BAL table is initialized at the common head of the tables, with everything below it already pruned. If the freezer later needs to be truncated below that common head, there is simply nothing left to delete.

This is a very specific upgrade-path issue, and we don't want to make "truncate below tail" a generally valid operation.

existing == hidden is intended to identify empty tables. We shouldn't broaden that condition to cover additional cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT:

We should allow "truncate below tail" becomes valid across the different group. Let me figure out the better solution.

// anything pruned away: its tail is the alignment point assigned when the
// table was added to an existing store. Realigning it down to the new head
// discards nothing that was not already being discarded.
if t.itemOffset.Load() == hidden {
return t.resetTo(items)
}
return errors.New("truncation below tail")
Expand Down
22 changes: 22 additions & 0 deletions core/rawdb/freezer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,28 @@ func TestChainFreezerBALAlignment(t *testing.T) {
if !bytes.Equal(got, balPayload) {
t.Fatalf("BAL[%d]: got %x, want %x", items, got, balPayload)
}
// Rewinding below the aligned tail must be permitted: the BAL table holds
// nothing down there, so dropping it is lossless. Rejecting it would abort
// the rewind, and because the other tables are truncated first, the freezer
// would be left inconsistent and fail to open again.
rewind := items / 2
if _, err := f.TruncateHead(rewind); err != nil {
t.Fatalf("truncate head to %d: %v", rewind, err)
}
if tail, err := f.Tail(ChainFreezerBALGroup); err != nil || tail != rewind {
t.Fatalf("BAL tail after rewind: got %d (err %v), want %d", tail, err, rewind)
}
require.NoError(t, f.Close())

reopened, err := NewFreezer(dir, "", false, 2049, chainFreezerTableConfigs)
if err != nil {
t.Fatalf("can't re-open freezer after rewind: %v", err)
}
defer reopened.Close()

if got, _ := reopened.Ancients(); got != rewind {
t.Fatalf("head after re-open: got %d, want %d", got, rewind)
}
}

func TestFreezerCloseSync(t *testing.T) {
Expand Down