execution/tests: update eest fixtures to stable v5.4.0#20541
Conversation
15ada4b to
a88ea19
Compare
|
@yperbasis this is good to go green hive eest run: https://github.com/erigontech/erigon/actions/runs/24702808441 |
There was a problem hiding this comment.
Pull request overview
Updates Erigon’s EEST integration from v5.3.0 to v5.4.0, aligning fixtures/CI inputs and fixing two consensus/validation ordering issues exposed by the newer EEST coverage.
Changes:
- Bump EEST fixtures submodule to v5.4.0 and update the Hive EEST CI workflow to download v5.4.0 fixtures.
- Ethash: remove the “known header” short-circuit so persisted headers still get validated during later validation passes.
- Tx execution: check tx gas vs remaining block gas earlier to ensure “gas allowance exceeded” classification wins over London fee-cap errors when both would apply.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| execution/tests/execution-spec-tests | Submodule bump to EEST v5.4.0 fixtures. |
| .github/workflows/test-hive-eest.yml | Update EEST_VERSION to v5.4.0 for CI fixture download parity. |
| execution/protocol/rules/ethash/rules.go | Ensure header validation runs even if the header is already persisted/known. |
| execution/protocol/txn_executor.go | Adjust pre-check ordering to surface ErrGasLimitReached before EIP-1559 fee-cap checks when appropriate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -311,6 +311,10 @@ func (st *TxnExecutor) preCheck(gasBailout bool, intrinsicGasResult mdgas.Intrin | |||
| } | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
The new early gas-pool check is intentionally redundant with buyGas()->gp.SubGas(), but the reason (error ordering vs London fee-cap checks) isn’t obvious from the code. Consider adding a short comment explaining that this check must happen before the EIP-1559 fee-cap validation to match EEST/reference-client error classification (gas allowance exceeded should win over fee-cap errors when tx.gas > gasAvailable).
| // This early gas-pool check is intentionally redundant with the later | |
| // buyGas()->gp.SubGas() path. It must happen before the EIP-1559 fee-cap | |
| // validation so error classification matches EEST/reference clients: | |
| // when tx.gas > gasAvailable, ErrGasLimitReached should win over fee-cap errors. |
| if st.gp != nil && st.msg.Gas() > st.gp.Gas() { | ||
| return ErrGasLimitReached | ||
| } |
There was a problem hiding this comment.
There’s no regression unit test covering the new error-ordering behavior (tx gas > remaining block gas should return ErrGasLimitReached even if London fee-cap/baseFee checks would also fail). Since this file already has TxnExecutor-focused tests, please add a test that sets a non-zero baseFee (NoBaseFee=false), constructs a London tx/message with maxFeePerGas < baseFee and gas > gp.Gas(), and asserts ErrGasLimitReached.
yperbasis
left a comment
There was a problem hiding this comment.
Suggestion:
- Add a one-line comment to the new probe in txn_executor.go:preCheck. The probe returns the same ErrGasLimitReached that buyGas → gp.SubGas would eventually return, so a future
reader will likely flag it as dead code and remove it. A comment like // Early probe: EELS check_transaction expects GAS_ALLOWANCE_EXCEEDED to surface before
INSUFFICIENT_MAX_FEE_PER_GAS (or similar pointing to cancun/fork.py::check_transaction) would preserve the ordering intent. Same story for rules/ethash/rules.go — optionally a brief
comment explaining why the short-circuit is gone (post-stage_headers, headers may be persisted pre-validation) would save the next archaeologist a git-blame trip.
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
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
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
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
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
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
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
closes #18760
Four commits, all in service of moving Erigon's EEST integration from v5.3.0 to v5.4.0:
Bumps the pinned fixtures submodule from v5.3.0 to v5.4.0. Pure submodule SHA change — the fixtures themselves are generated by ethereum/execution-spec-tests.
Sets EEST_VERSION: v5.4.0 so the Hive EEST CI job downloads the v5.4.0 fixtures tarball that matches the submodule.
Removes the early return nil short-circuit in Ethash.VerifyHeader that skipped validation when the header was already present in the chain reader:
This skip was safe under the old stage_headers pipeline, which performed header validation before persisting. Now that stage_headers is gone and the live flow is insertPosBlocks, blocks are persisted
first and validated later in ValidateChain. With the old skip, already-persisted pre-merge headers would be treated as "known" and return nil without being validated, causing v5.4.0 frontier/berlin
tests to accept otherwise-invalid blocks.
Added in preCheck immediately before the London fee-cap block. The new v5.4.0 test_tx_gas_limit case (PR ethereum/execution-spec-tests#1731) builds a tx whose gas_limit exceeds the block gas_limit and
has gas_price < base_fee, and asserts the client surfaces GAS_ALLOWANCE_EXCEEDED. The EELS reference (cancun/fork.py::check_transaction) checks tx.gas > gas_available before the fee-cap check; Erigon
did the fee-cap check first, so the 5 post-London fork variants were failing with INSUFFICIENT_MAX_FEE_PER_GAS. The new early probe fixes the ordering. The authoritative SubGas deduction in buyGas is
unchanged.
Net result