feat(l1): bal-devnet-6 support (EIP-7928 BAL + EIP-8037 2D gas)#6574
Conversation
Lines of code reportTotal lines added: Detailed view |
Benchmark Results ComparisonNo significant difference was registered for any benchmark run. Detailed ResultsBenchmark Results: BubbleSort
Benchmark Results: ERC20Approval
Benchmark Results: ERC20Mint
Benchmark Results: ERC20Transfer
Benchmark Results: Factorial
Benchmark Results: FactorialRecursive
Benchmark Results: Fibonacci
Benchmark Results: FibonacciRecursive
Benchmark Results: ManyHashes
Benchmark Results: MstoreBench
Benchmark Results: Push
Benchmark Results: SstoreBench_no_opt
|
7d45d6a to
1744a7c
Compare
|
| EIP | Bucket | Count |
|---|---|---|
| EIP-7702 | set_code_txs |
24 |
| EIP-7702 | set_code_txs_2 |
15 |
| EIP-7702 | gas |
1 |
| EIP-8037 | state_gas_set_code |
17 |
| EIP-8037 | state_gas_pricing |
1 |
| EIP-8037 | state_gas_sstore |
1 |
| EIP-7928 | block_access_lists_eip7702 |
8 |
| EIP-7928 | block_access_lists |
1 |
| EIP-7778 | gas_accounting |
3 |
| EIP-7708 | transfer_logs |
1 |
| EIP-7976 | refunds |
1 |
| EIP-1344 | chainid (Amsterdam fork-transition fixture) |
1 |
| Total | 74 |
Re-enable once we either:
- (a) bump fixtures to a snobal-devnet-7 release that locks in the new
accounting; or - (b) revert the bal-devnet-7-prep subtraction for bal-devnet-6
compatibility.
Full test list (74)
EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/
delegation_clearingdelegation_clearing_and_setdelegation_clearing_failing_txdelegation_clearing_tx_toeoa_tx_after_set_codeext_code_on_chain_delegating_set_codeext_code_on_self_delegating_set_codeext_code_on_self_set_codeext_code_on_set_codemany_delegationsnonce_overflow_after_first_authorizationnonce_validityreset_codeself_code_on_set_codeself_sponsored_set_codeset_code_multiple_valid_authorization_tuples_same_signer_increasing_nonceset_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce_self_sponsoredset_code_to_logset_code_to_non_empty_storage_non_zero_nonceset_code_to_self_destructset_code_to_self_destructing_account_deployed_in_same_txset_code_to_sstoreset_code_to_sstore_then_sloadset_code_to_system_contract
EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/
call_pointer_to_created_from_create_after_oog_call_againcall_to_precompile_in_pointer_contextcontract_storage_to_pointer_with_storagedelegation_replacement_call_previous_contractdouble_authpointer_measurementspointer_normalpointer_reentrypointer_resets_an_empty_code_account_with_storagepointer_revertspointer_to_pointerpointer_to_precompilepointer_to_staticpointer_to_static_reentrystatic_to_pointer
EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/gas/
account_warming
EIP-8037 — for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/
auth_refund_block_gas_accountingauth_refund_bypasses_one_fifth_capauth_with_calldata_and_access_listauth_with_multiple_sstoresauthorization_exact_state_gas_boundaryauthorization_to_precompile_addressauthorization_with_sstoreduplicate_signer_authorizationsexisting_account_auth_header_gas_used_uses_worst_caseexisting_account_refundexisting_account_refund_enables_sstoreexisting_auth_with_reverted_execution_preserves_intrinsicmany_authorizations_state_gasmixed_auths_header_gas_used_uses_worst_casemixed_new_and_existing_authsmixed_valid_and_invalid_authsmulti_tx_block_auth_refund_and_sstore
EIP-8037 — state_gas_pricing/
auth_state_gas_scales_with_cpsb
EIP-8037 — state_gas_sstore/
sstore_state_gas_all_tx_types
EIP-7928 — for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/
bal_7702_delegation_clearbal_7702_delegation_createbal_7702_delegation_updatebal_7702_double_auth_resetbal_7702_double_auth_swapbal_7702_null_address_delegation_no_code_changebal_selfdestruct_to_7702_delegationbal_withdrawal_to_7702_delegation
EIP-7928 — block_access_lists/
bal_all_transaction_types
EIP-7778 — for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/
multiple_refund_types_in_one_txsimple_gas_accountingvarying_calldata_costs
EIP-7708 — for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/
transfer_with_all_tx_types
EIP-7976 — for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/refunds/
gas_refunds_from_data_floor
EIP-1344 — for_amsterdam/istanbul/eip1344_chainid/chainid/
chainid(Amsterdam fork-transition fixture)
- Preamble no longer says "on the current branch" — the doc lives in-tree and applies wherever it's checked out. - Comment heading drops "on this branch" likewise. - Bal-devnet-6 section drops the "Tracking via PR #6574" line, which pointed back at the PR introducing this doc and stops being meaningful the moment that PR merges. The resolution-path bullets already cover what's needed to re-enable.
🤖 Kimi Code Review
This is a substantial PR implementing the Amsterdam fork (EIP-7928 Block Access Lists, EIP-8037 Multidimensional Gas, EIP-7976/7981 Floor Cost adjustments) with extensive testing. Below is the review feedback organized by category. Critical Consensus & Safety1. Fork Choice Reorg Depth ValidationFile: The implementation of execution-apis PR #786 correctly distinguishes between:
Issue: The error variant // Line 118-122: Spec violation
return Err(InvalidForkChoice::TooDeepReorg {
reorg_depth,
limit: latest.saturating_sub(stored_finalized), // This is the spec limit
});
// Line 127-131: Implementation limit
return Err(InvalidForkChoice::TooDeepReorg {
reorg_depth,
limit: REORG_DEPTH_LIMIT, // This is the implementation cap
});2. BAL Index Type Widening (u16 → u32)File: The change from
The VM & Gas Accounting3. EIP-8037 Clamp-and-Spill Refund LogicFile: The refund mechanism splitting credits between // Line 691-699: This math must exactly match EELS fork.py
let frame_outstanding_delta = self
.state_gas_spill_outstanding
.saturating_sub(self.current_call_frame.state_gas_spill_outstanding_snapshot);
let applied_to_spill = clamped.min(frame_outstanding_delta);Risk: If 4. Intrinsic Gas ParityFile: The standalone Note: The Amsterdam CREATE intrinsic split (REGULAR_GAS_CREATE + state portion) is correctly implemented in both paths. 5. System Call Gas LimitFile: System calls now use the actual block_gas_limit: block_header.gas_limit, // Was i64::MAX
is_system_call: true,This prevents OOG on state-gas-heavy system calls (EIP-2935, EIP-4788) when Block Building & Validation6. Builder/Validator ParityFile: This new test suite is critical infrastructure. It verifies that blocks built via Gap: The 7. 2D Gas Inclusion CheckFile: The EIP-8037 per-transaction 2D check is correctly placed before BAL recording: // EIP-8037 ... per-tx 2D inclusion check against running block totals. Run BEFORE we touch...
if context.is_amsterdam
&& let Err(e) = check_2d_gas_allowance(...)
{
return Err(e.into());
}This prevents rejected transactions from polluting the BAL. ✅ Correct. Testing & Tooling8. Fixture Version ManagementFile: The separation of Action Required: Add a TODO comment in the Makefile with a link to the zkevm fixture regeneration tracking issue so this narrowing can be removed later. 9. Known Issues DocumentationFile: Excellent documentation of the 74 intentionally skipped Amsterdam tests due to the bal-devnet-6 vs devnet-7-prep semantic mismatch. The categorization by EIP (7702, 8037, 7928, etc.) is helpful for future triage. Suggestion: Add the specific commit hash or date when these can be re-enabled (e.g., "Re-enable after fixture bump to snobal-devnet-7"). Minor Issues10. Error Code DocumentationFile: The comment lists error codes 11. Prestate Tracer Storage FilteringFile: The Optimization: The SummaryApprovals:
Recommendations:
The PR is well-structured for a complex fork implementation. The builder/validator parity tests are particularly valuable for preventing consensus failures in production block building. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewCode Review: PR 6574 — bal-devnet-6 Support (EIP-7928 BAL + EIP-8037 2D Gas)This is a large, well-motivated PR. The architecture and documentation are clear, and the overall approach is sound. There are a few correctness and robustness issues worth addressing before merge. Overview
High Severity1. Panic on
// Current (panics on None):
let head_block = context.storage
.get_block_header_by_hash(fork_choice_state.head_block_hash)?;
// Should be:
let head_block = context.storage
.get_block_header_by_hash(fork_choice_state.head_block_hash)?
.ok_or_else(|| RpcErr::Internal("head block header not found".into()))?;2. The implementation iterates 3. When a tx selfdestructs multiple accounts, each iteration computes Medium Severity4. Spurious When 5. L2 payload builder does not restore The L2 builder snapshots 6. If any tx in the block fails during replay, the function propagates the error and stops, leaving subsequent txs untraced. Geth continues tracing through failed txs (the prestate is still valid). The fix is to catch the error per-tx, record an empty or partial prestate, and continue iterating. Low Severity / Maintainability7. Three copies of EIP-7976/7981 floor gas logic — 8. 9. Missing test: EIP-4844 blob tx through 10. Deeply nested mixed-success/failure call trees for Positive Notes
Automated review by Claude (Anthropic) · sonnet · custom prompt |
🤖 Codex Code Review
Open question: crates/vm/backends/levm/mod.rs says system calls “do not use intrinsic gas”, but Static review only; I didn’t run the suite. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR implements EIP-7928 (Block Access List) and EIP-8037 (2D gas) for the bal-devnet-6 devnet, rolls up devnet-3→6 spec changes, adds
Confidence Score: 3/5The 2D gas accounting changes are intricate; the interaction between set_delegation's auth refund and state_gas_reservoir_initial needs verification before merging. When set_delegation credits the reservoir for existing EIP-7702 authorities, it reduces both state_gas_used and intrinsic_state_gas_charged by the refund but leaves state_gas_reservoir_initial at its pre-refund value. The refund_sender formula subtracts the stale reservoir_initial, causing the regular dimension to be overcounted by one refund-unit per existing authority, inflating block_regular_gas_used and tightening the 2D budget for subsequent transactions. The fork-choice and payload-builder changes are clean and safe. crates/vm/levm/src/utils.rs (set_delegation refund + reservoir_initial interaction) and crates/vm/levm/src/hooks/default_hook.rs (refund_sender regular-gas formula)
|
| Filename | Overview |
|---|---|
| crates/vm/levm/src/vm.rs | Central EIP-8037 state: adds 12 new dimensional-gas fields, credit_state_gas_refund, finalize_execution top-level halt/revert wipe, and regular_gas formula rewrite. Complex but well-commented; the state_gas_reservoir_initial / set_delegation interaction is the main risk area. |
| crates/vm/levm/src/utils.rs | set_delegation auth-refund logic and add_intrinsic_gas snapshot capture; state_gas_reservoir_initial is set before set_delegation runs, creating a dimensional overcounting issue for type-4 txs with existing authorities. |
| crates/vm/levm/src/hooks/default_hook.rs | refund_sender formula rewritten for 2D: raw_consumed − intrinsic_state − reservoir_initial − spill + reclassified; apply_same_tx_selfdestruct_state_refund added; validate_min_gas_limit updated for EIP-7976/7981 floor tokens. |
| crates/vm/levm/src/opcode_handlers/system.rs | CALL/CREATE/SELFDESTRUCT updated to use vm.state_gas_new_account (dynamic cpsb); handle_return_call/handle_return_create gain per-frame 2D gas snapshots and asymmetric REVERT/ExceptionalHalt handling. |
| crates/blockchain/fork_choice.rs | Implements execution-apis PR 786 no-reorg restriction, TooDeepReorg gated on finalized prefix, and REORG_DEPTH_LIMIT=128 as implementation cap. |
| crates/vm/backends/levm/mod.rs | check_2d_gas_allowance added for sequential and parallel paths; u16→u32 for BlockAccessIndex; SYSTEM_ADDRESS excluded from BAL entries; shadow recorder for per-tx access validation. |
| crates/blockchain/payload.rs | Transaction processing extracted into apply_tx_to_payload; check_2d_gas_allowance run before BAL recording; block_regular_gas_used / block_state_gas_used tracked per-tx. |
| crates/vm/levm/src/call_frame.rs | 7 new per-frame EIP-8037 snapshot fields added for clamp-and-spill state restoration; all initialized to 0 in new(). |
| crates/vm/levm/src/gas_cost.rs | COST_PER_STATE_BYTE promoted to cost_per_state_byte() function (pinned at 1174); EIP-7976/7981 helpers floor_tokens_in_access_list and total_cost_floor_per_token added. |
| crates/networking/rpc/engine/fork_choice.rs | NewHeadAlreadyCanonical now calls set_synced and returns head header for payload building; TooDeepReorg returns -38006. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[VM.execute] --> B[prepare_execution]
B --> B1[add_intrinsic_gas
sets intrinsic_state_gas_charged
sets state_gas_reservoir_initial]
B1 --> B2[validate_type_4_tx - set_delegation
adds refund to reservoir
subtracts refund from state_gas_used
NOT updating state_gas_reservoir_initial]
B2 --> C[snapshot reservoir_at_top_message_entry]
C --> D{is_create?}
D -- yes --> E[handle_create_transaction]
D -- no --> F[run_execution]
E --> G[finalize_execution]
F --> G
G --> G1{Amsterdam and not success?}
G1 -- REVERT --> G2[refill reservoir with execution_portion]
G1 -- ExceptionalHalt --> G3[restore reservoir to entry
reclassify gross_spill to regular]
G1 -- success --> G4[apply_same_tx_selfdestruct_refund]
G2 --> H[refund_sender]
G3 --> H
G4 --> H
H --> H1[regular_gas = raw_consumed
- intrinsic_state_charged
- reservoir_initial stale after auth-refund
- spill + reclassified]
H1 --> I[report.gas_used = max_regular + state_gas]
I --> J[block_regular_gas_used += tx_regular_gas
block_state_gas_used += tx_state_gas]
Comments Outside Diff (1)
-
crates/vm/levm/src/utils.rs, line 339-367 (link)PR description contradicts final code for
set_delegationThe PR's "Notes for reviewers" states that commit
8045f5337("EIP-7702 set_delegation only credits state_gas_reservoir") reverts thestate_gas_used/intrinsic_state_gas_chargedsubtractions and that "re-applying the subtraction is the bal-devnet-7 follow-up." However, commit03e58d9fflater in this same PR re-applies both subtractions. The final HEAD contains the subtractions, which directly contradicts the description reviewers are given. The description's "Notes for reviewers" section should be updated to reflect the actual final state.Prompt To Fix With AI
This is a comment left during a code review. Path: crates/vm/levm/src/utils.rs Line: 339-367 Comment: **PR description contradicts final code for `set_delegation`** The PR's "Notes for reviewers" states that commit `8045f5337` ("EIP-7702 set_delegation only credits state_gas_reservoir") reverts the `state_gas_used`/`intrinsic_state_gas_charged` subtractions and that "re-applying the subtraction is the bal-devnet-7 follow-up." However, commit `03e58d9ff` later in this same PR re-applies both subtractions. The final HEAD contains the subtractions, which directly contradicts the description reviewers are given. The description's "Notes for reviewers" section should be updated to reflect the actual final state. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
crates/vm/levm/src/utils.rs:354-361
**`state_gas_reservoir_initial` not updated alongside reservoir on auth refund**
`set_delegation` adds `refund` to `state_gas_reservoir` but leaves `state_gas_reservoir_initial` (captured moments earlier in `add_intrinsic_gas`) unchanged. The `refund_sender` formula is `regular_gas = raw_consumed − intrinsic_state_charged − reservoir_initial − spill + reclassified`. Because `intrinsic_state_charged` is reduced by `refund` but `reservoir_initial` is not, the result overcounts the regular gas dimension by `refund` (= `STATE_BYTES_PER_NEW_ACCOUNT × cpsb ≈ 131,488`) per existing authority. This flows into `block_regular_gas_used` via `apply_plain_transaction`, so a block with N existing-authority type-4 txs reports `N × refund` extra regular-dimension gas, tightening the 2D budget for subsequent transactions.
### Issue 2 of 2
crates/vm/levm/src/utils.rs:339-367
**PR description contradicts final code for `set_delegation`**
The PR's "Notes for reviewers" states that commit `8045f5337` ("EIP-7702 set_delegation only credits state_gas_reservoir") reverts the `state_gas_used`/`intrinsic_state_gas_charged` subtractions and that "re-applying the subtraction is the bal-devnet-7 follow-up." However, commit `03e58d9ff` later in this same PR re-applies both subtractions. The final HEAD contains the subtractions, which directly contradicts the description reviewers are given. The description's "Notes for reviewers" section should be updated to reflect the actual final state.
Reviews (1): Last reviewed commit: "fix(test): drop redundant module-level i..." | Re-trigger Greptile
| let refund = self.state_gas_new_account; | ||
| self.state_gas_reservoir = self | ||
| .state_gas_reservoir | ||
| .checked_add(state_refund) | ||
| .ok_or(InternalError::Overflow)?; | ||
| // Track as intrinsic state gas adjustment (matches EELS intrinsic_state_gas -= refund). | ||
| // Do NOT reduce state_gas_used here — that would inflate regular_gas in block accounting. | ||
| self.intrinsic_state_gas_refund = self | ||
| .intrinsic_state_gas_refund | ||
| .checked_add(state_refund) | ||
| .checked_add(refund) | ||
| .ok_or(InternalError::Overflow)?; | ||
| self.state_gas_used = self.state_gas_used.saturating_sub(refund); | ||
| self.intrinsic_state_gas_charged = | ||
| self.intrinsic_state_gas_charged.saturating_sub(refund); |
There was a problem hiding this comment.
state_gas_reservoir_initial not updated alongside reservoir on auth refund
set_delegation adds refund to state_gas_reservoir but leaves state_gas_reservoir_initial (captured moments earlier in add_intrinsic_gas) unchanged. The refund_sender formula is regular_gas = raw_consumed − intrinsic_state_charged − reservoir_initial − spill + reclassified. Because intrinsic_state_charged is reduced by refund but reservoir_initial is not, the result overcounts the regular gas dimension by refund (= STATE_BYTES_PER_NEW_ACCOUNT × cpsb ≈ 131,488) per existing authority. This flows into block_regular_gas_used via apply_plain_transaction, so a block with N existing-authority type-4 txs reports N × refund extra regular-dimension gas, tightening the 2D budget for subsequent transactions.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/utils.rs
Line: 354-361
Comment:
**`state_gas_reservoir_initial` not updated alongside reservoir on auth refund**
`set_delegation` adds `refund` to `state_gas_reservoir` but leaves `state_gas_reservoir_initial` (captured moments earlier in `add_intrinsic_gas`) unchanged. The `refund_sender` formula is `regular_gas = raw_consumed − intrinsic_state_charged − reservoir_initial − spill + reclassified`. Because `intrinsic_state_charged` is reduced by `refund` but `reservoir_initial` is not, the result overcounts the regular gas dimension by `refund` (= `STATE_BYTES_PER_NEW_ACCOUNT × cpsb ≈ 131,488`) per existing authority. This flows into `block_regular_gas_used` via `apply_plain_transaction`, so a block with N existing-authority type-4 txs reports `N × refund` extra regular-dimension gas, tightening the 2D budget for subsequent transactions.
How can I resolve this? If you propose a fix, please make it concise.Brings ethrex up to bal-devnet-4 fixture spec. Rolls up EIP-7928, EIP-8037, EIP-7976, EIP-7981, EIP-7708 and misc BAL validation fixes into one change set. BAL (EIP-7928) - Widen BlockAccessIndex and related recorder/index fields to u32. - Shadow BAL recorder on per-tx tx_dbs in the parallel validator: diff touched_addresses / storage_reads against header BAL to catch missing pure-access entries and missing storage_reads. - Fall back to pre-state code_hash in validate_tx_execution PART B when the BAL has no code_changes entry (missing_code_change). - Stop whitelisting SYSTEM_ADDRESS from unaccessed_pure_accounts via system_seed / current_accounts_state scrubs; user-tx touches still remove it via the per-tx tracked_accounts path. EIP-8037 (state gas 2D accounting) - Dynamic cost_per_state_byte(block_gas_limit), Amsterdam only. - Two-counter reservoir: state_gas_spill_outstanding + state_gas_credit_against_drain for correct revert math across nested sub-calls (PR #2733 clamp-and-spill). - Per-tx 2D inclusion check (PR #2703) in sequential + parallel paths: reject with GAS_ALLOWANCE_EXCEEDED when tx.gas worst-case exceeds remaining block regular/state budget. - intrinsic_state_gas immutable across the tx (PR #2711) and subtracted separately when deriving block-dimensional regular gas. - CREATE collision/early/child failure refunds account state gas. - Same-tx SELFDESTRUCT refunds state gas clamped against execution-only state gas (PR #2707), not total state_gas_used. - Revert-path reservoir refill uses the PR #2733 X - Z formula. - Top-level reservoir reset on tx failure (PR #2689). - Zero gas_remaining on precompile exceptional halt so block accounting sees the full intrinsic. Calldata / access-list floors - TOTAL_COST_FLOOR_PER_TOKEN 10 -> 16 under Amsterdam (EIP-7976). - Access-list data bytes fold into floor-token count (EIP-7981). EIP-7708 - Lex-ordered burn logs, no coinbase priority-fee log, SELFDESTRUCT- destination coalescing. Tests - New levm tests for EIP-7976/7981, EIP-8037 refund/code-deposit/ top-level-failure paths. - Skip 6 zkevm@v0.3.0 EIP-8025 fixtures filled against bal@v5.6.1 (re-enable once zkevm@v0.4.x ships). Hive consume-engine amsterdam: 1339 pass, 3 remaining (withdrawal missing-entry cases addressed by PR #6463, cherry-pick pending).
Addresses miss-slot risks found in the builder/validator parity audit of the bal-devnet-4 rollup. Three builder-side paths could produce blocks the validator rejects, plus minor hardening. - Mempool intrinsic gas was using `TX_CREATE_GAS_COST = 53000` unconditionally for CREATE. Under Amsterdam the VM charges the `(regular, state)` split derived from `intrinsic_gas_dimensions` (`REGULAR_GAS_CREATE + STATE_BYTES_PER_NEW_ACCOUNT * cpsb`). Route through the shared helper for Amsterdam+ so admission matches VM charge. - Payload builder (`fill_transactions`) had no EIP-8037 PR #2703 per-tx 2D inclusion check. A tx passing execution in the builder could still fail the check in the validator's aggregation loop and invalidate the block. Expose `check_2d_gas_allowance` as pub and call it before any BAL touches so rejected txs contribute nothing. - L2 payload builder recorded sender/recipient BAL touches before executing, with no checkpoint/restore for the `undo_last_tx` path (invalid L2 out-message) or apply-tx error. Mirror the L1 builder: take a `bal_checkpoint` after `set_bal_index`, restore on both rejection paths. - `execute_block_parallel` now computes `is_amsterdam` locally and explicitly gates the 2D inclusion loop, keeping the Amsterdam-only invariant checkable rather than implicit in the caller. - `check_2d_gas_allowance` doc now explains why our `block_regular_gas_used` aggregates `max(raw_regular, floor)` at tx-report time (matching EELS `block_output.block_gas_used`). - Post-exec 2D overflow rollback in `apply_plain_transaction` replaces the unchecked `-=` on `cumulative_gas_spent` with `saturating_sub` + `debug_assert`. - Missing-code-change per-tx BAL validation: when a BAL account has no `code_changes` entry (`seeded_pos == 0`), fall back to the `system_seed` / store pre-state code_hash. Fixes the remaining `missing_code_change` Hive test. Hive consume-engine amsterdam: 1340 pass, 2 remaining (withdrawal missing-entry cases addressed by PR #6463).
Regression guards for builder/validator drift on Amsterdam blocks. Both paths share the VM core but diverge in plumbing (mempool admission, shadow BAL recorder, 2D inclusion check, BAL checkpoint/restore, coinbase and SYSTEM_ADDRESS filters), and a disagreement means a missed slot on devnet that a green ef-tests run cannot catch — ef-tests only consume blocks, never produce them. Two groups of tests: Positive parity (9). Builder produces a legitimate block, validator pipeline (parallel, BAL-seeded) must accept. Covers: empty block with only pre-exec system calls; plain transfer; CREATE tx (Amsterdam intrinsic split); SSTORE state gas; BALANCE of untouched account (pure-access BAL entry); calldata floor (EIP-7976); access-list floor (EIP-7981); user-tx touch of SYSTEM_ADDRESS; multi-tx multi-sender aggregation. Negative parity (5). Builder produces a legitimate block, the test corrupts the BAL (drops / mutates / appends entries), re-hashes the header, and the validator must reject. Each scenario mirrors one of the Hive test_bal_invalid_* cases fixed in session 3: parity_reject_missing_pure_access_account parity_reject_surplus_system_address parity_reject_missing_storage_read parity_reject_missing_storage_change parity_reject_missing_code_change If one of the negative tests ever flips to "accept" the corresponding BAL-validation check has regressed; treat as P0.
Follow-up to PR #6518 addressing the test-gap list documented in the session-3 review. Covers every remaining item in TODO.md except the upstream zkevm@v0.4.x fixture re-enable (tracked externally). Tests (10 new): - `test_cpsb_clamp_to_one_for_tiny_gas_limit`, `test_cpsb_30m_bin_boundary` — cpsb quantization boundaries. Guards against an off-by-one in the `if quantized > CPSB_OFFSET` branch and against bin boundary regressions in the 5M-30M range. - `test_change_variants_rlp_roundtrip_index_above_u16_max` — RLP round-trip for all 4 BAL change variants at index 70_000, guarding against an accidental revert to the pre-devnet-4 `u16` type that would silently truncate high indices. - `amsterdam_create_intrinsic_matches_vm_dimensions` — mempool admission for Amsterdam CREATE txs must match the VM's `(regular, state)` split (TX_BASE + REGULAR_GAS_CREATE + STATE_BYTES_PER_NEW_ACCOUNT * cpsb), not the legacy 53000. - `test_intrinsic_parity_plain_transfer` / `test_intrinsic_parity_create_tx` / `test_intrinsic_parity_with_calldata_and_access_list` / `test_intrinsic_parity_eip7702_auth_list` — parity between the standalone `intrinsic_gas_dimensions` helper (used by mempool and payload builder) and `VM::get_intrinsic_gas` (used during execution). Run across Prague / Osaka / Amsterdam at 30M and 120M block gas limits. - `test_call_to_empty_account_with_value_retains_parent_state_gas` — EIP-8037 CALL-to-empty-with-value charges new-account state gas in the caller's frame, retained across successful parent continuation. Pairs with the existing `test_child_charge_then_revert_returns_state_gas_to_parent` for the revert direction. Code polish: - Clarifying comment on the `frame_outstanding_delta` invariant in `credit_state_gas_refund` (`crates/vm/levm/src/vm.rs`). The subtraction is fragile — documenting why it must read `state_gas_spill_outstanding` and not `state_gas_spill`. - `debug_assert!` guards on tx count vs `u32::MAX` at each block-exec entry (`execute_block`, `execute_block_pipeline`), keeping the EIP-7928 `BlockAccessIndex` invariant explicit rather than implicit in the ~10 downstream `u32::try_from(...).unwrap_or(u32::MAX)` sites. Docs: - `docs/roadmaps/forks-roadmap.md` — EIP-7976 / EIP-7981 flipped 🔴→✅, EIP-8037 status line expanded (dynamic cpsb, clamp-and-spill, 2D inclusion, same-tx SELFDESTRUCT refund), priority note updated for bal-devnet-4 + PR #6518. All 478 tests pass. No behavior changes — these are regression guards and documentation for the bal-devnet-4 work landed in PR #6518.
Two bot reviews (Codex, Claude) + one code reviewer (Greptile) flagged 7 issues. All 7 verified real and fixed in-PR per user request. Critical (L2 consensus/liveness): - `crates/l2/sequencer/block_producer/payload_builder.rs` now enforces the EIP-8037 PR #2703 per-tx 2D inclusion check against the L2's `configured_block_gas_limit` before executing each tx, matching the L1 builder. Without this, the L2 builder could reject valid txs or accept txs that violate one dimension of the block cap. - `fill_transactions` now snapshots and restores `block_regular_gas_used` / `block_state_gas_used` around `apply_plain_transaction` on the `undo_last_tx` rollback path (invalid L2 out-message). Previously those two counters stayed inflated after a rejected tx, polluting `gas_used()` and the final header `gas_used`. High (mempool DoS avenue): - `crates/blockchain/mempool.rs::transaction_intrinsic_gas` now enforces `max(intrinsic_regular + intrinsic_state, floor)` for Amsterdam+, matching the VM's `validate_min_gas_limit` check. Previously a tx with mostly zero calldata could pass mempool admission at the weighted EIP-2028 cost (400 gas for 100 zero bytes) but fail the VM's 6400-gas unweighted floor at block inclusion, polluting the pool. New standalone helper `intrinsic_gas_floor(tx, fork)` added in `crates/vm/levm/src/utils.rs` mirroring `VM::get_min_gas_used` so the mempool / payload builder can compute the floor without a VM instance. Re-exported from `ethrex-vm`. Medium: - `crates/vm/backends/levm/mod.rs` withdrawal index computation switched from `.map(|n| n + 1)` to `.map(|n| n.saturating_add(1))`. The prior form wraps to 0 in release builds when `n == u32::MAX` (the `debug_assert` only fires in debug). - `crates/vm/levm/src/opcode_handlers/system.rs` adds `debug_assert!` at the two reservoir-revert sites verifying `outstanding_delta >= credit_against_drain_delta`. If that invariant is ever violated, the `saturating_sub` silently mischarges the block's regular dimension; a loud debug panic is preferable. Style: - `crates/vm/levm/src/gas_cost.rs::access_list_bytes` — replace `keys.len() as u64` with `u64::try_from(...).unwrap_or(u64::MAX)` for consistency with the rest of the codebase. - `crates/vm/levm/src/hooks/default_hook.rs::refund_sender` — rename the currently-unused `gas_used_pre_refund` parameter to `_gas_used_pre_refund` at the signature and drop the interior `let _ =` that was silencing it. Expanded doc explains it's kept in the signature for future reintroduction. All 478 tests pass; no behavior changes except the three intentional ones (mempool floor, L2 2D check, L2 counter rollback) plus the already-exercised saturation edge.
…this branch `make -C tooling/ef_tests/blockchain test` now invokes `test-stateless-zkevm` instead of `test-stateless`. Reason: zkevm@v0.3.3 fixtures are filled against bal@v5.6.1, which is out of sync with this branch's bal-devnet-6+ (and bal-devnet-7-prep set_delegation re-application) gas accounting. PR #6527 on main broadened test-stateless to extract the entire for_amsterdam tree from the zkevm bundle and run all of it under --features stateless; that scope trips ~549 fixtures on this branch with GasUsedMismatch / ReceiptsRootMismatch / BlockAccessListHashMismatch. Re-broaden once the zkevm bundle is regenerated against the current bal spec. docs/known_issues.md updated to surface this in the PR sticky comment + job summary. Verified locally end-to-end: make test → 8737 + 93 passed; 0 failed.
The narrowing in the Makefile is repo-wide (lands on main once this PR merges), not branch-only. Drop the misleading "on this branch" wording and reframe the reason in terms of the current bal spec rather than this branch's state.
- Preamble no longer says "on the current branch" — the doc lives in-tree and applies wherever it's checked out. - Comment heading drops "on this branch" likewise. - Bal-devnet-6 section drops the "Tracking via PR #6574" line, which pointed back at the PR introducing this doc and stops being meaningful the moment that PR merges. The resolution-path bullets already cover what's needed to re-enable.
The Paris withdrawal Block Re-Org hive tests assert the old "accept deep reorg" engine-API behaviour. Under execution-apis PR #786, the EL must reject FCUs whose canonical_link_height < stored_finalized with the new -38006 TooDeepReorg error, and ethrex implements that. The hive engine simulator has not been updated to PR #786 semantics, so it treats the spec-correct rejection as a failure. Adds the 8 affected test names to KNOWN_FLAKY_TESTS in check-hive-results.sh (substring match), and documents the exclusion in docs/known_issues.md so it surfaces in the sticky PR comment + job summary. Re-enable once hive catches up.
Each section now shows a one-paragraph summary by default; affected test names, bucket tables, resolution paths, and full lists move into collapsibles. Visible-by-default content drops from ~83 to ~32 lines, so the sticky PR comment and job summary read at a glance instead of flooding the page.
…sults check The 8 Engine withdrawal Block Re-Org (Paris) entries added in 4d217db aren't flaky — they're spec-correct rejections under execution-apis #786 (`-38006 TooDeepReorg`) that hive hasn't caught up to. Group both categories under a neutral name and document the split in the array comment so the distinction is preserved. Renames the array, the jq-filter variable, and the log-line wording. No behaviour change.
…me-engine The Hive Consume Engine Amsterdam job runs the same snobal-devnet-6 fixtures we already skip in the blockchain runner's SKIPPED_BASE, just routed through the eels/consume-engine simulator — so it surfaces the same 54 failures across 32 EELS test functions on this branch. Add a third "(3) bal-devnet-6 fixture-vs-impl mismatch" category to KNOWN_EXCLUDED_TESTS in check-hive-results.sh. Substrings anchored on `[fork_Amsterdam` so legacy Prague/Osaka variants still run, mirroring the SKIPPED_BASE pattern. docs/known_issues.md gains a parallel section so the sticky PR comment + job summary list both surfaces. Re-enable once fixtures bump to snobal-devnet-7 or the bal-devnet-7-prep subtraction is reverted.
…subtraction The top-halt reclassification formula subtracted both `min(credit_against_drain, regular_gas_reclassified)` and `regular_gas_reclassified` from the gross spill, double-counting the already-reclassified amount whenever a deeper halt had reclassified its subtree's spill. Manifested on bal-devnet-6 FullSync as `gas_used` mismatch on block 597 (Δ = 131,488 = 1·STATE_NEW) for txs with nested CREATEs that all halt. Drain credits already affect `state_gas_refund_absorbed` (reduces net state-gas at finalize) and refill `state_gas_reservoir` via `credit_state_gas_refund`, so they have no further role at top-halt reclassification. The simpler rule is: every byte of gross spill not already reclassified at a deeper halt boundary moves into the regular dimension here. `test_top_halt_phantom_drain_does_not_cancel_real_spill` still passes (the cap was already pinning drain_cap to 0 in that case via the `min(_, regular_gas_reclassified)` clamp). All 30 EIP-8037 tests green.
…tests The snobal-devnet-6 ef-test suite (blockchain runner) covers the behavioral surface these unit tests were guarding: transfer/burn logs (tests/amsterdam/eip7708_eth_transfer_logs/), calldata + access-list floor (eip7976/eip7981), SSTORE 0→N→0 refunds (state_gas_sstore), top-level halt/revert/OOG reservoir reset (state_gas_reservoir), and CREATE code-deposit OOG paths (state_gas_create). None of these ethrex scenarios overlap with the 74 fixtures in SKIPPED_BASE. Kept: - eip7708: source-level constants vs keccak preimages (fixtures embed the hashes directly, can't validate the derivation) - eip8037_tests: intrinsic_gas_dimensions ↔ VM::get_intrinsic_gas parity (ethrex-internal two-path concern, not in EELS) - eip8037_top_level_failure_tests: phantom-drain and partial-credit divergence guards that pin gas_used on halt paths where ethrex's reclassification formula has historically drifted by one state-gas charge
Punts the parity work to a separate follow-up branch. Removes the hand-written suite (builder_validator_parity_tests.rs, 14 tests over ~1080 LOC) and the cfg-gated `builder-parity` feature that exercised build→validate parity across every Amsterdam blockchain ef-test fixture (test_runner.rs run_builder_parity + collect_header_mismatches, ~170 LOC), the Makefile target, and the Cargo feature flag. Default `make test` runtime and CI signal are unchanged — the feature was off by default. Will be reintroduced from scratch in a dedicated parity PR once devnet-7 fixtures settle.
Prior commit (2496c2b) trimmed `test/tests/levm/eip7708_tests.rs` to a single constants check, on the assumption the file was fully new in this PR. It wasn't — 25 of the 28 tests had been on `main` for a while (landed in PR #6322, well before this branch diverged). Only 3 tests were genuinely added on this PR: - test_burn_logs_emitted_in_lex_ascending_order_three_accounts - test_coinbase_priority_fee_does_not_emit_transfer_log - test_multi_selfdestruct_dest_emits_single_burn_log_with_combined_balance Those three are covered by the EELS burn/transfer-logs ef-tests we now run via the blockchain runner, so dropping them is correct. The other 25 belong to main and should not have been touched here. Restored to main verbatim and added the new `Environment::is_system_call` field introduced by this PR.
…zed prefix Hive `Withdrawals Block Re-Org (Paris)` tests were failing with `-38006` because ethrex was returning `TooDeepReorg` whenever an FCU's canonical link was below `stored_finalized`. Per execution-apis PR 786 point 6 the limit for `-38006` is "specific to the client software" — Erigon, Nethermind, Besu and geth all scope it to their state-retention/unwind capability, not to a finalized-crossing policy. ethrex's stricter reading caused legitimate CL-driven reorgs to be rejected. CLMock's `DefaultSlotsToFinalized = 2` puts `stored_finalized` two blocks behind head during canonical chain construction. Hive then asks for an 8- or 10-block sidechain reorg; canonical_link sits below finalized, the old check fired with `Reorg depth 8 exceeds the client's limit of 2`, and the test asserted Valid → FAIL. Spec compliance preserved: - Point 2 (skip when head is canonical ancestor of finalized) still handled by the `NewHeadAlreadyCanonical` branch at fork_choice.rs:81. - Point 5 (`-38002 Invalid forkchoice state` for disconnected safe/ finalized) unaffected. - Point 6 (`-38006 Too deep reorg`) still returned when `reorg_depth > REORG_DEPTH_LIMIT (128)` — ethrex's actual state-history cap. We now match the industry-wide reading: trust the CL's fork choice and only refuse when we physically cannot unwind. Also drops the 8 hive Block-Re-Org entries from `KNOWN_EXCLUDED_TESTS` in `.github/scripts/check-hive-results.sh` and the matching section in `docs/known_issues.md`. Smoke tests stay green (6 passing including `unfinalized_reorg_deeper_than_32_is_allowed`).
bal-devnet-6 EELS spec (`devnets/bal/6` `eoa_delegation.py`) only does `message.state_gas_reservoir += STATE_BYTES_PER_NEW_ACCOUNT × cpsb` on each existing-authority refund. Docstring: "no mutation of intrinsic_state_gas". Drops the two extra subtractions previously added in `cefdf69de` that anticipated EELS PR #2711 / #2816 (the bal-7 `state_refund` channel). For bal-6 the block-level `state_gas_used` intentionally stays "inflated" by the auth refund — the refund is sender-side only in this devnet. Block-accounting subtraction lands in bal-devnet-7 via the separate `state_refund` channel. Unblocks the snobal-devnet-6 EIP-7702 fixtures previously allowlisted under "bal-devnet-6 known-failing fixtures (Amsterdam fork only)" in tooling/ef_tests/blockchain/tests/all.rs.
ElFantasma
left a comment
There was a problem hiding this comment.
Four findings inline — all minor. None blocking.
Body finding (not inline): the 74-fixture skip list in tools/ef_tests/blockchain/tests/all.rs + docs/known_issues.md is the right contract, but worth wiring a tracking issue so the skip list shrinks as snobal-devnet-7 fixtures land — easy to forget once devnet attention moves on.
The skip list is alredy gone in the bal-devnet-7 branch |
Motivation
Bring ethrex up to bal-devnet-6 spec so we can participate in the devnet. Rolls up the work that landed across
bal-devnet-3→bal-devnet-4→bal-devnet-5→bal-devnet-6. The originalbal-devnet-6branch is kept for ongoing devnet work; this*-prbranch is the one intended for merge.Description
v1.1.0),BlockAccessIndexwidened fromu16tou32, shadow recorder for per-tx access validation in parallel execution,SYSTEM_ADDRESSexclusion.cost_per_state_bytepinned to 1174 for bal-devnet-4..6.ExceptionalHaltreclassifies state gas → regular dim (both CALL and CREATE return paths).state_gas_credit_against_drainelevated so credit burns propagate.gross_spill − credit_against_drain − already_reclassifiedreclassified to regular dim.set_delegationrefund: subtracts fromstate_gas_used/intrinsic_state_gas_charged(bal-devnet-7-prep accounting, SELFDESTRUCT-style). See note below.credit_against_draincapped byregular_gas_reclassifiedso phantom-drain credits don't cancel real spill.check_2d_gas_allowanceruns per-tx before BAL recording (rejected txs don't pollute the BAL).-38006 TooDeepReorgreturned when the requested reorg depth exceeds ethrex's state-history retention (point 6).docs/known_issues.md).bal-devnet-7 direction (intentional)
The bal-devnet-6 spec acknowledges as a known-bug that
set_delegationfor an existing authority should subtract the auth refund fromtx_state_gas(mirroring SELFDESTRUCT) but currently does not. Upstream has a regression test (9b3961a65 test_snobal_block_gas_used_inflated_by_7702_auth_refund) that locks in the un-subtracted behavior for bal-devnet-6 verification. This PR carries the bal-devnet-7-prep subtraction so we are already aligned with where EELS is moving; the cost is that 74 snobal-devnet-6 fixtures expect the old un-subtracted accounting and are skipped viatooling/ef_tests/blockchain/tests/all.rs::SKIPPED_BASEuntil fixtures bump to snobal-devnet-7.Checklist
STORE_SCHEMA_VERSION(crates/storage/lib.rs) if the PR includes breaking changes to theStorerequiring a re-sync. (N/A — no Store schema changes)