Skip to content

execution: implement EIP-8037 changes and simplifications for bal-devnet-7#21207

Merged
taratorio merged 28 commits into
mainfrom
worktree-eip-8037-bal-devnet-7
May 16, 2026
Merged

execution: implement EIP-8037 changes and simplifications for bal-devnet-7#21207
taratorio merged 28 commits into
mainfrom
worktree-eip-8037-bal-devnet-7

Conversation

@taratorio

Copy link
Copy Markdown
Member

@taratorio

Copy link
Copy Markdown
Member Author

@mh0lt should be ready 🤞

@taratorio taratorio added this pull request to the merge queue May 16, 2026
Merged via the queue into main with commit e98f94d May 16, 2026
67 checks passed
@taratorio taratorio deleted the worktree-eip-8037-bal-devnet-7 branch May 16, 2026 09:38
@yperbasis yperbasis added the Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 label May 18, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Glamsterdam https://eips.ethereum.org/EIPS/eip-7773

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants