Skip to content

fix(exec): EIP-7702 delegate-then-CREATE nonce-halt stopgap + defensive hardening#387

Open
Richard1048576 wants to merge 2 commits into
Galxe:mainfrom
Richard1048576:fix/7702-create-halt-stopgap
Open

fix(exec): EIP-7702 delegate-then-CREATE nonce-halt stopgap + defensive hardening#387
Richard1048576 wants to merge 2 commits into
Galxe:mainfrom
Richard1048576:fix/7702-create-halt-stopgap

Conversation

@Richard1048576

@Richard1048576 Richard1048576 commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Stacked on #385. The tx_filter stopgap builds on that PR's block-order sequential
filter, so this branch contains #385's commits too. Until #385 lands on main, the diff
below also shows #385's changes; GitHub recomputes it to just the files here once #385
merges. The net-new change is the single commit cb8745a816.

Three hardening changes on the deterministic ordered-block execution path (the executor
panics on any tx-level InvalidTransaction, which halts every validator).

1. CREATE-halt stopgap — tx_filter (gravity-audit#838)

A 7702 authority delegated in-block to a non-zero delegate can, if its delegated code
runs CREATE/CREATE2, have its nonce bumped a second time during execution — an
effect the filter cannot model without executing (nonce change (1) caller bump and (2) 7702
authorization bump are modelled; (3) execution-induced CREATE bump is not). A same-block
follow-up tx that the filter admits is then NonceTooLow in revm → executor panic → halt.
Reproduced (unit-level): filter admits [tx1, tx2], revm returns NonceTooLow{tx:1,state:2}.

This drops every later same-block tx from an account delegated earlier in the block
(halt-safe over-rejection), keeping the NonceTooLow → panic path unreachable for this
vector.

  • Known gap (documented in code): does not cover an account already delegated at block
    start that is then called via an inner call the filter cannot see; closing that fully would
    require rejecting all delegated-EOA txs, which breaks legitimate 7702. The complete fix is
    executor-side (do not panic on a tx-level InvalidTransaction) — tracked in #838 / #823.
  • Not RPC-reachable: the required future-nonce follow-up tx is parked in reth's queued
    sub-pool and never proposed, so this vector needs a byzantine block proposer, not an
    ordinary RPC user (analysis in #838).

2. onchain_config/metadata_txn — return Result instead of .unwrap()

The free transact_system_txn helper returned (SystemTxnResult, EvmState) and .unwrap()ed
transact_raw. It now returns Result. Defensive only — no production caller today (the live
path uses the executor method), so wiring it into the live path later cannot reintroduce a
halt.

3. onchain_config/types — saturating voting-power casts (gravity-audit#823)

Validator voting-power to::<u64/u128>() (panics on overflow) → saturating_to + a saturating
fold, so a pathological votingPower cannot overflow-panic on the epoch-boundary
NewEpochEvent path. A safety net: the clamp is only reachable above ~1.8e19 tokens for a
single validator (far past total supply).

Verification

Built and run on a Prague single-node devnet:

  • 28 tx_filter unit tests pass, including a new test_filter_drops_later_tx_from_in_block_delegated_authority; no regression in the existing 27.
  • Prague e2e suite passes. The only "failure" is the halt-repro test (test_eip7702_authority_nonce_halt) correctly not halting on the fixed binary (head 574→735, setcode mined, stale tx dropped) — an inverted test whose "failure" confirms the fix. All P-B 7702, fix/survival, cross-account, and EIP-2935 tests pass.

Also folded in — oracle replay-revert log level (was #359)

Absorbs the standalone #359 into this execute-layer PR (closing #359). On mainnet a
benign system-tx revert was logged at ERROR: a duplicate / already-committed oracle
attestation rejected by NativeOracle's sequential-nonce replay guard
(NonceNotSequential, selector 0x32e429cd; legacy NonceNotIncreasing 0xc778b1a4) —
the guard working as intended. This tripped alerting and masked real failures.

execute_ordered_block now decodes the revert selector, classifies severity, and logs
recoverable system-tx reverts at WARN; unknown / fatal reverts stay ERROR.
Touches lib.rs (the log site) + onchain_config/errors.rs (selector decode + severity).
Compiles clean (cargo check -p reth-pipe-exec-layer-ext-v2).

Related

🤖 Generated with Claude Code

… defensive hardening

Three hardening changes on the deterministic ordered-block execution path. Stacked on Galxe#385
(the tx_filter stopgap builds on that PR's block-order sequential filter).

1. tx_filter: CREATE-halt stopgap (gravity-audit#838). A 7702 authority delegated in-block to
   a non-zero delegate can, if its delegated code runs CREATE/CREATE2, have its nonce bumped a
   *second* time during execution — an effect the filter cannot model without executing. Any
   *later* same-block tx from such an account is now dropped (halt-safe over-rejection),
   keeping the executor's NonceTooLow -> panic path unreachable for this vector. KNOWN GAP
   (documented in code): does not cover an account already delegated at block start that is
   then called via an inner call the filter cannot see; closing that fully would require
   rejecting all delegated-EOA txs, which breaks legitimate 7702. The complete fix is
   executor-side (do not panic on a tx-level InvalidTransaction), tracked in #838 / #823.
   Not RPC-reachable: a future-nonce follow-up tx is parked in reth's queued sub-pool and
   never proposed, so this vector requires a byzantine block proposer (see #838).

2. onchain_config/metadata_txn: the free `transact_system_txn` helper returns `Result` instead
   of `.unwrap()`-panicking on a tx-level error (defensive; no production caller today, so
   wiring it into the live path later cannot reintroduce a halt).

3. onchain_config/types: validator voting-power casts use `saturating_to` + a saturating fold
   so a pathological `votingPower` cannot overflow-panic on the epoch-boundary NewEpochEvent
   path (gravity-audit#823; a safety net, only reachable above ~1.8e19 tokens for one
   validator, far past total supply).

Verified on a Prague single-node devnet: 28 tx_filter unit tests pass (including a new stopgap
test; no regression), and the Prague e2e suite passes — the only "failure" is the halt-repro
test correctly NOT halting on the fixed binary, which confirms the fix.
…t ERROR

A duplicate / already-committed oracle attestation rejected by NativeOracle's
sequential-nonce replay guard (NonceNotSequential, selector 0x32e429cd; legacy
NonceNotIncreasing 0xc778b1a4) is benign — the replay guard working as intended.
It was logged at ERROR on mainnet, tripping alerting and masking real failures.
Decode the revert selector, classify severity, and log recoverable system-tx
reverts at WARN; unknown / fatal reverts stay ERROR.

Folds gravity-reth#359 into this PR so it ships as one execute-layer change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Richard1048576 Richard1048576 force-pushed the fix/7702-create-halt-stopgap branch from c5c3679 to 37fe70e Compare July 11, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant