Skip to content

Commit b1fa33d

Browse files
LukaszRozmejflcl42benaadamskamilchodolaMarchhill
authored
perf(bal): verify-only fast path — skip end-of-block encode + Keccak (#11659)
* Add EIP-8037 and Amsterdam fixes * Run all tests * Align EIP-7928 BlockAccessIndex with uint32 spec, add validation (#11362) * fix(bal): align EIP-7928 BlockAccessIndex with uint32 spec, add validation Widens BlockAccessIndex from uint16 to uint32 per EIP-7928 (commit 645099785a) and geth bal-devnet-4. Hardens the BAL decoder so truncated/malformed wire bytes produce a clean RlpException at engine_newPayloadV5 instead of crashing. Adds the missing validation rules geth bal-devnet-4 enforces: empty storage_changes per slot is rejected, and BlockAccessIndex is bounded by [0, txCount + 1]. Decoder primitives: - New Rlp.ValueDecoderContext.DecodeUInt() / RlpStream.Encode(uint) / Rlp.LengthOf(uint) helpers. - Rlp.Decode<T> and Rlp.DecodeArrayPool<T> wrap IndexOutOfRangeException / ArgumentOutOfRangeException as RlpException so any truncated-RLP primitive read surfaces consistently. - BlockAccessListDecoder rejects an empty AccountChanges entry (0xc0 inside the outer list) and SlotChangesDecoder rejects an empty StorageChange list for a slot — geth bal-devnet-4 "empty storage writes" parity. Type widening: - IIndexedChange.Index, BalanceChange/NonceChange/CodeChange/StorageChange.Index, BlockAccessList.Index and BlockAccessIndex on the Change journal record all become uint. SortedList<int, T> keys flip to SortedList<uint, T>; ushort query parameters on AccountChanges become uint. Prestate sentinel preservation: - Replaces the legacy -1 int sentinel with Eip7928Constants.PrestateIndex (uint.MaxValue). Adds PrestateAwareIndexComparer that orders the sentinel before all real indices, restoring the iteration semantics that AccountChanges.GetBalance/GetNonce/GetCode/AccountExists and ApplyStateChanges' [^1].Index check rely on. - Decoder-built SortedLists also use the prestate-aware comparer so that LoadPreStateToSuggestedBlockAccessList grafting prestate onto the suggested BAL after decode keeps it sorted first. - Loop predicates that compare change.Key directly against blockAccessIndex explicitly skip PrestateIndex so the raw uint comparison doesn't trigger on the huge sentinel value. Validation: - BlockValidator gains ValidateBlockLevelAccessListIndexBounds enforcing index <= txCount + 1 (mirrors geth's index < txCount + 2 check) with a new BlockLevelAccessListIndexOutOfRange error message. Tests: - New PrestateAwareIndexComparerTests, AccountChangesPrestateTests covering the comparer and prestate-fallback iteration semantics. - BlockAccessListDecoderTests adds: empty-bytes / truncated-outer-list / inner-empty-list throw RlpException; empty storage_changes per slot throw; decoded SlotChanges accepts a later prestate graft as first; BalanceChange round-trips for indices 0x10_0000 and uint.MaxValue. - BlockValidatorTests adds tx-index bound cases (0, 1 valid; 2, uint.MaxValue rejected for a 0-tx block). - ExecutionPayloadV4Tests covers the engine-API decoding-error path for malformed BAL bytes. * style: drop unused usings flagged by IDE0005 CI surfaced four IDE0005 warnings (treated as errors): - BlockAccessListManager: drop `using Nethermind.Crypto;` — Keccak.OfAnEmptySequenceRlp comes from Nethermind.Core.Crypto, already imported. - PrestateAwareIndexComparerTests / AccountChangesPrestateTests: drop `using Nethermind.Core;` — Eip7928Constants resolves via the test's parent namespace Nethermind.Core.Test.BlockAccessLists. - Eip8037Tests: drop `using System;` — no System.* type referenced directly. * fix(bal): record system pre/post-block SSTOREs as reads per EIP-7928 v5.7.0 EIP-7928 v5.7.0 specifies that SSTOREs performed during system pre-block calls (EIP-2935 BlockHashHistory, EIP-4788 BeaconRootContract) and post-block calls (EIP-7002 withdrawal requests, EIP-7251 consolidation requests) are recorded in the BAL as storage reads on the touched slot — not as storage changes with post-values. Same-value writes are skipped entirely. Without this, nethermind generated a BAL whose Keccak256 differs from what eels-built fixtures expect, so the BlockAccessListHash check fails for every Amsterdam block that touches a system contract slot (most pyspec tests). IWorldState gains an opt-in scope: IDisposable? BeginSystemPreBlockScope() TracedAccessWorldState implements it via an int depth counter. While depth > 0, Set(storageCell, value) reclassifies the recording: AddStorageRead when the slot value would change, no-op when unchanged. The state mutation still applies via the inner world state. BlockAccessListManager wraps the three system contract entry points with the scope: StoreBeaconRoot (EIP-4788 system tx), ApplyBlockhashStateChanges (EIP-2935 fast-path Set), and ProcessExecutionRequests (EIP-7002 / EIP-7251 post-block system txs). Parallel-mode state application: In parallel processing, non-system slots wrap stateProvider with BlockAccessListBasedWorldState whose Set is a no-op — actual state mutation relies on ApplyStateChanges replaying the suggested BAL's storage_changes. With the spec-correct BAL containing reads instead of changes for the system slots, ApplyStateChanges has nothing to replay for them, so the canonical state would diverge. TxProcessorWithWorldState gains an `isSystemSlot` parameter. The ParallelTxProcessorWithWorldStateManager passes `isSystemSlot: i == 0 || i == _len - 1` (pre-execution and post-execution slots). For those slots, the TracedAccessWorldState wraps stateProvider directly, so system pre/post-block SSTOREs mutate the canonical state regardless of BAL recording. Tx slots (1..n) keep the BAL-backed wrapping unchanged. Sequential pyspec tests (which is what the Ethereum.Blockchain.Pyspec.Test suite runs) are unaffected by the parallel slot change but benefit from the BAL recording fix; the BlockAccessListHash check now passes for blocks that previously failed solely on system pre-block storage encoding. Note: a residual InvalidStateRoot mismatch remains on a subset of Amsterdam pyspec tests (~70/360 ecrecover_weird_v + a similar slice of stMemoryStress). These were previously masked by the BAL hash error firing first. The remaining state divergence appears unrelated to BAL recording and is left for follow-up. * test(jsonrpc): update Eth_get_block_access_list_by_* fixtures for EIP-7928 v5.7.0 The Eth_get_block_access_list_by_hash and _by_number tests had hardcoded the pre-v5.7.0 shape, recording the EIP-2935 BlockHashHistory system pre-block SSTORE as a storageChanges entry with the post-value. v5.7.0 records system pre-block SSTOREs as storageReads (slot key only). Updated both expected JSON strings to match the new spec-compliant output. * Revert "fix(bal): record system pre/post-block SSTOREs as reads per EIP-7928 v5.7.0" This reverts commit 364f294826d448eb76bf61b3a80f42351a26ea0b. * Revert "test(jsonrpc): update Eth_get_block_access_list_by_* fixtures for EIP-7928 v5.7.0" This reverts commit 937ca5d1f970dab62e7d51d2ebff10e8d5151f15. * review: address PR #11362 feedback - Rlp.DecodeArrayPool<T>: dispose partially-allocated ArrayPoolList<T> before wrapping IndexOutOfRangeException/ArgumentOutOfRangeException as RlpException so the rented buffer is returned to the pool. - Rlp.ValueDecoderContext.DecodeUInt(): collapse case 0 to a single `return RlpHelpers.ThrowNonCanonicalInteger(Position)` (DoesNotReturn, uint) to match DecodeInt and drop the dead `return default`. - BlockAccessListManager.GetPostExecution(): use uint.MaxValue literal to match the uint? balIndex parameter. - AccountChanges.SlotChangesAtIndex: build the returned SortedList with PrestateAwareIndexComparer.Instance so a later prestate graft sorts first, matching the rest of the codebase. - PrestateAwareIndexComparer xmldoc: clarify that decoded BALs also use this comparer (so later prestate grafting preserves order). * test(pyspec): skip EELS bal@v5.7.0 ported_static fixtures with legacy state-test conversion bug EELS's `from_state_test` conversion for ported_static tests omits the EIP-2935 / EIP-4788 system pre-block SSTORE entries from the suggested BAL, while a real client (and Nethermind) executes them — so every such fixture's BlockAccessListHash diverges from what we compute. 91 such tests were the entire residual pyspec failure set on the bal-devnet-4a branch. Detect the conversion via the legacy difficulty value 0x20000 baked into the post-merge mixHash field — real prevRandao would never be exactly 0x...020000 — and Assert.Ignore those tests. Track upstream EELS fix; remove the guard once bal@>v5.7.0 ships with the system pre-block SSTOREs included in the BAL. * fix(pyspec test): drop ?-annotation in non-nullable file CS8632: PyspecTestFixture.cs is not under `#nullable enable`, so the `string?` introduced in 6fb652762b broke the build of every pyspec job. `string` works the same here — the value already gets a null check on the next line. * test(pyspec): also skip blockchain_test_engine_from_state_test variant The first guard only walked test.Blocks, which is null for engine fixtures. Engine fixtures keep the same legacy 0x...020000 sentinel, just on the JSON `prevRandao` field of the engine_newPayload params. Walk EngineNewPayloads too. * Update tests * fix(eip-8037): pin cost_per_state_byte at static 1174 for bal-devnet-4 bal-devnet-4 keeps cost_per_state_byte static at 1174 (carried over from bal-devnet-3), removing the per-block-gas-limit scaling formula that an earlier draft of EIP-8037 specified. snøbal-devnet-4 fixtures encode the same gas usage (63574) at 1M / 5M / 10M / 30M block gas limits, confirming the value is now invariant across blockGasLimit. Reduce CalculateCostPerStateByte to a direct return of CostPerStateByte and drop the dynamic-quantization helpers and BitOperations dependency. The blockGasLimit parameter is retained on call sites in case a future devnet revisits per-block scaling. Update the EIP-8037 unit test to pin the static behaviour rather than asserting quantized values. * fix(test): match TCS Exception type to IncrementalValidation signature * fix test * fixes * fix * Fixes * lint * update tests * fix tests * fix ci * perf: optimize BAL lookups and eliminate redundant GetCodeHash calls - Replace SortedDictionary with Dictionary in BlockAccessList for O(1) account lookups on the EVM hot path (was O(log n) with 20-byte span comparisons). Sorted enumeration preserved for encoding/validation. - Merge TryGetDelegation + GetCachedCodeInfo into a single call in InstructionCall, eliminating a redundant GetCodeHash per CALL opcode. - Inline IsDeadAccount in EXTCODEHASH to avoid a second GetCodeHash call for the same address. * remove skips * More pyspec test chunks * Squashed commit of the following: commit 3a3078f428e24435464d33676e50c50eacb9644e Author: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Tue Apr 28 18:23:03 2026 +0200 delete old tests commit 95de888a18a16a38ed425bda0a70c4235de96d9c Merge: 244c2c60b1 31a673a6a4 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 18:08:12 2026 +0200 Merge branch 'master' into engine-api-glamsterdam-cleanup commit 244c2c60b1873b1d732025b0240c154ecbbe6dd0 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 18:05:32 2026 +0200 fix lint commit 9194b8f54322915a57ecbc0acbb9b4b71a7508b5 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 18:04:19 2026 +0200 remove tests commit eedfbb775a018860ef0a6b51822589752ac5ea76 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 17:57:46 2026 +0200 revert formatting changes and zero hash stuff commit dc71aa5093665c75a5fb126ffad835cc0b81ee3a Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 17:38:42 2026 +0200 cleanups commit ec2fce3609d2e06fa856cb0c545d6fb434900391 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 17:33:11 2026 +0200 fixing commit 90a1da8c5f816cc3965cadd98f28ceb31b236bf6 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 17:15:58 2026 +0200 remove test commit 4451656d1c79855c0d6c23c0c1d9a3f0b9755139 Author: MarekM25 <marekm2504@gmail.com> Date: Tue Apr 28 17:12:32 2026 +0200 Cleanup mess commit 2425748d949b3ac4364b549d66abbb0427d65289 Merge: 4a904608e4 a36154c39a Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 27 14:33:36 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 4a904608e48750d3f20cbf2554294f7e64969a0a Merge: 35497680be 18d60a482b Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 27 14:08:25 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 35497680be5f1d60b672e72f66d2949833dc82d0 Merge: 30fcc367cd b71c3528d0 Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 27 14:07:39 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 30fcc367cd486bbd0a5611d943bdf2eff2956a9b Merge: 540e4a2fd1 ed6a354e6a Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 27 13:09:48 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 540e4a2fd1774064c1cff747118a3ae4a7644be0 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 04:52:49 2026 +0300 minor fixes commit 010547be9ef97dcf853a2b239b7b0bb7a210879a Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 03:22:39 2026 +0300 test fixes commit 6fcc136f54a8acc1b1266103230b772f302f7c09 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 02:11:12 2026 +0300 fixes commit 0bccb0649f8c9d6c7c689d61b5635aae0e9bc191 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 01:52:02 2026 +0300 test fix commit 830c578eb2388140e2141b4639313fa83f48d854 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 01:38:55 2026 +0300 fix tests commit 1f5fd2cff7221d0f72dfe0f994567ef883710f4e Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 01:35:17 2026 +0300 claude review again commit d87491f353d6525dc50c398df01b2990f4c692f2 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 00:57:57 2026 +0300 fixes commit dd431e29428ae75d0586af2981f8764510ba3b47 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sun Apr 26 00:51:52 2026 +0300 fix tests commit 71dc99f55d9d7055f05ea5bb9bc89a35954e42c0 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sat Apr 25 22:40:50 2026 +0300 fixes commit 9c03d12606e21bbc6306d70419437a7c55d535e1 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sat Apr 25 22:32:58 2026 +0300 update per 786 commit a4c4f3c4134f6d0c18c7ed3da162079a794b3435 Merge: fc49e7e1fe 9cba44cda1 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Sat Apr 25 21:38:25 2026 +0300 Merge remote-tracking branch 'origin/master' into engine-api-glamsterdam commit fc49e7e1fe858eafdc895483189e17da9751a237 Merge: bca0c63446 42c551f4ab Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 20 16:14:33 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit bca0c634462fadb7a479dcb2e63786b70845ede1 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Mon Apr 20 16:14:10 2026 +0300 remove unused import commit 3d2c973a68c5ce443a468790766905dfcbf0d7f0 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Mon Apr 20 16:09:17 2026 +0300 fix taiko tests commit b30ef5c436dd6a2e6c7f352245205a41e0b26cd7 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Mon Apr 20 15:34:35 2026 +0300 fix tests commit 6f131cd7afd097785a24635a5bed232a3b836064 Merge: a8c884a092 0fe41f4176 Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 20 14:55:46 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit a8c884a092cc7e7fcd4f38a7bd9d30e0e5836b06 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Mon Apr 20 14:54:40 2026 +0300 conflicts fix commit 96eb9124bbeac213070c5d29759e79010df4b310 Merge: 82a1f70572 425a2a3fe8 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Mon Apr 20 14:52:33 2026 +0300 Merge remote-tracking branch 'origin/master' into engine-api-glamsterdam # Conflicts: # src/Nethermind/Nethermind.Merge.Plugin/BlockTreeExtensions.cs # src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs # src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs commit 82a1f705728f739c85b650ff8316f2a2ebd1f7e6 Merge: 7b6133f3a8 436c65bbc3 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Mon Apr 20 13:16:55 2026 +0300 Merge remote-tracking branch 'origin/master' into engine-api-glamsterdam commit 7b6133f3a89ffc60106e88b7c5b4f95d4feae20f Merge: 2f068ec5b3 02c202601b Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Thu Apr 16 14:49:46 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 2f068ec5b3f8e1a4742bfd29fbafbf620609e40a Merge: bdd7ee59c4 5464c8ba2b Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Thu Apr 16 14:10:02 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit bdd7ee59c40d65d664cbadbada4e6708581eceb7 Merge: f09cb41ddd bfaaeb0f53 Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Wed Apr 15 16:45:19 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit f09cb41ddd3293a1269d137796187cdb1babcaf7 Merge: 370acdf4c4 cf03d184f5 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Wed Apr 15 16:24:17 2026 +0300 Merge remote-tracking branch 'origin/master' into engine-api-glamsterdam commit 370acdf4c4a587c46022feb6f16837422dfaa5f2 Merge: d1d0370429 6cd0ea49db Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Mon Apr 13 14:28:21 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit d1d0370429045dc3819dbbe4c09785227526f21e Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Fri Apr 10 17:30:54 2026 +0300 more fixes commit c99d6cc7fb4391d9f37862ea47ffa0b830c5cf51 Merge: 4d0f215ad0 a52cb90edc Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Fri Apr 10 17:24:06 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 4d0f215ad0e184ac4e4e73a2411eae0d80b69100 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Fri Apr 10 17:23:42 2026 +0300 claude review commit cb6defe8ce8196fe7fdda28b35a1376e3e72f5e1 Merge: af63142ba1 0057bd83c2 Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Fri Apr 10 12:34:37 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit af63142ba12aaddd443cb124ddea4af41f4785e4 Merge: 1b338f380d 2f24891849 Author: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Date: Fri Apr 10 12:08:36 2026 +0300 Merge branch 'master' into engine-api-glamsterdam commit 1b338f380ddff161e66a20a7fad584578d9214ed Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Fri Apr 10 12:08:13 2026 +0300 claude review commit 619259b75cb57403ab826708b1127f65412905a5 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Thu Apr 9 18:32:59 2026 +0300 taiko fix commit d06779353879b10f287b7405c319f23540125a52 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Thu Apr 9 18:19:14 2026 +0300 cleaner design commit 1e27be19466204dc0bfbb515edde6dbd90053d52 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Thu Apr 9 18:12:04 2026 +0300 improve tests matching the specs commit a77e1827d4fb7d5e0a3a8739c8747a9a43279b7e Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Thu Apr 9 18:04:58 2026 +0300 improve test commit 4ba069c9c65ce7e7b4922d63c4e5341c17a80f71 Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Thu Apr 9 18:01:59 2026 +0300 fix comment commit fe6f9b094eeeccc97158d1652dfa5e03275d78ca Author: stavrosvl7 <stavrosvl7@gmail.com> Date: Thu Apr 9 17:53:57 2026 +0300 engine api changes as per https://github.com/ethereum/execution-apis/pull/770 https://github.com/ethereum/execution-apis/pull/760 * update tests * remove skips * halt changes * fixes * fix * Bump pyspec fixtures to snobal-devnet-6@v1.1.0 * fix spill * Don't refund spilled state gas on top-level halt * Refund full state gas on top-level revert; only reservoir-portion on halt Top-level REVERT preserves gas_left, so the spilled portion of state_gas_used (originally drawn from gas_left) is still in the user's pocket and must be refunded to the reservoir alongside the reservoir-funded portion. Top-level halt burns gas_left, so the spilled portion was paid out of gas the user can no longer reclaim — only the reservoir-funded portion is restorable. Splits RefundRevertedTopLevelStateGas into a revert path (full refund) and a new RefundHaltedTopLevelStateGas (reservoir-only, spill discarded) and wires the halt error sites to the latter. * fix: address review - replace LINQ OrderBy with List.Sort, consistent GetContentLength * fix * fix * Cover missing pyspec fork dirs + add sync and transaction test types The pyspec fixture archive ships three uncovered directories: a stray state- tests transition fork, plus two test types we never wired in. Adds: - CancunToPragueAtTime15kStateTests — fills a one-class gap in Tests.cs - PyspecSyncBlockchainTestFixture + AmsterdamSyncBlockchainTests, OsakaSyncBlockchainTests — runs blockchain_tests_sync fixtures through the engine harness; the additional syncPayload field is left for a follow-up - TestType.Transaction + TransactionTest/Json/Base + ConvertTransactionTests + PyspecTransactionTestFixture — decodes raw txbytes via Rlp.Decode and matches expected EEST exception tokens (TYPE_4_*) against the validator's ValidationResult.Error or RLP decode message; covers AmsterdamTxTests, OsakaTxTests, PragueTxTests fork directories Bumps FlatDB pyspec chunking from 4 to 16 to match the regular workflow — ~860 tests/shard instead of ~3,437/shard, well under the 256/matrix cap and the 20-minute job timeout. * Moar tests * fx * Filter known-broken from_state_test BAL revert tests; relax BAL error match Two CI failures in the new EEST snobal-devnet-6 fixtures around BAL validation in negative and revert scenarios: - Class B (negative tests, 15 cases): EEST's *_from_state_test synthesized blockchain test versions of state tests ship an incomplete suggested BAL for transactions expected to fail at tx-level. Nethermind correctly rejects the block but via "InvalidBlockLevelAccessList: missing/surplus account changes" rather than the expected TransactionException. The block IS invalid; only the failure mode differs. AssertValidationError now accepts BAL missing/surplus/incorrect-changes errors as a valid alternative outcome. - Class A (positive tests, 4 cases): Nethermind's generated BAL for REVERT/TSTORE scenarios disagrees with the suggested BAL on intermediate storage values, even though the final post-state matches. Filter these four specific from_state_test variants out of Pyspec test loading until the BAL-on-revert behavior is investigated. Original state tests in PyspecStateTestFixture still cover the same scenarios. * optimise allocations of worldstates, txprocessors, bals * optimise bal structures * fix tests * perf(bal): zero-alloc dict-lookup ValidateBlockAccessList (#11448) * Optimize early validation * Drop redundant `using` on SortedDictionary value-enumerator The Dispose chain through `SortedDictionary.ValueCollection.Enumerator` → `SortedDictionary.Enumerator` → `TreeSet<KVP>.Enumerator` bottoms out at an empty Dispose, so the `using` only adds visual noise around the manual MoveNext/Current control. Concrete struct type still keeps the enumerator unboxed. * Drop unused System.Collections.Generic / System.Linq usings (IDE0005) * Increase tests * fix * optimise bal generation (#11452) * optimise bal generation * change to ref readonly * fix --------- Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> * fix * Revert "fix" This reverts commit 4767a18e6e6c20516a1494f07fea397b1a39ad3e. * Revert "fix" This reverts commit 9870f9732db82393fce8468c04c6d2c9ce804c09. * EIP-8037: stop subtracting state-gas spill from block regular-dim Calculate8037BlockRegularGas was subtracting StateGasSpill from the regular dim's per-tx contribution. Per EELS amsterdam/fork.py:1167-1172, spilled state gas reduces gas_left directly and must remain counted in block_regular_gas; max(sum_regular, sum_state) at block level single- counts it via the dim that wins. Subtracting it under-counted block.gasUsed when sum_regular dominated (HeaderGasUsedMismatch on devnet block 1788: expected 28551136, computed 28438432, diff 112704 = 3×SSetState worth of spill across the block's CREATE-tx init-code paths). Updates 4 unit tests with hardcoded expectations from the previous model and adds a regression test mirroring block 1788 (sub-cap CREATE tx whose init code spills state gas via cold SSTOREs before top-level REVERT, so sum_regular dominates and the spill must remain in the regular dim). Verified against 1369 EIP-8037 / BAL / state-gas pyspec fixtures and 201 EIP-7928/8037/6780 unit tests. * update tests * fx * Revert pyspec fixtures to execution-spec-tests snobal-devnet-6@v1.1.0 The tests-snobal-devnet-6@v1.1.1 tag in ethereum/execution-specs has no fixtures_snobal-devnet-6.tar.gz asset attached (only auto-generated source archives), so every Pyspec shard 404s on download. Point back at ethereum/execution-spec-tests where the fixture artifact is published. * Revert "EIP-8037: stop subtracting state-gas spill from block regular-dim" This reverts commit 3758ae4288. EELS reference (devnets/snobal/6 amsterdam/vm/__init__.py + vm/gas.py) shows regular_gas_used is incremented only by charge_gas (regular ops); charge_state_gas spilling into gas_left increments only state_gas_used, never regular_gas_used. Therefore tx_regular_gas at line 1168 (intrinsic_regular + regular_gas_used) excludes spill. The pre-revert '- stateGasSpill' subtraction is required to recover regular_ops_only from (initial_gas_left - final_gas_left), which includes spill. The original block-1788 mismatch this commit tried to address (HeaderGasUsedMismatch -112704) is a separate halt/revert state-gas restoration bug specific to a CREATE scenario. EELS team confirmed the spec-side test had the same gap and clients (geth, besu, nethermind pre-fix) were aligned via convergent behaviour; spec-side fix is expected next week. Reverting to keep CI green and consensus-aligned with the rest of the network. Block 1788 root-cause investigation moves to a follow-up. * add mapping * Wire transient storage snapshot/restore through BlockAccessListBasedWorldState The parallel BAL builder uses BlockAccessListBasedWorldState as the inner world state, which owns its own TransientStorageProvider but had stub TakeSnapshot/Restore (returned Snapshot.Empty / no-op). On REVERT the EVM calls IWorldState.Restore(snapshot), which in parallel mode reached BALWS and silently dropped the transient-storage rollback — TSTORE writes inside reverting frames leaked into TLOADs after the revert, producing wrong storage / fee values in the generated BAL versus EELS' suggested BAL. Forward TakeSnapshot/Restore to the inner _transientStorageProvider, matching how WorldState wires the same field. Resolves the KnownPyspecBalRevertBugs skip list (deleted): all six failing [ParallelEngine] tests now pass — test_tstore_reentrancy variants, test_subcall[delegatecall_with_invalid], test_trans_storage_reset, test_set_code_max_depth_call_stack, test_10_revert_undoes_store_after_return. Verified locally: 1562 of 1562 pyspec tests in the affected families pass, 194 of 194 EIP-7928/8037/BAL unit tests pass, 72 of 73 State.Test pass (1 pre-existing skip). * Tag sequential Pyspec jobs * Tag regular Pyspec jobs * Use bracket tags for extra test variants * EIP-8037: replace worst-case-OR pre-validation with intrinsic-floor check ValidateTransactionGasAllowance (BlockAccessListManager and TransactionProcessor) rejected a tx whenever its worst-case contribution to EITHER dim exceeded that dim's remaining capacity. Per EIP-8037 the tx contributes (r,s) with r+s ≤ tx.gasLimit and routes between dims; the block fits iff some split satisfies both per-dim caps. Worst-case-OR rejected blocks where each dim's worst-case alone overflowed but the actual two-dim split fit — surfaced on bal-devnet-6 via kurtosis: a 60M block with 4×~16M txs to a state-heavy contract had sum_regular=37.7M and sum_state=57.8M, so block.gasUsed = max() = 57.8M ≤ 60M, but the OR check rejected tx[3] because either worst-case alone exceeded remaining headroom in its dim. NM rejected → all NM nodes diverged from geth+besu. Same shape as the prior 'all prysm-nm nodes struggle' devnet report. Replaces with the spec-correct condition: a tx unavoidably contributes its intrinsic to each dim, so reject only when intrinsic_regular alone overflows the regular dim, intrinsic_state alone overflows the state dim, or the minimum required execution gas exceeds the combined remaining capacity. Anything beyond intrinsic that overflows is caught post-execution by CheckGasUsed using the proper max(Σregular, Σstate) ≤ block.gasLimit formula. Updates Eip7928Tests: - Tx_exceeding_block_gas_limit_rejected_in_parallel_mode renamed and retargeted at the intrinsic-overflow path (a 20k block where the 21k intrinsic alone overflows). The original assertion (tx.gasLimit > block.gasLimit triggers rejection) was itself the bug — under EIP-8037 such a tx is valid as long as its actual execution fits. - New regression Tx_with_gaslimit_above_block_remaining_but_intrinsic_fits_accepted_under_eip8037 pins the now-correct behaviour. BlockProcessorTests.IncrementalValidation_rejects_eip8037_tx_when_worst_case_exceeds_ordered_remaining_gas keeps passing — its scenario also trips the intrinsic-overflow path (after firstTx claims 80k regular, secondTx's 21k intrinsic_regular alone overflows the 20k regular headroom). * EIP-8037: avoid long overflow in combined-capacity admission check The combined-capacity branch `minGasRequired > regularAvailable + stateAvailable` overflowed when pyspec fixtures used block gas_limit near i64.MaxValue (e.g. test_call_bounds: gas_limit=9_223_372_036_854_775_807). The sum wrapped to a negative long, the comparison fired truthy, and tx[0] was rejected with 'Block gas limit exceeded' even though both dims had effectively unbounded headroom. 18 pyspec tests in the (13of16) chunk regressed on PR #11436. Reformulated as: 'tx unavoidably needs minGasRequired across both dims; the regular dim alone covers it iff regularAvailable >= minGasRequired, otherwise the residual minGasRequired - regularAvailable must fit in stateAvailable'. Same semantics, no overflow. * Revert "EIP-8037: avoid long overflow in combined-capacity admission check" This reverts commit 60aa7ef00dc897dd70f1f3f1f9847a8d2c423237. * Revert "EIP-8037: replace worst-case-OR pre-validation with intrinsic-floor check" This reverts commit 83dcde59bf54dede0bc5b97c10bee36a19fd32f4. * Reapply "EIP-8037: replace worst-case-OR pre-validation with intrinsic-floor check" This reverts commit d2410313642445d69c4ef787ee87fd539e9e72a0. * Reapply "EIP-8037: avoid long overflow in combined-capacity admission check" This reverts commit b7c2a725d5e2f304989eb337ce84b59d26f17221. * Revert ValidateTransactionGasAllowance to spec-compliant worst-case-OR The intrinsic-floor / AND variants of this check made block-125-shape kurtosis devnet traffic stay in lockstep, but they diverge from the EELS amsterdam spec text (fork.py:540-560 codifies worst-case-OR exactly) and break test_low_gas_limit[fork_Amsterdam-state_test--g0] which asserts rejection of tx.gasLimit > block.gasLimit on a fresh block. The original devnet-6 block-125 divergence (NM rejects, geth+besu accept) is therefore not in the admission rule itself but in NM's per-tx (regular, state) accumulator that feeds it. Investigating that as a separate fix: geth+besu accept block 125 with worst-case-OR, so their per-tx values must keep totalRegular AND totalState below 60M-15.86M=44.14M after 3 txs while NM's evidently exceeds that threshold. * Surface parallel worker tx-rejection cause without masking When a parallel BAL worker rejects a tx with InvalidBlockException, the incremental validator was running the admission rule and CheckGasUsed before checking the worker's exception slot. With the worker reporting tx.GasLimit on rejection, the cumulative-gas check could trip a follow-on "block gas limit exceeded" that masked the original cause and diverged from the sequential path. Rethrow ParallelExecutionException as soon as the worker's slot carries an exception, and have the worker report (0, 0) so any future consumer of the tuple agrees with sequential semantics. * Invalidate BlockAccessList ItemCount cache on mutations BlockAccessListManager reuses one GeneratedBlockAccessList instance across blocks, while BlockAccessList cached its computed ItemCount without invalidation on Reset, Clear, Merge, or any Add/Restore path. After one validation cached a small count, a later oversized generated BAL would pass the EIP-7928 item-limit check on RLP-imported blocks (and the reverse — a smaller BAL after a larger one — would be rejected). Null _itemCount at the entry of every mutating method on BlockAccessList. The RLP decoder's init-set value still survives for the lifetime of freshly decoded (and never mutated) BALs. Add unit tests covering each mutator and a two-block ValidateProcessedBlock regression that processes the same BAL instance at the floor, then over the floor. * fixes * fix * fixes * fixesfix * Drop duplicate Rlp.ValueDecoderContext.DecodeUInt from #11362 The merge of master left two DecodeUInt() definitions on ValueDecoderContext: one from #11362 (bal-devnet-6) and one from #11479 (master). Keep #11479's version (captures position before ReadByte advances; adds the result < 128 non-canonical-integer check) and remove #11362's. The Encode(uint)/LengthOf(uint) helpers from #11362 are kept since master did not add equivalents. * fixes * tests * Fixes * Align error messages * fix * lint * sentinel change * Fix receipts * CheckPerTxInclusion * initial impl * use task * fix * fix one more time * fix * attempt fix * Feedback * fix * tidy * Revert "attempt fix" This reverts commit 9f78de89669c14c6a5659d0a7a0aadfc83bb5038. * tidy * fix * Fixes * pre-execution in parallel thread * fix * add comment * feedback * Feedback * Feedback * Fix processing stats for parallel * lint * Harden * Update deps * Optimize * Super-optimize * Ultra-optimize * Turbo-optimize * lint * Eldritch-optimize * Forbidden optimizations * fix * Feedback * merge conflict * Hive fixes * fix(bal): defer GetCachedCodeInfo past CALL OOG checks to keep delegation target out of BAL on revert cea517aa20 (perf: optimize BAL lookups...) collapsed the two-step CALL delegation lookup into a single GetCachedCodeInfo call placed before the delegated cold-account-access gas charge. That moved InternalGetCodeInfo of the delegation target ahead of the OOG point, so _worldState.GetCodeHash recorded the target in the BAL even when the CALL OOG'd before its frame was entered. Per EIP-7928 / EELS, the delegation target must only appear in the BAL when the CALL frame actually executes — so its recording must happen after all CALL-level OOG checks pass. Restore upstream's two-step ordering: TryGetDelegation early (records codeSource only by parsing its code), then GetCachedCodeInfo after the delegated cold-access charge and new-account-creation check (records the delegation target only when the call is sure to enter the frame). Surfaced by snobal-devnet-6@v1.1.0 fixtures that exercise CALL/CALLCODE/ DELEGATECALL/STATICCALL × cold/warm × oog_success_minus_1 / oog_after_target_access. Cleared all ~50 Amsterdam blockchain Pyspec failures across 8 chunks. Adds a focused EvmInstructions regression test that constructs an outer CALL into an EIP-7702-delegated EOA with gas exactly one short of the delegation target's cold-access charge and asserts the BAL contains the call target (the EOA) but not the delegation target. The test fails without this fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feedback * Improve validation performance * MOAR * MOAR (fix) * fix lint * Revert "MOAR (fix)" This reverts commit 597551877ff10dc0637953a27059f24cbc20ca54. * Revert "MOAR" This reverts commit b88a14fcad8e317212379b772fdd094e610cc160. * perf(bal): defer slot-array rebuild in ReadOnlyAccountChanges Replace the per-call O(n) InsertSorted in LoadPreStateStorage with a dirty flag and a single O(n log n) Array.Sort triggered lazily on first read of StorageChanges or ChangedSlots. Cumulative cost for a block touching n unique slots drops from O(n²) to O(n log n). Adapted from upstream PR #11455 with two changes: 1. Thread-safety: getters call WaitForPrestate() then EnsureSorted(), which double-checks _sortedDirty under _sortLock. Volatile.Write on the flag publishes the new array references with release semantics so a reader observing dirty=false is also guaranteed to see the updated arrays. Required because parallel tx workers on this branch all wake on the prestate gate and hit StorageChanges concurrently. 2. Sentinel: tests use Eip7928Constants.PrestateIndex (uint.MaxValue) / uint literals instead of -1; the upstream PR predates the int->uint widening of IIndexedChange.Index. Co-authored-by: kamilchodola * Reduce sorting * Partial feedback * Partial feedback * Improve test runners * Add pyspec memory monitor * More stable tests * Kamil fix * fix * Revert "Kamil fix" This reverts commit 7954eaa489a0a83195eba4ac2b9d5a24787135b3. * Reduce code changes * Reduce code * dedupe tests * docs(bal): address Claude review feedback on PR #11511 Seven of eight review comments addressed; the leftover IBlockAccessListManager TODO is intentionally kept as-is. - BlockAccessListBasedWorldState.{Get,GetOriginal}: extract shared GetAtCurrentIndex and document that EIP-2200 "original" and "current" collapse to the same BAL slot here (intra-tx writes go through the per-tx journal, not back into this state). - ReadOnlyAccountChanges.LoadPreState*: document the per-account realloc cost (~3 small arrays per account) and the trade-off against the cross-cutting reads that a separate-prestate-field design would impose; kept simple deliberately. - ReadOnlyAccountChanges.WaitForPrestate: spell out the two scheduling invariants (loader must not call WaitForPrestate; ParallelUnbalancedWork must guarantee slot 0 a thread). Mirrored at the parallel-loop call site. - GeneratedAccountChanges.SlotChangesAtIndexEqual: dispose the SortedDictionary value-enumerator in finally so a future BCL change to its Dispose semantics doesn't leak. - BlockAccessListAtIndex.AddNonceChange: document the BAL convention that newNonce == 0 means "no recorded nonce change", not "set to 0". - BlockAccessListManager.ApplyStateChanges: document the precondition that the BAL has been prestate-loaded, and explain the GetBalance(0) fallback to zero (created-mid-block accounts). - TracedAccessWorldState: class-level remarks documenting the SetGeneratingBlockAccessList setup contract; field comment noting the deliberate fail-fast on null. * fix lint * Fix bad merge * fix lint * perf(bal): cut per-SLOAD allocations and SortedDictionary insert cost Three review comments on the parallel BAL hot path: - ReadOnlySlotChanges.Get(uint blockAccessIndex) used to return a fresh leading-zero-stripped byte[] every call (one alloc per SLOAD through BlockAccessListBasedWorldState). Now takes a caller-owned Span<byte> buffer and returns a slice. The single caller (BlockAccessListBasedWorldState.GetAtCurrentIndex) owns a 32-byte instance scratch buffer — one per parallel worker, single-threaded use. - TracedAccessWorldState.GetInternal did the same thing for intra-tx SLOADs: MemoryMarshal.CreateReadOnlySpan(...).ToArray() allocated a new byte[32] per call. Replaced with a 32-byte instance scratch buffer on TracedAccessWorldState (also rented per-worker from the pool). - BlockAccessListAtIndex._accountChanges was a SortedDictionary keyed on Address. The sorted property is only consumed once at merge time, by GeneratedBlockAccessList which has its own SortedDictionary doing the re-sort anyway. Swapped for a plain Dictionary so AddBalanceChange / AddNonceChange / AddStorageChange / AddAccountRead / etc. are O(1) instead of O(log n) on the per-tx hot path. Test update: AccountChangesPrestateTests.Slot_get_returns_prestate_value adopts the new ReadOnlySlotChanges.Get(uint, Span<byte>) signature. * test(pyspec): bump fixtures to bal@v7.0.0 Point pyspec fixture download at the bal-devnet-7 mirror release on execution-spec-tests (bal@v7.0.0 / fixtures_bal.tar.gz), tracking the tests-bal@v7.0.0 tag on execution-specs. * Update EIP-8037 gas constants * Add EIP-7928 raw block access list RPC * Fix EIP-8037 reverted state gas accounting * Fix EIP-8037 block state gas accounting for EIP-7702 refunds * Add EIP-8037 CREATE collision gas regressions * Extend EIP-8037 same-tx selfdestruct regressions * Add EIP-8037 selfdestruct beneficiary charging regression * Clean up EIP-7928 decoder test comments * Cover eth/71 protocol inheritance * Verify post-merge eth capabilities * Add EIP-8037 selfdestruct beneficiary variants * Update EIP-8037 block gas budget regression * Update EIP-7928 BAL expectations for EIP-8037 * Cover EIP-7702 BAL authorization rejections * Cover EIP-7702 BAL delegation chains * Cover EIP-7702 BAL precompile delegation * Cover EIP-7928 same-tx selfdestruct storage * Cover EIP-7928 CREATE2 recreation BAL * Cover EIP-7928 selfdestruct sender BAL * Cover EIP-7928 EXTCODEHASH boundaries * Error message consistency * Fix EIP-8037 reverted state gas accounting * Update errors * Update messages * Simplify EIP-8037 state gas constants * Return stored raw block access list RLP * Clean up EIP-8037 state gas helpers * Shorten blob gas validation note * refactor(bal): use GasValidationResultSlot in IncrementalValidation The GasValidationResultSlot type from master already lives in the tree but wasn't being used — every IncrementalValidation signature carried a verbose TaskCompletionSource<(long BlockGasUsed, long BlockStateGasUsed, IntrinsicGas<EthereumGasPolicy> IntrinsicGas, InvalidBlockException? Exception)>[] instead. Swap to GasValidationResultSlot[] across IBlockAccessListManager and its two implementations, the parallel executor, and the tests. Worker sites become TrySetResult(new GasValidationResult(...)) and the validator destructures gasResults[j].GetResult() into the typed record. Functionally equivalent — GasValidationResultSlot.GetResult blocks via Monitor.Wait the same way TaskCompletionSource.Task.GetAwaiter().GetResult did, and TrySetCanceled likewise dispatches a TaskCanceledException via ExceptionDispatchInfo. The Task preExecutionTask parameter is kept since our parallel-loop has a separate pre-execution iteration. * test(eip8037): replace opaque PR 2703 references with descriptive names Renamed Spec_pr2703_* tests to describe the scenario they pin, and rephrased docstrings / inline comments / failure messages that anchored to the upstream PR number. Reader no longer needs to open execution-specs PR 2703 to know what each test covers. Eip8037BlockGasIntegrationTests: Spec_pr2703_boundary_state_exact_fit_accepts -> State_dimension_exact_fit_at_block_gas_limit_accepts Spec_pr2703_boundary_state_exceeded_by_one_rejects -> State_dimension_one_over_block_gas_limit_rejects Spec_pr2703_creation_tx_regular_check_actual_usage_modest_accepts -> Creation_tx_intrinsic_state_excluded_from_regular_worst_case_accepts Spec_pr2703_single_tx_state_check_exceeds_block_limit_rejects -> Single_tx_state_worst_case_over_block_gas_limit_rejects_at_inclusion Spec_pr2703_creation_tx_state_check_exceeded_rejects -> Creation_tx_state_worst_case_over_remaining_state_budget_rejects_at_inclusion Spec_pr2703_eip7825_cap_with_modest_actual_gas_accepts -> Regular_worst_case_capped_by_eip7825_with_modest_post_exec_gas_accepts Eip8037BlockGasInclusionCheckTests + BlockProcessorTests: rephrased "PR 2703 test_..." section comments into prose describing what the case pins. Production-code spec anchors (BlockAccessListManager, Eip8037BlockGasInclusionCheck, EthereumGasPolicy) keep their "execution-specs PR 2703" references — they were deliberately added as spec citations and are useful for reviewers tracking the rule back to upstream. * perf(bal): port master's BlockAccessListValidationIndex for fast incremental validation Ports upstream's column-oriented validation index (lost to merge-conflict "take ours" resolution when bal-devnet-6 merged into master, since master's implementation was bound to the unified BlockAccessList / AccountChanges types we'd already split into ReadOnly*/Generated*/*AtIndex on this branch). The index flattens the suggested BAL into 4 column-oriented lanes (balance/nonce/code/storage) keyed by (accountOrdinal, key) at the row of each tx index. ChangesEqual(other, index) then compares two indexes row-by-row via ReadOnlySpan<T>.SequenceEqual — no per-account dict lookups, no merge-walks — which is what ValidateBlockAccessList runs once per tx. Wiring (BlockAccessListManager): - PrepareForProcessing builds the suggested index once and pairs a mutable generated index laid out identically. Also computes the suggested chargeable-storage-reads tally once for the fast-path surplus-reads gas check. - MergeAndReturnBal grows an optional Action<BlockAccessListAtIndex> callback; the parallel impl invokes it with the live slice between target.Merge() and pool-return, the sequential impl with the slice held on the worldstate. The manager-side hook (RegisterGeneratedSlice) pushes the slice's rows into the mutable index and rolls the generated-side read counter forward. - ValidateBlockAccessList tries the index first: a single ChangesEqual call plus the precomputed surplus-reads check. On mismatch (or before the index is populated) it falls through to the existing streaming walk that produces precise error diagnostics. Adaptations from master's version: - Build / Count / Fill bind to ReadOnlyBlockAccessList + ReadOnlyAccountChanges, and read change arrays directly (no ChangeSet.BlockAccessChanges indirection). - Add(BlockAccessList) replaced with Add(BlockAccessListAtIndex slice): our generated rows arrive as per-tx slices, one push per tx, each contributing balance/nonce/code/storage at its own .Index. - StorageLane values are EvmWord (matches StorageChange.Value's wire type on this branch). Tests: 12 new BlockAccessListValidationIndexTests covering exact match, order-insensitive match (by address, by slot), large-row sort path, overflow isolation, and mismatch detection on each lane. Suite passes along with all existing BAL tests (244 total). * Deduplicate BAL/EIP-8037 test bodies - Collapse the two Eip8037BlockGasInclusionCheck boundary tests and the two CalculateBlockRegularGas floor tests into single [TestCase]s. - Collapse the three Eip7702 pre-validation authorization rejection tests (max_nonce zero/max, high_s) into one [TestCaseSource]; the post-validation existing_code variant stays separate because its assertion shape differs. - Fold the three error variants of debug_getRawBlockAccessList into one [TestCaseSource] keyed on a setup callback and expected error code. - Extract a BuildCreateFactory helper for the CREATE/CREATE2 factory pattern repeated across three Eip8037Regression selfdestruct tests. - Extract a SetupPrecompileBalScenario helper for the shared init ritual used by the four *_under_PrecompileCachedCodeInfoRepository tests. - Drop the spurious [Test] on the CodeInfoRepository_getcachedcodeinfo parameterized test (combined with two [TestCase]s, NUnit would also invoke it with no arguments). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop redundant comments from dedup commit Helper signatures and TestCase names already convey what the removed comments restated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * opts * feedback * refactor(bal): split BlockAccessListManager into partial files by concern The class had grown to 919 lines across half a dozen concerns; primary constructor parameters were buried under fields and four levels of nested types. Split into five partial files, one concern each, with a docstring on each partial declaration so a reader scanning the directory sees the boundary before opening the file. BlockAccessListManager.cs (215) Class skeleton + primary constructor, fields, ParallelExecutionException, public properties, lifecycle (PrepareForProcessing / Setup / Reset / SpendGas / SetBlockExecutionContext / CheckInitialized), and the per-tx hot path (GetTxProcessor / NextTransaction / Rollback / ReturnTxProcessor). BlockAccessListManager.Validation.cs (258) IncrementalValidation, the two CheckPerTxInclusion overloads, ValidateBlockAccessList (fast-path + streaming slow-path), the RegisterGeneratedSlice merge-hook, and IsSystemContract. BlockAccessListManager.PrestateAndStateChanges.cs (174) LoadPreStateToSuggestedBlockAccessList (populates the suggested BAL with start-of-block values), the static ApplyStateChanges (writes BAL deltas onto stateProvider), and SetBlockAccessList (finalises the produced block with GeneratedBlockAccessList + RLP + hash). BlockAccessListManager.SystemContracts.cs (74) Bridge methods that route through pre-/post-execution slots of the tx processor pool: StoreBeaconRoot, ApplyBlockhashStateChanges, ApplyAuRaPreprocessingChanges, ProcessWithdrawals, ProcessExecutionRequests. BlockAccessListManager.TxProcessorPool.cs (304) Nested ITxProcessorWithWorldStateManager interface plus its ParallelTxProcessorWithWorldStateManager and SequentialTxProcessorWithWorldStateManager implementations and the TxProcessorWithWorldState bundle. No behavioural change — just a move. No public API changes, no behaviour changes. Verified via full solution build, dotnet format clean, and the BAL-area test sweep: 50 Core BAL + 62 Consensus + 42 Blockchain + 17 BalRecorder + 72/73 State + 239 EVM tests all pass. * Address Claude review on PR #11573 + skip per-tx BAL validation during sequential block building - High (BlockAccessListManager.PrestateAndStateChanges.cs): _lastLoadedBal was set before the try/catch, so a partial-load failure poisoned the dedup key. A retry against the same hash silently skipped the load and workers saw partial / default prestate. Moved the assignment to after the try block; on success only. - Medium (ReadOnlyAccountChanges.cs): replace LINQ Enumerable.SequenceEqual on four arrays with MemoryExtensions.SequenceEqual over ReadOnlySpan<T> (zero-alloc, no iterator, BCL non-LINQ — see coding-style.md). Drop the now-unused `using System.Linq;`. - Medium (BlockAccessListManager.cs): only the parallel path feeds the generated validation index (RegisterGeneratedSlice is wired into the parallel MergeAndReturnBal callback; the sequential NextTransaction merges through WorldState.MergeGeneratingBal without the hook). So _hasGeneratedValidationIndexUpdates never flips in sequential mode and the fast path never triggers. Gate the index build on ParallelExecutionEnabled to skip the O(n) BAL walk + lane sort entirely in sequential mode. - High (ReadOnlyAccountChanges.cs): document why slot 0 cannot be starved by ThreadPool pressure. The structural enforcement is in ParallelUnbalancedWork.For: the calling thread is one of the workers (it runs InitProcessor.Execute() inline at InitProcessor.For:305 rather than queueing it) and indices are drawn from an atomic counter starting at fromInclusive, so the very first GetNext() across all workers returns 0 — the calling thread can't be pre-empted out of existing, so slot 0 always begins executing. Plus: ParallelBlockValidationTransactionsExecutor.ProcessTransactionsSequential now skips the per-tx ValidateBlockAccessList calls when building a block (ProcessingOptions.ProducingBlock). There is no suggested BAL to compare against during building — ValidateBlockAccessList would early-return on `BlockAccessList is null` anyway, but skipping the call removes the NextTransaction → Validate dance and makes the intent explicit on this hot path. * restore some balstore changes, remove workflows * test(bal): restore inlined helpers in BlockValidatorTests Three helpers got deleted during the BAL split refactor — only one of them was actually tied to the unified BlockAccessList type. The other two were deleted along with it and their bodies inlined into every test. Restored: - AmsterdamSut(ITxValidator? tx): factory for BlockValidator wired to Amsterdam. Used 5x. - AssertValidation(expected, actual, error, failPrefix): canonical `isValid + error.StartsWith` assertion pattern. Used 5x. - WithBal(this BlockBuilder, ReadOnlyBlockAccessList bal): file-scoped extension that encodes the BAL, computes the hash, and chains the three .With… calls. Retyped from `BlockAccessList` to `ReadOnlyBlockAccessList` for our split refactor; used 4x. Net delta in the file vs master shrinks from +85/-112 to +29/-71 — the remainder is the necessary type renames (BlockAccessList → ReadOnly…, AccountChanges → ReadOnly…), one rewritten test that has to build a GeneratedBlockAccessList via BlockAccessListAtIndex (our split-refactor generated side has no AddStorageChange on the immutable read-only type), and the deletion of `..._fresh_item_count..._reused` (it relied on `AddAccountRead` mutation which our types don't expose, and the cache- invalidation concern doesn't apply because GeneratedBlockAccessList.ItemCount computes fresh on every get). 49 BlockValidatorTests pass, lint clean. * test(bal): restore inlined helpers in BlockProcessorTests Re-introduces PrepareSetup, BuildGasResults, and ResultsForCount, and collapses the two PrepareForProcessing_keeps_parallel_bal_execution_* tests back into a single [TestCase(1)] [TestCase(2)] parametrized form, matching the structure on master. The deletion of the helpers and the parametrized test was unrelated to the BAL split refactor and only added noise to the diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(bal): replace prestate gating with per-worker parent reader Adopts master's parallel-execution architecture (PR #11436) for parent-state reads while keeping our split BAL-type design: * Parallel workers now rent an IReadOnlyTxProcessingScope from a pooled IReadOnlyTxProcessingEnvFactory, scoped against the parent state root captured at PrepareForProcessing. BlockAccessListBasedWorldState falls through to that snapshot reader for any (address, slot) the suggested BAL doesn't carry at the current block-access index. * BAL completeness is preserved: reads for an account that isn't declared in the suggested BAL at all throw InvalidBlockLevelAccessListException with the same message format the sequential validator produces from ValidateBlockAccessList. * Parallel execution now requires stateProvider.IsInScope so a parent state root is always capturable; sequential path takes over otherwise. Drops prestate-loading machinery in favour of the parent reader: * LoadPreStateToSuggestedBlockAccessList + per-account TaskCompletionSource gate (EnablePrestateGate / SignalPrestateLoaded / WaitForPrestate) are gone — workers no longer block on a per-account gate before reading prestate-dependent state. * ReadOnlyAccountChanges drops LoadPreStateBalance/Nonce/Code/Storage, GetSlotsForPreStateLoad, RecordWasChanged, ExistedBeforeBlock, and EmptyBeforeBlock; ReadOnlySlotChanges drops LoadPreStateChange. * Adds HasStateChanges, IsStorageRead, and TryGetLast{Balance,Nonce,Code} ChangeBefore convenience APIs the new world-state read paths need. * IndexedChanges keeps its prestate-slot storage and the wire-level PrestateIndex sentinel guard in IndexedChangeDecoder — both are dormant at runtime now but still defend against a malicious peer. The slot-0 loader iteration in BlockProcessor.ParallelBlockValidationTransactionsExecutor disappears (only ApplyStateChanges remains in iteration 0); slot 1 keeps the pre-execution StoreBeaconRoot / ApplyBlockhashStateChanges step and continues to synchronize with IncrementalValidation via preExecutionDoneTcs. Test updates: * BlockAccessListBasedWorldStateTests + the relevant TracedAccessWorldState test now wire up a parent reader (the inner state itself, scoped against genesis) and stop populating BAL prestate sentinels — covered values live in the parent state instead. * Removes AccountChangesPrestateTests and ReadOnlyAccountChangesTests entirely (every test exercised a deleted API). * Restores the parametrized PrepareForProcessing_keeps_parallel_bal_execution_for_validated_eip8037_blocks test and adds back the PrepareForProcessing_disables_parallel_bal_execution_when_state_provider_is_not_scoped test now that the IsInScope gate is in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(bal): restore tx-scheduling, parent-reader, and ApplyStateChanges tests Tests deleted earlier as collateral damage from the BAL split refactor that didn't carry the master-only features they exercised: * Parallel_validation_execution_order_keeps_canonical_lead_and_sorts_tail_by_gas_limit * Parallel_validation_execution_order_uses_stable_estimated_work_tie_breakers * Parallel_validation_cancel_incomplete_gas_results_preserves_completed_slots * Parallel_validation_uses_canonical_receipt_and_bal_indexes_with_scheduled_work_order * Parallel_validation_parent_reader_scope_is_per_worker_and_disposed_on_return * Parallel_validation_parent_reader_uses_parent_root_captured_before_pre_block_changes * ValidateBlockAccessList_matches_accounts_by_address_when_insertion_order_differs * ApplyStateChanges_uses_parent_state_without_prestate_sentinels (renamed to ApplyStateChanges_replays_balance_nonce_and_storage_onto_parent_state) * ApplyStateChanges_creates_missing_account_from_balance_change The tx-scheduling, parent-reader, and ApplyStateChanges tests come back verbatim against the now-available APIs. The two formerly-mutation-driven tests (ValidateBlockAccessList + ApplyStateChanges) are ported to the split BAL types: the suggested side is built immutably via the Build.A.BlockAccessList / Build.An.AccountChanges builders, and any generated-side mutation goes through BlockAccessListAtIndex.AddBalanceChange followed by GeneratedBlockAccessList.Merge — preserving the original test intents (insertion-order tolerance, parent-state replay, missing-account materialisation) under the new architecture. Re-introduces TrackingReadOnlyTxProcessingEnvFactory, BalIndexRecordingTransactionProcessorAdapter, CreateTxForExecutionOrder, CreateAuthorizationList, CreateAccessList, and the factory-overload ctor on ParallelTestBlockAccessListManager that the scheduling test needs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(bal): hide computed HasStateChanges from JSON serialization ReadOnlyAccountChanges.HasStateChanges is a computed read-side helper introduced for BlockAccessListBasedWorldState.GetAccountChanges; it must not appear in the eth_getBlockAccessList* wire payload alongside the real BAL fields, otherwise the EthRpcModule serialization tests diverge from the spec-shaped output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(bal): minimize BlockProcessorTests diff against master Rebases BlockProcessorTests onto master's structure verbatim, applying only the changes the BAL split refactor strictly requires: * Test ordering matches master exactly. * Test names match master exactly (no more "_replays_*" rename). * Helper layout matches master. * All ~12 `new BlockAccessList()` literals → `new ReadOnlyBlockAccessList()`. * 5 IncrementalValidation call sites pass `Task.CompletedTask` (the extra pre-execution gate param our branch's signature carries). * `using Nethermind.Blockchain;` for IReadOnlyTxProcessor* interfaces. * ParallelTestBlockAccessListManager: BlockAccessList → GeneratedBlockAccessList type on the mock property, and the IncrementalValidation signature adds the same `Task preExecutionTask` parameter. Four tests are rebodied because their master forms mutate the unified BlockAccessList type (AddBalanceChange/AddAccountRead/AddStorageRead) which our split types don't expose on the suggested-side: * ApplyStateChanges_uses_parent_state_without_prestate_sentinels * ApplyStateChanges_creates_missing_account_from_balance_change * ValidateBlockAccessList_storage_read_budget_uses_ItemCost * ValidateBlockAccessList_matches_accounts_by_address_when_insertion_order_differs On our branch each builds the suggested side immutably via Build.A.BlockAccessList / Build.An.AccountChanges and (for the ValidateBlockAccessList tests) emits the generated side via BlockAccessListAtIndex + GeneratedBlockAccessList.Merge. The intents — apply replays balance/nonce/storage onto the parent state, BAL size check uses fresh ItemCost, validator matches by address despite insertion order — are preserved. The previously-renamed `ApplyStateChanges_replays_balance_nonce_and_storage_onto_parent_state` is renamed back to master's `ApplyStateChanges_uses_parent_state_without_prestate_sentinels`. The `AddAccountRead` helper is deleted (it relied on unified-BAL mutation and only existed to compose those four tests). Verified all 28 master test names are present in our file. Diff vs master shrinks from +361/-340 to +62/-42 — all remaining changes are the type and signature deltas above. 1476 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(bal): run BAL system contracts sequentially, drop preExecutionTask Reverts to master's parallel-execution shape: StoreBeaconRoot and ApplyBlockhashStateChanges run sequentially in BlockProcessor.ProcessBlock *before* ParallelBlockValidationTransactionsExecutor.ProcessTransactions, instead of as a dedicated iteration inside the parallel For. Removes the extra synchronization the overlapped layout required: * IBlockAccessListManager.IncrementalValidation drops Task preExecutionTask (NullBlockAccessListManager and BlockAccessListManager.Validation follow). The validator can merge balIndex=0 immediately on entry — the pre-execution writes are already in the slice by then. * ParallelBlockValidationTransactionsExecutor: - parallel loop range len+2 → len+1; iteration 1 (the pre-execution block + preExecutionDoneTcs SetResult/TrySetException) deleted. - scheduled tx index goes back to txExecutionOrder[i-1] (was i-2); balIndex remains txIndex+1. - state tuple drops preExecutionDoneTcs. - outer catch drops preExecutionDoneTcs.TrySetCanceled() and the catch(TaskCanceledException) widens to master's catch(OperationCanceledException ex) when (ex is TaskCanceledException || token.IsCancellationRequested). * BlockProcessor.cs: the `if (!_balManager.ParallelExecutionEnabled)` gate around the StoreBeaconRoot/ApplyBlockhashStateChanges/Commit triple is removed — both paths now invoke pre-execution sequentially. On the BAL path, _systemContractHandler is BlockAccessListSystemContractHandler, which routes through balManager → GetPreExecution() → BAL slice for balIndex=0; on the standard path, it routes through BeaconBlockRootHandler + BlockhashStore against stateProvider, same as master. * BlockProcessorTests.cs + Eip8037BlockGasIntegrationTests.cs: drop the extra Task.CompletedTask argument from every IncrementalValidation call (6 + 6 sites) and from the ParallelTestBlockAccessListManager mock signature. Trade-off: pre-execution no longer overlaps with parallel tx execution. For Amsterdam that's two system-contract calls per block — small enough that diff parity with master wins on maintainability. Easy to revert if benchmarks ever show pre-execution on the critical path. Test sweep: Blockchain (1476), State (782), EVM (3579), Merge.Plugin (1006) all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthr…
1 parent 0d11efa commit b1fa33d

7 files changed

Lines changed: 140 additions & 23 deletions

File tree

src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,61 @@ public void ValidateBlockAccessList_matches_accounts_by_address_when_insertion_o
597597
Assert.DoesNotThrow(() => balManager.ValidateBlockAccessList(block, 0));
598598
}
599599

600+
// Verify-only structural-equivalence check: covers the mismatch classes the column-index
601+
// and gas-budget checks don't catch (their inputs slip through to the structural walk in
602+
// SetBlockAccessList).
603+
[TestCaseSource(nameof(VerifyOnlyStructuralMismatchCases))]
604+
public void SetBlockAccessList_verify_only_rejects_structural_mismatch(
605+
ReadOnlyBlockAccessList suggested,
606+
Action<BlockAccessListAtIndex> populateGenerated)
607+
{
608+
IWorldState stateProvider = TestWorldStateFactory.CreateForTest();
609+
using IDisposable scope = stateProvider.BeginScope(IWorldState.PreGenesis);
610+
using BlockAccessListManager balManager = CreateAmsterdamBalManager(stateProvider);
611+
612+
Block block = Build.A.Block
613+
.WithNumber(1)
614+
.WithGasUsed(0)
615+
.WithBlockAccessList(suggested)
616+
.TestObject;
617+
618+
PrepareSetup(balManager, block, Amsterdam.Instance);
619+
620+
BlockAccessListAtIndex slice = new();
621+
populateGenerated(slice);
622+
balManager.GeneratedBlockAccessList.Merge(slice);
623+
624+
Assert.Throws<BlockAccessListBasedWorldState.InvalidBlockLevelAccessListException>(
625+
() => balManager.SetBlockAccessList(block));
626+
}
627+
628+
private static IEnumerable<TestCaseData> VerifyOnlyStructuralMismatchCases()
629+
{
630+
// storage_reads content mismatch (count matches, values differ): the one mismatch class
631+
// nothing else catches — column-index only tracks read counts via the gas-budget check.
632+
yield return new TestCaseData(
633+
Build.A.BlockAccessList
634+
.WithAccountChanges(Build.An.AccountChanges
635+
.WithAddress(TestItem.AddressA)
636+
.WithStorageReads((UInt256)5)
637+
.TestObject)
638+
.TestObject,
639+
(Action<BlockAccessListAtIndex>)(s => s.AddStorageRead(TestItem.AddressA, (UInt256)7)))
640+
.SetName("storage_reads content mismatch (same count, different value)");
641+
642+
// Per-account presence mismatch with matching count: suggested has AddressA, generated
643+
// touched AddressB — the structural walk catches via `generated.GetAccountChanges` miss.
644+
yield return new TestCaseData(
645+
Build.A.BlockAccessList
646+
.WithAccountChanges(Build.An.AccountChanges
647+
.WithAddress(TestItem.AddressA)
648+
.WithBalanceChanges(new BalanceChange(0, 1))
649+
.TestObject)
650+
.TestObject,
651+
(Action<BlockAccessListAtIndex>)(s => s.AddBalanceChange(TestItem.AddressB, before: 0, after: 1)))
652+
.SetName("account presence mismatch (same count, different address)");
653+
}
654+
600655
[TestCase(1)]
601656
[TestCase(2)]
602657
public void PrepareForProcessing_keeps_parallel_bal_execution_for_validated_eip8037_blocks(int txCount) =>

src/Nethermind/Nethermind.Consensus/Processing/BlockAccessListManager.StateChanges.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,23 @@ public void SetBlockAccessList(Block block)
9393
if (block.IsGenesis)
9494
{
9595
block.Header.BlockAccessListHash = Keccak.OfAnEmptySequenceRlp;
96+
return;
9697
}
97-
else
98-
{
99-
CheckInitialized();
10098

101-
MergeAndReturnBal(uint.MaxValue);
102-
block.GeneratedBlockAccessList = GeneratedBlockAccessList;
103-
// EncodeToBytes precomputes per-account RLP sub-lengths once via a rented ArrayPool
104-
// buffer and reuses them across the encode pass — avoids re-walking each account's
105-
// sub-collections (slot changes, storage reads, balance/nonce/code) twice.
106-
block.EncodedBlockAccessList = BlockAccessListDecoder.EncodeToBytes(GeneratedBlockAccessList);
107-
block.Header.BlockAccessListHash = new(ValueKeccak.Compute(block.EncodedBlockAccessList).Bytes);
99+
CheckInitialized();
100+
MergeAndReturnBal(uint.MaxValue);
101+
block.GeneratedBlockAccessList = GeneratedBlockAccessList;
102+
103+
if (ParallelExecutionEnabled)
104+
{
105+
// IncrementalValidation only covered indices 0..txCount; the post-execution row
106+
// (txCount + 1) was just merged but not yet compared.
107+
ValidateBlockAccessList(block, (uint)(block.Transactions.Length + 1));
108+
ValidateStructuralEquivalence(block, GeneratedBlockAccessList);
109+
return;
108110
}
111+
112+
block.EncodedBlockAccessList = BlockAccessListDecoder.EncodeToBytes(GeneratedBlockAccessList);
113+
block.Header.BlockAccessListHash = new(ValueKeccak.Compute(block.EncodedBlockAccessList).Bytes);
109114
}
110115
}

