Commit 46c91f9
authored
fix(eip-7702): filter intrinsic gas + Gravity acceptance tests (#343)
* fix(eip-7702): filter_invalid_txs intrinsic gas check (gravity-audit#668)
The pre-execution filter now mirrors the pool's ensure_intrinsic_gas
so a TxEip7702 with gas_limit < 21000 + 25000·N — injected outside the
pool's fork-gate — is discarded instead of reaching grevm and panicking
the executor with IntrinsicGasTooLow.
Free filter_invalid_txs and its tests move into a new tx_filter module
per _local/drafts/pipe-exec-layer-lib-refactor.md step 3, shrinking
lib.rs from 1829 to 1448 lines. Two regression tests added for
intrinsic-gas-too-low and at-the-floor 7702 cases under Prague.
* test(eip-7702): acceptance tests for #668 fix + designator e2e
Implements the focused subset of the EIP-7702 acceptance design
(`_local/drafts/eip-7702/EIP-7702-acceptance-tests-2026-06-01.md`)
that exercises Gravity-side integration of the gravity-audit#668 fix.
Unit coverage:
- §3.1 U-1..U-3 in `tx_filter::tests`: two-auth 7702 tx with sufficient
gas passes the filter; three-auth 7702 tx at 21k gas is discarded
(the #668 regression); a Prague-spec legacy tx is not regressed.
- §3.2 U-4 / U-5 in `eth::tests`: `ensure_intrinsic_gas` rejects a
TxEip7702 one wei below the Prague intrinsic floor; `validate_one`
short-circuits the same tx with `TxTypeNotSupported` when the pool's
ForkTracker has Prague off, so the intrinsic path is never reached.
E2E coverage in new `gravity_eip7702_test.rs` (drives MockConsensus +
PipeExecLayerApi under `gravity_prague_p3.json`, Prague at block 100):
- P-12: a low-gas TxEip7702 injected through OrderedBlock is discarded
by `filter_invalid_txs` instead of panicking the executor at
`lib.rs:1072`. Pins the #668 fix at the integration boundary.
- P-13: a sufficient-gas TxEip7702 with one valid Authorization lands
on chain; the authority's account code becomes the 23-byte
`0xef0100 || target` designator.
- P-14: P-13 runs under both `disable_grevm=false` (`GrevmExecutor`)
and `disable_grevm=true` (`WrapExecutor<BasicBlockExecutor>`);
designator deployment is observably equivalent in both modes.
The spec's stronger "byte-identical state roots" sub-claim is not
asserted — this codebase intentionally delegates cross-executor
equivalence to unit-level diff tests
(`crates/ethereum/evm/src/parallel_execute.rs:446-449`); both state
roots are printed for forensic inspection.
Adds `alloy-signer` / `alloy-signer-local` as dev-deps of the execute
crate to sign TxEip7702 + SignedAuthorization in tests. Anvil account 0
(pre-funded in `gravity_prague_p3.json`) is the relayer; authority is a
deterministic non-funded EOA so failures reproduce.
Out of scope, documented in the test header as follow-ups: P-1 / P-3
(pool fork-gate via RPC), P-5..P-10 (multi-auth, chain_id==0,
re-delegation, CALL/DELEGATECALL/STATICCALL → designator — exercise
upstream revm semantics), P-15..P-18 (grevm BlockSTM concurrency),
N-1 (manual smoke check).
* fix(eip-7702): reject pre-Prague 7702 txs at filter + drop dead test code
Address PR #343 review findings.
1. Pre-Prague boundary guard (acceptance design P-2). When `spec_id <
PRAGUE`, revm-interpreter's `calculate_initial_tx_gas` ignores
`authorization_list_num` entirely (gas/calc.rs:417), so a TxEip7702
with `gas_limit == 21000` would have passed the new intrinsic check
added in 3877eab and reached the executor — where revm-handler's
`validation.rs:181-182` rejects it with `Eip7702NotSupported` and the
`unwrap_or_else(|err| panic!(...))` at `lib.rs:1067-1073` panics the
node, which is exactly the failure mode gravity-audit#668 reported.
Add an explicit `tx.is_eip7702() && !spec_id.is_enabled_in(PRAGUE)`
short-circuit in `filter_invalid_txs::is_tx_valid` plus a unit test
that pins the SHANGHAI-spec rejection path with a 100k-gas tx (well
above the pre-Prague flat 21k intrinsic) so the guard, not the
intrinsic check, is what kicks in.
2. Drop the dead `now_us` helper and its `_silence_unused` shim from
`gravity_eip7702_test.rs`. All four e2e scenarios use `p3_ts_us`;
`now_us` was left over from an earlier iteration. Also drop the now
unused `SystemTime` import.
* refactor(eip-7702): derive spec_id inside filter_invalid_txs
The caller's MERGE/SHANGHAI/PRAGUE ladder at lib.rs:644-650 mislabelled
CANCUN-active blocks as SHANGHAI. The code happened to be correct for
the filter's purposes only because CANCUN and SHANGHAI behave identically
for `calculate_initial_tx_gas` — but that's an implicit assumption the
caller had no way to verify, and a future hardfork that *did* change
intrinsic gas semantics could silently break the filter without anyone
noticing. The PR review surfaced this as a duplication nit; in fact it
was a latent correctness bug.
Move spec_id derivation inside `filter_invalid_txs` using the canonical
`revm_spec_by_timestamp_and_block_number` helper (alloy-evm) — the same
mapping the executor itself uses, so the two cannot drift apart on a
future hardfork. The function now takes `&ChainSpec, block_timestamp,
block_number` instead of a pre-built `SpecId`; the lib.rs ladder and its
truncated PRAGUE/SHANGHAI/MERGE branches are gone.
Unit tests switch from passing `SpecId::PRAGUE` / `SpecId::SHANGHAI`
literals to passing `&prague_chain_spec()` / `&shanghai_chain_spec()`
built via `ChainSpecBuilder::from(&*MAINNET).{prague,shanghai}_activated()`
— same semantic intent, but the test now exercises the same spec
resolution path the production caller does.
* ci(integration): cover EIP-7702 / EIP-2935 tests in gravity-pipe-test job
The integration tests for EIP-7702 (gravity_eip7702_test.rs) and EIP-2935
(gravity_eip2935_test.rs) were silently uncovered by CI: unit.yml excludes
the whole `reth-pipe-exec-layer-*` family and also filters out `kind(test)`
integration binaries, and the existing gravity-pipe-test job used
`cargo test --exact test`, which only matches the single `fn test()` in
gravity_pipe_test.rs.
Switch that step to `cargo nextest run` with a binary filterset covering
gravity_pipe_test, gravity_eip2935_test, gravity_eip7702_test. All three
share the same MockConsensus + PipeExecLayerApi harness, so one cold build
covers them. Drop --show-output --nocapture: nextest prints failed-test
output by default, which is cleaner for CI logs.
GRETH_DISABLE_PIPE_EXECUTION is intentionally left unset here (commented
inline) since these tests assert the real fork-gated pipe-exec path.
* ci(integration): restrict nextest compile scope with --test to avoid latent breakage
The previous commit (64a2617) extended the gravity-pipe-test job to cover
gravity_eip2935_test and gravity_eip7702_test via `cargo nextest run -p
reth-pipe-exec-layer-ext-v2 -E 'binary(...)'`. That filterset only filters at
*run* time — `-p <crate>` still builds every integration test binary in the
crate, which surfaced a latent breakage: gravity_hardfork_test.rs imports
`reth_ethereum_forks::Hardforks` but `reth-ethereum-forks` is not declared in
execute/Cargo.toml's [dev-dependencies], so compilation fails with E0432.
That test file (and its missing dep) pre-dates this PR — it shipped via #307
and was silently uncovered by the original `cargo test --test gravity_pipe_test`
command, which only compiled one binary. Fixing the underlying defect is out
of scope for the EIP-7702 fix branch; track it separately.
Switch to explicit `--test <name>` flags per binary, which restricts both
compile and run to exactly the three intended targets. CI scope stays where
intended (gravity_pipe_test + EIP-7702 + EIP-2935); the latent
gravity_hardfork_test.rs breakage is left for a follow-up audit-tracked fix.
Verified locally with `cargo nextest run --no-run --locked -p
reth-pipe-exec-layer-ext-v2 --test gravity_pipe_test --test
gravity_eip2935_test --test gravity_eip7702_test` (exit 0).
* ci(nextest): bump slow-timeout for gravity pipe-exec integration tests
The previous commit (7f076ae) made these three integration binaries run
in CI for the first time under nextest. The default slow-timeout of 30s ×
terminate-after=4 (= 120s hard kill) is too tight: gravity_pipe_test pushes
1671 blocks through MockConsensus + PipeExecLayerApi, and under runner
contention (sharing a 2-core ubuntu-latest box with 24 other gravity_eip*
scenarios) routinely hits ~122s — 2s past the kill threshold. Combined with
the workspace-default `retries = { count = 2 }`, nextest then burns ~6 min
retrying a fundamentally fine test before declaring failure.
Add an override mirroring the existing `binary(e2e_testsuite)` entry,
granting these three binaries 2m × terminate-after=3 (6 min ceiling per
test). Other 24 gravity_eip2935/7702 scenarios already passed comfortably
under the default — they fit in well under 30s each — so the override is
scoped narrowly and doesn't slacken correctness signal for the rest of the
workspace.
* ci(nextest): bump gravity pipe-exec timeout to 30m and disable retries
Mirrors the contract of the pre-nextest CI command (`cargo test --test
gravity_pipe_test`), which had no per-test timeout — only the 60-min
job-level cap. The previous 2m × 3 (= 6 min) override was tight enough to
risk false-positive kills under runner contention; bump to 5m × 6 (= 30 min
absolute ceiling) so the kill threshold is dominated by the job timeout,
not nextest's per-test policy.
Set retries = 0 for these binaries:
- These are deterministic state-machine tests (boot a reth node, push a
fixed block sequence, assert end state); a fail-once test will fail-again,
so retry burns runner time without adding signal.
- Default retries=2 combined with 30m per-test would be 90m worst case,
exceeding the 60-min job timeout-minutes.1 parent ba7e949 commit 46c91f9
8 files changed
Lines changed: 1580 additions & 366 deletions
File tree
- .config
- .github/workflows
- crates
- pipe-exec-layer-ext-v2/execute
- src
- tests
- transaction-pool/src/validate
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| 75 | + | |
| 76 | + | |
75 | 77 | | |
76 | 78 | | |
77 | 79 | | |
| |||
90 | 92 | | |
91 | 93 | | |
92 | 94 | | |
93 | | - | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
94 | 110 | | |
95 | 111 | | |
96 | 112 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
75 | 76 | | |
76 | 77 | | |
77 | 78 | | |
| 79 | + | |
| 80 | + | |
78 | 81 | | |
79 | 82 | | |
80 | 83 | | |
| |||
0 commit comments