execution: carry the block access list as a first-class block object - #22894
Conversation
… block access list as a first-class block object EIP-7928 Block Access Lists are part of the payload and should be consumed by execution from the block it is handed, the same model as headers and bodies. Previously the BAL only existed transiently on RawBlock during InsertBlocks and RawBlock.AsBlock dropped it, so the block object execution processes never carried its own BAL and exec re-read it from the DB. - types.Block gains an unexported blockAccessList sidecar (BlockAccessList / SetBlockAccessList), carried out-of-band and never in the block RLP/hash; RawBlock.AsBlock/Copy/WithSeal transfer it. SetBlockAccessList copies the input so a transaction-owned slice cannot alias it. Regression test pins it out of RLP/hash. - newPayload attaches the payload's BAL to the block. - Execution consumes the BAL from the block, falling back to the DB sidecar for snapshot/forward-sync blocks that don't carry it. - rawdb.ReadBlock populates the BAL sidecar as secondary storage.
common.Copy is marked //go:fix inline, so vet's inline analyzer flags any call to it. The rest of the tree already uses bytes.Clone directly — this was the only remaining call site. Claude-Session: https://claude.ai/code/session_01UYCsHj9HYTJnqUCy8a7W91
|
@mh0lt AccessList must be field of |
|
@mh0lt nits:
|
| // BlockOverlay is active. ProcessBAL still computes+validates the BAL | ||
| // from the write-set as the ultimate fallback. | ||
| data := b.BlockAccessList() | ||
| if len(data) == 0 { |
There was a problem hiding this comment.
if len(data) == 0 && header.HasBAL() to save an unnecessary call to DB
| 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 { |
Follow-up to erigontech#22894 addressing the [staged-sync review comment](https://github.com/erigontech/erigon/pull/22894/changes#r3689214008) and the [rawdb review comment](https://github.com/erigontech/erigon/pull/22894/changes#r3689241839). The original `Header.HasBAL()` predicate conflated two states: whether the BAL commitment field is present and whether it commits to a non-empty BAL. Those meanings need different behavior for a canonical empty BAL encoded as `0xc0`. This change: - defines `Header.HasBAL()` as presence of the BAL commitment field; - adds `Header.HasNonEmptyBAL()` for a commitment other than the canonical empty-list hash; - uses `HasNonEmptyBAL()` in staged sync and backward BAL fetching to avoid unnecessary database and network work; - keeps `rawdb.ReadBlock` on `HasBAL()` so a stored empty `0xc0` sidecar is preserved; - covers missing, empty, and non-empty commitments, including the required single DB lookup for a missing non-empty sidecar; - makes `TestAssembleBlockWithFreshlyAddedTxns` wait for observed txpool polls instead of a fixed delay. This keeps block reconstruction faithful while retaining the intended optimization for BAL consumers that do not need an empty sidecar. The builder-test change removes timing dependence and does not change production block-building behavior. TDD note: the CI stabilization is a test-only synchronization refactor, so no production behavior was changed. The failed race-test job is the original red signal. Validation: - `go test ./execution/types ./db/rawdb ./execution/stagedsync ./execution/p2p -count=1` - `go test ./execution/stagedsync ./execution/execmodule -run '^(TestBlockAccessListBytes|TestAssembleBlockWithFreshlyAddedTxns)$' -count=20` - `ERIGON_EXEC3_PARALLEL=true GOMAXPROCS=4 go test -race -timeout=10m ./execution/stagedsync ./execution/execmodule -run '^(TestBlockAccessListBytes|TestAssembleBlockWithFreshlyAddedTxns)$' -count=50` - `ERIGON_EXEC3_PARALLEL=true GOMAXPROCS=4 go test -race -timeout=60m ./execution/execmodule -count=3` - `go test ./execution/stagedsync ./execution/execmodule -count=1` - `make lint` (four consecutive clean runs) - `make erigon integration`
- removes `block.SetBlockAccessList` in favour of always passing it via block constructors as we do for withdrawals, transactions, header, etc - we can now also simplify our API by removing `bal` from `execModule.InsertBlocks` because they are now attached to the block after erigontech#22894
Independent refactor split out of #21414 (per the request to break it up). Stands alone on
main.EIP-7928 Block Access Lists are part of the payload and should be consumed by execution from the block it is handed — the same model as headers and bodies. Previously the BAL only existed transiently on
RawBlockduring InsertBlocks andRawBlock.AsBlockdropped it, so the block object never carried its own BAL and exec re-read it from the DB.types.Blockgains an unexportedblockAccessListsidecar (BlockAccessList/SetBlockAccessList), carried out-of-band and never in the block RLP/hash;SetBlockAccessListcopies the input so a tx-owned slice can't alias it. Regression test pins it out of RLP/hash.Behaviour-preserving: the
!dbg.IgnoreBALgate and BAL validation are unchanged — this only changes where exec sources the BAL (block vs redundant DB re-read). It's the plumbing that lets the parallel/fold-ahead commitment (in #21414) consume the BAL; nothing computes commitment in parallel from it by itself. Part of the split recorded on #21414.