src/Nethermind/Nethermind.Consensus/Processing/BlockAccessListManager.Validation.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Nethermind.Core.BlockAccessLists;
99
using Nethermind.Core.Specs;
1010
using Nethermind.Evm.GasPolicy;
11+
using Nethermind.Int256;
1112
using static Nethermind.Consensus.Processing.BlockProcessor;
1213
using static Nethermind.State.BlockAccessListBasedWorldState;
1314

@@ -286,4 +287,55 @@ private static bool IsSystemUserReadAt0(AccountChangesAtIndex ac, uint index)
286287
private static bool IsSystemContract(Address address)
287288
=> address == Eip7002Constants.WithdrawalRequestPredeployAddress
288289
|| address == Eip7251Constants.ConsolidationRequestPredeployAddress;
290+
291+
/// <summary>
292+
/// Closes the gap between the column-index per-row validation and what the end-of-block
293+
/// canonical-bytes hash compare used to catch: namely, account-set presence and the exact
294+
/// set of storage_reads per account. Throws <see cref="InvalidBlockLevelAccessListException"/>
295+
/// on any structural difference between the suggested and generated BALs.
296+
/// </summary>
297+
/// <remarks>
298+
/// Does not compare <c>StorageChanges</c> (write entries): those are fully covered by the
299+
/// incremental <see cref="ValidateBlockAccessList"/> calls at indices 0..txCount+1 via
300+
/// <c>ChangesAtIndexEqual</c>, which compares every balance/nonce/code/storage-write lane at
301+
/// each tx index in lockstep.
302+
/// </remarks>
303+
private static void ValidateStructuralEquivalence(Block block, GeneratedBlockAccessList generated)
304+
{
305+
ReadOnlyBlockAccessList suggested = block.BlockAccessList!;
306+
ReadOnlySpan<ReadOnlyAccountChanges> suggestedAccounts = suggested.AccountChangesAsSpan;
307+
308+
if (suggestedAccounts.Length != generated.AccountChanges.Count)
309+
{
310+
throw new InvalidBlockLevelAccessListException(block.Header,
311+
$"Account-set size mismatch: suggested={suggestedAccounts.Length}, generated={generated.AccountChanges.Count}.");
312+
}
313+
314+
for (int a = 0; a < suggestedAccounts.Length; a++)
315+
{
316+
ReadOnlyAccountChanges sug = suggestedAccounts[a];
317+
GeneratedAccountChanges gen = generated.GetAccountChanges(sug.Address)
318+
?? throw new InvalidBlockLevelAccessListException(block.Header,
319+
$"Suggested BAL declares account {sug.Address} which execution did not touch.");
320+
321+
ReadOnlySpan<UInt256> sugReads = sug.StorageReads;
322+
if (sugReads.Length != gen.StorageReads.Count)
323+
{
324+
throw new InvalidBlockLevelAccessListException(block.Header,
325+
$"storage_reads count mismatch for {sug.Address}: suggested={sugReads.Length}, generated={gen.StorageReads.Count}.");
326+
}
327+
328+
// Both sides keep storage_reads sorted (decoder-validated; SortedSet on the generated side).
329+
int i = 0;
330+
foreach (UInt256 genRead in gen.StorageReads)
331+
{
332+
if (!sugReads[i].Equals(genRead))
333+
{
334+
throw new InvalidBlockLevelAccessListException(block.Header,
335+
$"storage_reads mismatch for {sug.Address} at offset {i}.");
336+
}
337+
i++;
338+
}
339+
}
340+
}
289341
}

