diff --git a/db/rawdb/accessors_chain.go b/db/rawdb/accessors_chain.go index fc7c6df8e7e..6b67a46cd0f 100644 --- a/db/rawdb/accessors_chain.go +++ b/db/rawdb/accessors_chain.go @@ -609,7 +609,10 @@ func ReadBlockAccessListBytes(db kv.Getter, hash common.Hash, number uint64) ([] return data, nil } -// WriteBlockAccessListBytes stores the RLP-encoded block access list sidecar for a block. +// WriteBlockAccessListBytes stores the RLP-encoded block access list sidecar for +// a block. This is secondary storage (serving, backfill, unwind); the primary +// carry into execution is Block.BlockAccessList(), written via the block overlay +// in InsertBlocks and flushed at commit. func WriteBlockAccessListBytes(db kv.Putter, hash common.Hash, number uint64, data []byte) error { if err := db.Put(kv.BlockAccessList, dbutils.BlockBodyKey(number, hash), data); err != nil { return fmt.Errorf("failed to store block access list: %w", err) @@ -807,6 +810,13 @@ func ReadBlock(tx kv.Getter, hash common.Hash, number uint64) *types.Block { return nil } block := types.NewBlockFromStorage(hash, header, body.Transactions, body.Uncles, body.Withdrawals) + // Carry the BAL sidecar (secondary storage) so a block reconstructed from the + // DB carries its BAL like its header/body. Only Amsterdam+ blocks have one. + if header.BlockAccessListHash != nil { + if bal, err := ReadBlockAccessListBytes(tx, hash, number); err == nil && len(bal) > 0 { + block.SetBlockAccessList(bal) + } + } return block } diff --git a/execution/engineapi/engine_server.go b/execution/engineapi/engine_server.go index 1ce2cad4be0..59c3a08cec8 100644 --- a/execution/engineapi/engine_server.go +++ b/execution/engineapi/engine_server.go @@ -483,6 +483,10 @@ func (s *EngineServer) newPayload(ctx context.Context, req *engine_types.Executi // via rlp.EncodeToBytes. Both slices reference the same underlying // byte buffers from req.Transactions. block := types.NewBlockFromStorageWithBinaryTxs(blockHash, &header, transactions, txs, nil /* uncles */, withdrawals) + // Carry the payload's BAL on the block so execution consumes it from the + // in-memory payload; InsertBlocks still stores it in the overlay (flushed at + // commit) as secondary storage. + block.SetBlockAccessList(blockAccessListBytes) payloadStatus, err := s.HandleNewPayload(ctx, "NewPayload", block, expectedBlobHashes, blockAccessListBytes) if err != nil { if errors.Is(err, rules.ErrInvalidBlock) { diff --git a/execution/stagedsync/exec3.go b/execution/stagedsync/exec3.go index c8250d4ef43..88b1ccb1cad 100644 --- a/execution/stagedsync/exec3.go +++ b/execution/stagedsync/exec3.go @@ -612,12 +612,19 @@ func (te *txExecutor) executeBlocks(ctx context.Context, startBlockNum uint64, m go warmTxsHashes(b) var dbBAL types.BlockAccessList - // Read BAL through blockTx (overlay or execRoTx) — do NOT open - // a separate db.View() as it can deadlock with the stageloop's - // RW transaction when BlockOverlay is active. - data, err := rawdb.ReadBlockAccessListBytes(blockTx, b.Hash(), blockNum) - if err != nil { - return err + // Prefer the BAL carried on the block (the payload) — the newPayload / + // backward-sync paths attach it, so no read is needed. Fall back to the + // BAL sidecar in the DB (via blockTx: overlay or execRoTx) for blocks + // that don't carry it (snapshot / forward-sync); do NOT open a separate + // db.View() as it can deadlock with the stageloop's RW transaction when + // BlockOverlay is active. ProcessBAL still computes+validates the BAL + // from the write-set as the ultimate fallback. + data := b.BlockAccessList() + if len(data) == 0 { + data, err = rawdb.ReadBlockAccessListBytes(blockTx, b.Hash(), blockNum) + if err != nil { + return err + } } if len(data) > 0 && !dbg.IgnoreBAL { dbBAL, err = types.DecodeBlockAccessListBytes(data) diff --git a/execution/types/block.go b/execution/types/block.go index 17ed9fb2afe..e0b5d631d2d 100644 --- a/execution/types/block.go +++ b/execution/types/block.go @@ -808,6 +808,7 @@ func (r RawBlock) AsBlock() (*Block, error) { } } b.transactions = txs + b.blockAccessList = r.BlockAccessList return b, nil } @@ -819,6 +820,12 @@ type Block struct { transactions Transactions withdrawals []*Withdrawal + // blockAccessList is the RLP-encoded EIP-7928 Block Access List sidecar + // carried with the payload (nil pre-Amsterdam). It is NOT part of the block's + // RLP/consensus encoding or hash — never add it to EncodeRLP/DecodeRLP/ + // payloadSize. The header's BlockAccessListHash is the consensus commitment. + blockAccessList []byte + // binaryTransactions optionally caches the transactions' encodings (e.g. from // an engine_newPayload payload) so RawBody() can skip re-encoding them. binaryTransactions BinaryTransactions @@ -1380,6 +1387,14 @@ func (b *Block) ParentBeaconBlockRoot() *common.Hash { return b.header.ParentBea func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash } func (b *Block) BlockAccessListHash() *common.Hash { return b.header.BlockAccessListHash } +// BlockAccessList returns the RLP-encoded EIP-7928 BAL sidecar carried with the +// payload (nil when absent). It is not part of the block's RLP encoding or hash. +func (b *Block) BlockAccessList() []byte { return b.blockAccessList } + +// SetBlockAccessList attaches the RLP-encoded BAL sidecar to the block, copying +// the input so a transaction-owned or later-mutated source cannot alias it. +func (b *Block) SetBlockAccessList(bal []byte) { b.blockAccessList = bytes.Clone(bal) } + // Header returns a deep-copy of the entire block header using CopyHeader() func (b *Block) Header() *Header { return CopyHeader(b.header) } func (b *Block) HeaderNoCopy() *Header { return b.header } @@ -1540,10 +1555,11 @@ func (b *Block) Copy() *Block { } newB := &Block{ - header: CopyHeader(b.header), - uncles: uncles, - transactions: CopyTxs(b.transactions), - withdrawals: withdrawals, + header: CopyHeader(b.header), + uncles: uncles, + transactions: CopyTxs(b.transactions), + withdrawals: withdrawals, + blockAccessList: bytes.Clone(b.blockAccessList), } szCopy := b.size.Load() newB.size.Store(szCopy) @@ -1557,10 +1573,11 @@ func (b *Block) WithSeal(header *Header) *Block { headerCopy.mutable = false headerCopy.hash.Store(nil) // invalidate cached hash return &Block{ - header: headerCopy, - transactions: b.transactions, - uncles: b.uncles, - withdrawals: b.withdrawals, + header: headerCopy, + transactions: b.transactions, + uncles: b.uncles, + withdrawals: b.withdrawals, + blockAccessList: b.blockAccessList, } } diff --git a/execution/types/block_test.go b/execution/types/block_test.go index 2983c19967c..63100628f6f 100644 --- a/execution/types/block_test.go +++ b/execution/types/block_test.go @@ -162,6 +162,38 @@ func TestBlockEncoding(t *testing.T) { } } +// TestBlockAccessListNotInEncoding pins the invariant that the BAL sidecar is +// carried out-of-band: it must never enter the block's RLP encoding or hash. +func TestBlockAccessListNotInEncoding(t *testing.T) { + t.Parallel() + blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + hashBefore := block.Hash() + block.SetBlockAccessList([]byte{0x01, 0x02, 0x03}) + if got := block.Hash(); got != hashBefore { + t.Errorf("BAL changed block hash: got %x want %x", got, hashBefore) + } + enc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(enc, blockEnc) { + t.Errorf("BAL leaked into block RLP:\ngot: %x\nwant: %x", enc, blockEnc) + } + + var decoded Block + if err := rlp.DecodeBytes(enc, &decoded); err != nil { + t.Fatal("decode error: ", err) + } + if decoded.BlockAccessList() != nil { + t.Errorf("BAL survived RLP round-trip (must be a non-encoded sidecar): %x", decoded.BlockAccessList()) + } +} + func TestEIP1559BlockEncoding(t *testing.T) { t.Parallel() blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0")