mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-25 01:09:28 +00:00
core/rawdb: fix WriteAncientHeaderChain and add tests
This commit is contained in:
parent
da877346c6
commit
e67fa13e34
4 changed files with 183 additions and 6 deletions
|
|
@ -797,6 +797,13 @@ func WriteAncientHeaderChain(db ethdb.AncientWriter, headers []*types.Header) (i
|
|||
if err := op.AppendRaw(ChainFreezerReceiptTable, num, nil); err != nil {
|
||||
return fmt.Errorf("can't append block %d receipts: %v", num, err)
|
||||
}
|
||||
// The assumption is held that BAL of ancient block is no longer available
|
||||
// (it may still reachable, but it's not worthwhile to even retrieve it
|
||||
// from the network). A nil entry is stored in the BAL table as the absence
|
||||
// placeholder.
|
||||
if err := op.AppendRaw(ChainFreezerBALTable, num, nil); err != nil {
|
||||
return fmt.Errorf("can't append block %d bals: %v", num, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -926,6 +926,47 @@ func makeTestBAL(t *testing.T) (rlp.RawValue, *bal.BlockAccessList) {
|
|||
return encoded, &decoded
|
||||
}
|
||||
|
||||
// TestWriteAncientBlocksNilBAL ensures that freezing a block with no block
|
||||
// access list produces an empty entry in the BAL ancient table and that
|
||||
// ReadAccessList returns nil afterwards (i.e. the empty entry is not surfaced
|
||||
// as a malformed BAL).
|
||||
func TestWriteAncientBlocksNilBAL(t *testing.T) {
|
||||
db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database with ancient backend: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
block := types.NewBlockWithHeader(&types.Header{
|
||||
Number: big.NewInt(0),
|
||||
Extra: []byte("nil-bal block"),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyTxsHash,
|
||||
ReceiptHash: types.EmptyReceiptsHash,
|
||||
})
|
||||
if block.AccessList() != nil {
|
||||
t.Fatalf("test precondition: block must have nil access list")
|
||||
}
|
||||
if _, err := WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil})); err != nil {
|
||||
t.Fatalf("WriteAncientBlocks failed: %v", err)
|
||||
}
|
||||
hash, number := block.Hash(), block.NumberU64()
|
||||
|
||||
// The BAL ancient entry should exist as an empty blob.
|
||||
if blob := ReadAccessListRLP(db, hash, number); len(blob) != 0 {
|
||||
t.Fatalf("ReadAccessListRLP: got %x, want empty", blob)
|
||||
}
|
||||
// ReadAccessList must surface nil rather than attempting to RLP-decode
|
||||
// the empty payload.
|
||||
if b := ReadAccessList(db, hash, number); b != nil {
|
||||
t.Fatalf("ReadAccessList: got %v, want nil", b)
|
||||
}
|
||||
// HasAccessList only consults the KV store and there's nothing there.
|
||||
if HasAccessList(db, hash, number) {
|
||||
t.Fatal("HasAccessList returned true for absent BAL")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBALStorage tests write/read/delete of BALs in the KV store.
|
||||
func TestBALStorage(t *testing.T) {
|
||||
db := NewMemoryDatabase()
|
||||
|
|
|
|||
|
|
@ -365,7 +365,11 @@ func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
|||
if kind == ChainFreezerHeaderTable || kind == ChainFreezerHashTable {
|
||||
return f.ancients.Ancient(kind, number)
|
||||
}
|
||||
tail, err := f.ancients.Tail(tableTailGroup(kind))
|
||||
group, err := tableTailGroup(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tail, err := f.ancients.Tail(group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -387,13 +391,11 @@ func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
|||
}
|
||||
|
||||
// tableTailGroup returns the tail group identifier for a chain freezer table.
|
||||
// Unknown tables resolve to the default block-data group, since the chain
|
||||
// freezer's only prunable group today is bodies+receipts.
|
||||
func tableTailGroup(kind string) string {
|
||||
func tableTailGroup(kind string) (string, error) {
|
||||
if cfg, ok := chainFreezerTableConfigs[kind]; ok {
|
||||
return cfg.tailGroup
|
||||
return cfg.tailGroup, nil
|
||||
}
|
||||
return ChainFreezerBlockDataGroup
|
||||
return "", errUnknownTable
|
||||
}
|
||||
|
||||
// ReadAncients executes an operation while preventing mutations to the freezer,
|
||||
|
|
|
|||
|
|
@ -371,6 +371,133 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestChainFreezerBALAlignment exercises the new-table alignment path: a chain
|
||||
// freezer is first opened with the legacy table set (no BAL), populated with a
|
||||
// few blocks and closed. It is then re-opened with the full chain freezer
|
||||
// table set (which includes the BAL column). The expectation is that the BAL
|
||||
// table is fast-forwarded to the existing head without disturbing the body /
|
||||
// receipt tables, and that subsequent writes append cleanly across all tables.
|
||||
func TestChainFreezerBALAlignment(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Build a "legacy" subset of the chain freezer table set, omitting BAL.
|
||||
legacyTables := make(map[string]freezerTableConfig)
|
||||
for name, cfg := range chainFreezerTableConfigs {
|
||||
if name == ChainFreezerBALTable {
|
||||
continue
|
||||
}
|
||||
legacyTables[name] = cfg
|
||||
}
|
||||
|
||||
// First open: legacy config. Fill in `items` blocks of dummy data.
|
||||
const items = uint64(10)
|
||||
payload := bytes.Repeat([]byte{0xab}, 64)
|
||||
|
||||
f, err := NewFreezer(dir, "", false, 2049, legacyTables)
|
||||
if err != nil {
|
||||
t.Fatalf("can't open legacy freezer: %v", err)
|
||||
}
|
||||
if _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
for i := uint64(0); i < items; i++ {
|
||||
if err := op.AppendRaw(ChainFreezerHashTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerHeaderTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerBodiesTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerReceiptTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("legacy write failed: %v", err)
|
||||
}
|
||||
if got, _ := f.Ancients(); got != items {
|
||||
t.Fatalf("legacy head: got %d, want %d", got, items)
|
||||
}
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
// Re-open with the full chain freezer table set, which now includes BAL.
|
||||
// repair() should detect the empty BAL table and fast-forward it to the
|
||||
// existing head rather than truncating everyone down to zero.
|
||||
f, err = NewFreezer(dir, "", false, 2049, chainFreezerTableConfigs)
|
||||
if err != nil {
|
||||
t.Fatalf("can't re-open freezer with BAL added: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// The head must be preserved.
|
||||
if got, _ := f.Ancients(); got != items {
|
||||
t.Fatalf("head after re-open: got %d, want %d", got, items)
|
||||
}
|
||||
// Existing data must still be readable in full.
|
||||
for i := uint64(0); i < items; i++ {
|
||||
for _, kind := range []string{
|
||||
ChainFreezerHashTable, ChainFreezerHeaderTable,
|
||||
ChainFreezerBodiesTable, ChainFreezerReceiptTable,
|
||||
} {
|
||||
got, err := f.Ancient(kind, i)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s[%d]: %v", kind, i, err)
|
||||
}
|
||||
if !bytes.Equal(got, payload) {
|
||||
t.Fatalf("read %s[%d]: payload mismatch", kind, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
// The block-data tail must be unchanged (no spurious tail bump).
|
||||
if tail, err := f.Tail(ChainFreezerBlockDataGroup); err != nil || tail != 0 {
|
||||
t.Fatalf("blockdata tail: got %d (err %v), want 0", tail, err)
|
||||
}
|
||||
// The BAL tail should equal the head — the table is empty but aligned.
|
||||
if tail, err := f.Tail(ChainFreezerBALGroup); err != nil || tail != items {
|
||||
t.Fatalf("BAL tail: got %d (err %v), want %d", tail, err, items)
|
||||
}
|
||||
// Reads to BAL for any pre-alignment block must report out-of-bounds.
|
||||
for i := uint64(0); i < items; i++ {
|
||||
if _, err := f.Ancient(ChainFreezerBALTable, i); err == nil {
|
||||
t.Fatalf("reading BAL[%d] succeeded; want error (out of bounds)", i)
|
||||
}
|
||||
}
|
||||
// A subsequent batch must append uniformly to every table, BAL included.
|
||||
balPayload := []byte("real-bal")
|
||||
if _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
i := items
|
||||
if err := op.AppendRaw(ChainFreezerHashTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerHeaderTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerBodiesTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerReceiptTable, i, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(ChainFreezerBALTable, i, balPayload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("post-alignment write failed: %v", err)
|
||||
}
|
||||
if got, _ := f.Ancients(); got != items+1 {
|
||||
t.Fatalf("head after post-alignment write: got %d, want %d", got, items+1)
|
||||
}
|
||||
got, err := f.Ancient(ChainFreezerBALTable, items)
|
||||
if err != nil {
|
||||
t.Fatalf("BAL[%d]: %v", items, err)
|
||||
}
|
||||
if !bytes.Equal(got, balPayload) {
|
||||
t.Fatalf("BAL[%d]: got %x, want %x", items, got, balPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreezerCloseSync(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, _ := newFreezerForTesting(t, map[string]freezerTableConfig{"a": {noSnappy: true}, "b": {noSnappy: true}})
|
||||
|
|
|
|||
Loading…
Reference in a new issue