src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private void ValidateProcessedBlock(Block suggestedBlock, ProcessingOptions opti
101101
suggestedBlock.AccountChanges = block.AccountChanges;
102102
suggestedBlock.ExecutionRequests = block.ExecutionRequests;
103103
suggestedBlock.GeneratedBlockAccessList = block.GeneratedBlockAccessList;
104-
suggestedBlock.EncodedBlockAccessList = block.EncodedBlockAccessList;
104+
suggestedBlock.EncodedBlockAccessList = block.EncodedBlockAccessList ?? suggestedBlock.EncodedBlockAccessList;
105105
}
106106

107107
protected bool ShouldComputeStateRoot(BlockHeader header) =>
@@ -243,7 +243,9 @@ protected virtual Block PrepareBlockForProcessing(Block suggestedBlock)
243243
RequestsHash = bh.RequestsHash,
244244
IsPostMerge = bh.IsPostMerge,
245245
ParentBeaconBlockRoot = bh.ParentBeaconBlockRoot,
246-
SlotNumber = bh.SlotNumber
246+
SlotNumber = bh.SlotNumber,
247+
// Carried for the verify-only fast path which doesn't recompute it.
248+
BlockAccessListHash = bh.BlockAccessListHash
247249
};
248250

249251
if (!ShouldComputeStateRoot(bh))

