execution: implement EIP-8037 changes and simplifications for bal-devnet-7#21207
Merged
Conversation
…-8037-bal-devnet-7
…t delegation then reset within same tx - cumulative net change is zero, so no BAL CodePath entry)
…net 130 (115+buffer)
…-8037-bal-devnet-7
…-8037-bal-devnet-7
Member
Author
|
@mh0lt should be ready 🤞 |
mh0lt
approved these changes
May 15, 2026
This was referenced May 15, 2026
Closed
7 tasks
…-8037-bal-devnet-7
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
mh0lt
added a commit
that referenced
this pull request
May 18, 2026
The EIP-8037 inclusion check at TxnExecutor.preCheck (#21207) verifies that a transaction fits in the remaining block-level gas pool per dimension: regular: min(tx.gas, MaxTxnGasLimit if Amsterdam+) <= gp.RegularGasAvailable() state: tx.gas <= gp.StateGasAvailable() (Amsterdam+) This is gated on st.gp != nil. In serial exec st.gp is the block-level pool reused across all txs (exec3_serial.go:372, see #20541), so the check fires correctly. In parallel exec each worker constructs its own per-tx gas pool sized to txn.GetGasLimit() (trace_worker.go:121), so the same call from preCheck compares tx.gas against itself and never fires -- the check is effectively bypassed under parallel. Reported symptoms: EEST blocks crafted with tx.gas > block.gasLimit (GAS_ALLOWANCE_EXCEEDED expected) were not rejected by the parallel matrix in #21017. The bad tx either failed later in preCheck with the wrong sentinel (ErrFeeCapTooLow) or executed and tripped the gas-used mismatch -- in either case, EEST/Hive ExceptionMappers mis-classified the failure. Fix: 1. Extract the per-dimension check into protocol.CheckBlockGasInclusion (execution/protocol/gaspool.go). Same logic, one source of truth. A nil pool returns nil so simulation paths (eth_call / eth_simulateV1 / trace_call) -- which construct executors without a block-level pool -- are unaffected. 2. Replace the inline check in TxnExecutor.preCheck with a call to the helper. Serial behaviour is unchanged. 3. Add the parallel equivalent in exec3_parallel.go finalize, where be.gasPool (the block-level pool) is in scope. The call runs in tx-completion order against be.gasPool, immediately before the existing ConsumeRegular / ConsumeState / SubBlobGas block, so the check sees the correctly-decremented pool for each tx. Wraps rules.ErrInvalidBlock for the parallel reject, so the existing bad-block handling (POSSync ReportBadHeaderPoS, unwind, BadBlock(hash)) fires identically to other parallel-side rejects. The earlier revision of this PR added a static tx.gas > header.GasLimit check at the block-iteration layer of both exec3.go (parallel) and exec3_serial.go (serial). That approach worked but duplicated logic across the two paths and didn't follow the EIP-8037 per-dimension semantics. Replaced by the helper-based version here. Local validation: - TestSimulatedBackend_CallContractRevert PASS (regression check) - TestSimulatedBackend_PendingAndCallContract PASS - TestGeneratedTraceApiCollision PASS - go test -short ./execution/{protocol,vm,stagedsync,tests} PASS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
for https://notes.ethereum.org/@ethpandaops/bal-devnet-7