src/Nethermind/Nethermind.Core/BlockAccessLists/ReadOnlyBlockAccessList.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@ namespace Nethermind.Core.BlockAccessLists;
1919
public class ReadOnlyBlockAccessList : IEquatable<ReadOnlyBlockAccessList>
2020
{
2121
private readonly Dictionary<Address, ReadOnlyAccountChanges> _accountChanges;
22+
private readonly ReadOnlyAccountChanges[] _orderedAccounts;
2223

2324
[JsonIgnore]
2425
public int ItemCount { get; }
2526

2627
public EnumerableWithCount<ReadOnlyAccountChanges> AccountChanges
2728
=> new(_accountChanges.Values, _accountChanges.Count);
2829

30+
/// <summary>
31+
/// Span over the address-sorted accounts (same data as <see cref="AccountChanges"/>, but
32+
/// skips the dictionary's enumerator for hot walks).
33+
/// </summary>
34+
[JsonIgnore]
35+
public ReadOnlySpan<ReadOnlyAccountChanges> AccountChangesAsSpan => _orderedAccounts;
36+
2937
public bool HasAccount(Address address) => _accountChanges.ContainsKey(address);
3038

3139
public ReadOnlyAccountChanges? GetAccountChanges(Address address)
@@ -41,6 +49,7 @@ public ReadOnlyBlockAccessList() : this([], 0) { }
4149
/// </summary>
4250
public ReadOnlyBlockAccessList(ReadOnlyAccountChanges[] orderedAccounts, int itemCount)
4351
{
52+
_orderedAccounts = orderedAccounts;
4453
_accountChanges = new Dictionary<Address, ReadOnlyAccountChanges>(orderedAccounts.Length);
4554
foreach (ReadOnlyAccountChanges a in orderedAccounts)
4655
{

src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,6 @@ public static EvmExceptionType InstructionExtCodeHash<TGasPolicy, TTracingInst>(
618618
{
619619
return stack.PushZero<TTracingInst>();
620620
}
621-
// Otherwise, push the account's code hash.
622621
ValueHash256 hash = state.GetCodeHash(address);
623622
return stack.Push32Bytes<TTracingInst>(in hash);
624623
// Jump forward to be unpredicted by the branch predictor.

src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -291,16 +291,11 @@ public virtual async Task NewPayloadV5_rejects_invalid_BAL_after_processing(stri
291291
using (Assert.EnterMultipleScope())
292292
{
293293
Assert.That(successResponse, Is.Not.Null);
294-
Assert.That(response, Is.EqualTo(chain.JsonSerializer.Serialize(new JsonRpcSuccessResponse
295-
{
296-
Id = successResponse.Id,
297-
Result = new PayloadStatusV1
298-
{
299-
LatestValidHash = Keccak.Zero,
300-
Status = PayloadStatus.Invalid,
301-
ValidationError = $"InvalidBlockLevelAccessListHash: Expected {expectedBalHash}, got {invalidBalHash}"
302-
}
303-
})));
294+
Assert.That(response, Does.Contain("\"status\":\"INVALID\""));
295+
Assert.That(response, Does.Contain($"\"latestValidHash\":\"{Keccak.Zero.ToString(true)}\""));
296+
Assert.That(response,
297+
Does.Contain($"InvalidBlockLevelAccessListHash: Expected {expectedBalHash}, got {invalidBalHash}")
298+
.Or.Contain("InvalidBlockLevelAccessList: Account-set size mismatch"));
304299
}
305300
}
306301

0 commit comments

Comments
 (0)