Skip to content

feat(hardfork): gas-exempt system txs + zero SYSTEM_CALLER at Alpha#367

Merged
nekomoto911 merged 25 commits into
Galxe:mainfrom
nekomoto911:feat/system-tx-gas-exempt
Jul 2, 2026
Merged

feat(hardfork): gas-exempt system txs + zero SYSTEM_CALLER at Alpha#367
nekomoto911 merged 25 commits into
Galxe:mainfrom
nekomoto911:feat/system-tx-gas-exempt

Conversation

@nekomoto911

@nekomoto911 nekomoto911 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Gravity Alpha hardfork makes protocol-injected system transactions (metadata onBlockStart + DKG/JWK validator txns) gas-exempt (fee-free but still gas-metered) and zeroes the historical SYSTEM_CALLER sentinel balance (~1.158×10⁵⁸ G genesis alloc) on the activation block.

Why

The SYSTEM_CALLER's genesis sentinel balance was a "fake supply" footgun:

  • Pollutes any total/circulating supply accounting.
  • Implicit dependency on the sentinel being large enough to cover system-tx base fees forever — silent invariant.
  • Concept-leakage between "protocol overhead" and "user economy".

Eliminating both the silent dependency and the fake supply in one fork closes the four Feishu-tracked issues (假供应污染 / 增发 footgun / 概念混乱 / 软不变量).

Design

Dual lever for gas exemption (load-bearing combo = L2 + disable_base_fee; disable_balance_check retained as zero-cost defensive layer):

  • L1 cfg-side (serial + grevm, byte-symmetric): disable_base_fee=true + disable_balance_check=true on transact_system_txn when Alpha is active. disable_nonce_check stays false — nonce sequence is part of the protocol contract.
  • L2 construction-side: system-tx gas_price = 0 when Alpha is active (otherwise base_fee).

One-shot balance migration at Alpha activation:

  • Routes through apply_state_change channel (same pattern as EIP-2935 boundary deployment), so serial == grevm by construction.
  • Reads SYSTEM_CALLER current AccountInfo via a new ParallelExecutor::basic accessor — hook is self-contained, no caller-side scaffolding.
  • Sets balance = U256::ZERO, preserves nonce (>0 because of per-block system txs — keeps the account non-empty under EIP-161, never pruned by state-clear) and code/code_hash (defensive — historical alloc is codeless, but the read result is ground truth).
  • Idempotency gated by transitions_at_timestamp(current_ts, parent_ts) — fires exactly on the activation block.

RPC replay parity — gas-exempt-style cfg must apply during RPC replay of post-Alpha historical blocks, otherwise once the sentinel balance hits zero those blocks would fail with insufficient funds / GasPriceLessThanBasefee. The fix is keyed on sender == SYSTEM_CALLER (recovered, not user-supplied — pure simulation endpoints are explicitly anti-spoofed).

  • Block-family endpoints (debug_traceBlock* / trace_block / trace_replayBlockTransactions): block-level EVM is rebuilt once at the system→user transaction boundary, exploiting the protocol invariant that system txs are positionally pinned at the front of the block. Invariant pinned by reth_chainspec::system_txs_form_head_prefix predicate + per-loop debug_assert!.
  • Single-tx endpoints (debug_traceTransaction / trace_transaction / trace_replayTransaction / replay_transactions_until): per-tx EVM rebuild on kind transition, with initial EVM peeked-and-pre-toggled to skip a redundant rebuild when the prelude leads with a system tx.
  • Pure simulation endpoints (eth_call / eth_estimateGas / eth_simulateV1 / debug_traceCall / trace_call* / trace_rawTransaction): unchanged — they execute user-supplied tx envs, never replay history, and user-supplied from=SYSTEM_CALLER MUST NOT receive free gas.

Single source-of-truth predicates:

  • reth_chainspec::is_system_tx_gas_exempt(chain_spec, block_ts) — the Alpha gate predicate, hoisted to reth-chainspec so RPC + execution layer + pipe layer all share one impl.
  • reth_chainspec::is_gravity_system_caller(addr) — sender comparison helper.
  • reth_chainspec::system_txs_form_head_prefix(senders) — unit-tested algorithm of record for the system-txs-at-block-head protocol invariant. Both RPC replay loops debug_assert! it.
  • reth_chainspec::SYSTEM_CALLER — the single address const.

Sibling PRs (split out for independent review)

Two pre-existing RPC↔canonical divergences originally folded into this PR have been split into their own PRs. They are orthogonal to the Alpha hardfork — neither depends on hardfork activation — and can merge in any order vs this PR:

Merge-order coordination: any of the three can land first. The other two then rebase upstream/main; git's --empty=drop auto-handles the identical patch-id commits. No manual conflict resolution needed.

Commits (23)

Group Commit
Setup / refactor 02c4d892 refactor: dedupe SYSTEM_CALLER + add gating helpers
1cbc03f5 refactor: hoist SYSTEM_CALLER read into ParallelExecutor::basic
03c37d31 refactor: hoist is_system_tx_gas_exempt helper to reth-chainspec
3411118c refactor: use is_gravity_system_caller helper at sender comparison sites
Core feature 0d50df60 feat: gas-exempt system transactions behind Alpha fork (L1+L2)
7acf27ef feat: zero SYSTEM_CALLER balance at Alpha activation (apply_state_change)
ee261914 feat(rpc): per-tx gas-exempt cfg for block-family trace endpoints
274507c5 feat(rpc): per-tx gas-exempt cfg for single-tx trace endpoints
Polish 64c41f38 refactor: drop fused_inspector machinery from GravityTracingCtx
d1107e64 perf(rpc): peek first prelude tx to seed replay EVM with matching cfg
f8487835 docs: clean up internal anchor references and stale doc paths
Tests — core invariants 31a9e5f4 test: bundle equivalence + migration hook edge cases (load-bearing)
86a068a9 test(nice): fee-zero / balance-untouched + disable_balance_check defensive verify
96eed10f test(grep): long-term regression prevention checklist
Tests — RPC 4d86493b test(e2e): Alpha-activation block + dual-backend system-tx gas-exempt parity
7826cffa test(rpc): pre-Alpha block replay regression (no false exemption)
8d73fcbf test(rpc): pure-simulation endpoints reject SYSTEM_CALLER spoofing
348c5c05 test(rpc): block-family + single-tx trace endpoints post-Alpha consistency
339c0904 test(rpc): tighten post-Alpha trace endpoints to byte-equal canonical (HP-1)
CI hygiene f0afa12f refactor(ci): move grep invariants from Rust test to bash script + CI step
ed12dedb fix(ci): backtick SYSTEM_CALLER in is_system_tx_gas_exempt doc
97728ad7 fix(ci): drop tx.clone() in Trace::trace_block_with replay loop
Invariant pin b8fe95a7 test(rpc): pin system-txs-at-block-head invariant for trace replay loops

Test plan

  • Bundle byte-equivalence between serial (WrapExecutor) and grevm (GrevmExecutor) on transact_system_txn (crates/ethereum/evm/src/parallel_execute.rs U-6) — load-bearing serial==grevm invariant.
  • Migration hook edge cases (crates/pipe-exec-layer-ext-v2/execute/src/system_caller_migration.rs#tests): activation-block balance zeroing + nonce/code preserve + EIP-161 non-pruning + idempotency + pre/post-activation no-op + degenerate fixture handling.
  • Fee-zero / balance-untouched (U-7) + gas_used equivalent pre/post-fork (U-7b) + disable_balance_check zero collateral (U-8).
  • E2E: Alpha-activation block T-1 / T / T+1 behavior under both backends, plus dual-backend state-root parity across the boundary (gravity_system_tx_gas_exempt_test.rs).
  • RPC replay: pre-Alpha block trace endpoints behave identically pre/post change (gravity_system_tx_pre_alpha_replay_test.rs).
  • RPC anti-spoof: simulation endpoints reject from=SYSTEM_CALLER free-gas attempt (gravity_system_tx_simulation_anti_spoof_test.rs).
  • RPC post-Alpha trace consistency: block-family + single-tx trace endpoints byte-equal canonical execution for blocks containing system txs (gravity_system_tx_post_alpha_trace_test.rs).
  • Invariant pin — system_txs_form_head_prefix: 7 unit tests in crates/chainspec/src/gravity.rs#tests covering empty / user-only / system-only / normal / epoch-switch shapes + two violation shapes (system-after-user / user-first-then-system).
  • Long-term regression grep checklist (scripts/check-gravity-invariants.sh, wired into the integration workflow): system-tx callsites count, SYSTEM_CALLER address single-definition, RPC historical-state gating, execute_history_block caller audit, single-predicate referenced by all layers.

Compatibility notes

  • Pre-Alpha blocks: byte-identical behavior — gas-exempt gating returns false for block_ts < alphaTime. RPC replay regression test pins this.
  • SYSTEM_CALLER balance reads: searched, no in-protocol consumer; off-chain consumers (indexers, supply-stats) reading this address should drop any special-case once Alpha activates.
  • Off-chain implications: post-Alpha system txs continue to appear in block bodies with gas_used > 0 but receipt.gas_price = 0 and no balance delta — explorers may want to label these distinctly.

Not in this PR (deferred follow-ups)

  • A grevm-upstream parallel_apply_transitions_and_create_reverts accounting fix (reverts_size is never updated, while revm serial updates both state_size and reverts_size). Discovered by U-6 bundle equivalence test; non-consensus (state root unaffected, only BundleState::size_hint() memory hint differs). Patch + cover letter prepared locally and to be sent upstream to Galxe/grevm separately. Bumping the grevm pin once landed will tighten U-6 from "structural equality minus size_hint" to full byte equality.

Branch

Built on top of upstream/main 6ea2ba328c (PR #366, randomness-by-height precompile gas guard). Backup of pre-shrink tip at feat/system-tx-gas-exempt-pre-shrink locally (29 commits, included BLS+R9 trio + their CI hygiene fixes — now extracted into #370 / #371).

@nekomoto911 nekomoto911 added enhancement New feature or request hardfork labels Jun 26, 2026
nekomoto911 added a commit to nekomoto911/gravity-reth that referenced this pull request Jun 27, 2026
… step

Why

`crates/ethereum/evm/tests/grep_checklist.rs` shelled out to `rg` from
`#[test]` functions via `Command::new("rg")`. This is an
anti-pattern in two ways:

  1. The invariants are lints, not unit tests — they don't exercise
     compiled code, they only check that source patterns hold. Living
     under `cargo test` makes them look like they verify behavior
     when they verify policy.
  2. ubuntu-latest CI runners don't ship ripgrep, so every
     `#[test]` in grep_checklist.rs panicked in `Command::new("rg")
     .output().expect(...)` with NotFound. This blocked the `test /
     ethereum` job on PR Galxe#367, while the underlying system-tx
     gas-exempt feature itself was correct.

Replace with `scripts/check-gravity-invariants.sh` and run it from the
`gravity-pipe-test` CI job (which already passes; ~5s ripgrep apt
install is acceptable on one job, not the matrix). Each invariant
prints OK on success or fails with a paste-able `rg` command on
stderr so a contributor can immediately reproduce locally.

Invariant count

8 total (6 carried over from the old grep_checklist.rs + 2 HP-2
additions that were missing):

  1. fn transact_system_txn lives in exactly 2 source files
     (serial + grevm impls).
  2. SYSTEM_CALLER address literal 625f0000 has a single source of
     truth in chainspec/src/gravity.rs (+ tests).
  3. trace.rs RPC replay uses historical state.
  4. execute_history_block has no non-test callers.
  5. Pipe + RPC custom precompile registration in sync.
  6. is_system_tx_gas_exempt predicate is wired in all three layers.
  7. (HP-2 new) disable_base_fee / disable_balance_check writes are
     either fork-gated (file co-references is_system_tx_gas_exempt /
     is_gravity_system_caller / GravityHardfork::Alpha /
     SYSTEM_CALLER) or in approved buckets (test files; the pure-
     simulation endpoint estimate.rs, which legitimately sets
     disable_base_fee unconditionally — gas-exempt-feature N/A
     because it doesn't replay user-signed txs).
  8. (HP-2 new) RPC replay paths
     (replay_transactions_until / trace_block_until_with_inspector)
     per-tx sender-check by referencing is_system_tx_gas_exempt or
     is_gravity_system_caller in the same file. Catches a future PR
     that adds a replay caller without wiring the SYSTEM_CALLER
     check.

Both HP-2 invariants were called out as missing in the design's
acceptance-tests §5 (Galxe#3 / Galxe#4); the original grep_checklist replaced
them with `is_system_tx_gas_exempt_referenced_by_all_three_layers`
which is strictly weaker (it checks the predicate is wired at
crate scope; the HP-2 invariants check fork-gate and replay-path
parity at file scope).

CI step placement

`gravity-pipe-test` job in `.github/workflows/integration.yml`
(already runs ubuntu-latest, already touches gravity-specific
e2e, already passes). The `test / ethereum` matrix job is
deliberately NOT extended — keeping the apt-get install on one job
saves CI time, and the matrix job's scope (full cargo nextest) is
orthogonal to a static-source lint.

Verification

  bash scripts/check-gravity-invariants.sh
  # All 8 invariants pass on HEAD as of this commit.
@nekomoto911
nekomoto911 force-pushed the feat/system-tx-gas-exempt branch from d95f237 to b8fe95a Compare June 30, 2026 10:19
nekomoto911 added a commit to nekomoto911/gravity-reth that referenced this pull request Jun 30, 2026
… step

Why

`crates/ethereum/evm/tests/grep_checklist.rs` shelled out to `rg` from
`#[test]` functions via `Command::new("rg")`. This is an
anti-pattern in two ways:

  1. The invariants are lints, not unit tests — they don't exercise
     compiled code, they only check that source patterns hold. Living
     under `cargo test` makes them look like they verify behavior
     when they verify policy.
  2. ubuntu-latest CI runners don't ship ripgrep, so every
     `#[test]` in grep_checklist.rs panicked in `Command::new("rg")
     .output().expect(...)` with NotFound. This blocked the `test /
     ethereum` job on PR Galxe#367, while the underlying system-tx
     gas-exempt feature itself was correct.

Replace with `scripts/check-gravity-invariants.sh` and run it from the
`gravity-pipe-test` CI job (which already passes; ~5s ripgrep apt
install is acceptable on one job, not the matrix). Each invariant
prints OK on success or fails with a paste-able `rg` command on
stderr so a contributor can immediately reproduce locally.

Invariant count

8 total (6 carried over from the old grep_checklist.rs + 2 HP-2
additions that were missing):

  1. fn transact_system_txn lives in exactly 2 source files
     (serial + grevm impls).
  2. SYSTEM_CALLER address literal 625f0000 has a single source of
     truth in chainspec/src/gravity.rs (+ tests).
  3. trace.rs RPC replay uses historical state.
  4. execute_history_block has no non-test callers.
  5. Pipe + RPC custom precompile registration in sync.
  6. is_system_tx_gas_exempt predicate is wired in all three layers.
  7. (HP-2 new) disable_base_fee / disable_balance_check writes are
     either fork-gated (file co-references is_system_tx_gas_exempt /
     is_gravity_system_caller / GravityHardfork::Alpha /
     SYSTEM_CALLER) or in approved buckets (test files; the pure-
     simulation endpoint estimate.rs, which legitimately sets
     disable_base_fee unconditionally — gas-exempt-feature N/A
     because it doesn't replay user-signed txs).
  8. (HP-2 new) RPC replay paths
     (replay_transactions_until / trace_block_until_with_inspector)
     per-tx sender-check by referencing is_system_tx_gas_exempt or
     is_gravity_system_caller in the same file. Catches a future PR
     that adds a replay caller without wiring the SYSTEM_CALLER
     check.

Both HP-2 invariants were called out as missing in the design's
acceptance-tests §5 (Galxe#3 / Galxe#4); the original grep_checklist replaced
them with `is_system_tx_gas_exempt_referenced_by_all_three_layers`
which is strictly weaker (it checks the predicate is wired at
crate scope; the HP-2 invariants check fork-gate and replay-path
parity at file scope).

CI step placement

`gravity-pipe-test` job in `.github/workflows/integration.yml`
(already runs ubuntu-latest, already touches gravity-specific
e2e, already passes). The `test / ethereum` matrix job is
deliberately NOT extended — keeping the apt-get install on one job
saves CI time, and the matrix job's scope (full cargo nextest) is
orthogonal to a static-source lint.

Verification

  bash scripts/check-gravity-invariants.sh
  # All 8 invariants pass on HEAD as of this commit.

@Richard1048576 Richard1048576 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. This cleanly addresses the earlier scope feedback — the BLS pop-verify RPC fix and the R9 provider-equivalence test are split out into #370 and #371, leaving this PR focused on the Alpha hardfork itself.

Checked that this PR stands on its own: no compile dependency on #370 (the remaining bls mentions here are test-harness comments, not symbols), and the hardfork-specific tests are all retained — gravity_system_tx_gas_exempt / pre_alpha_replay / simulation_anti_spoof / post_alpha_trace.

Design and behavior were verified end-to-end earlier:

  • Gas-exempt dual lever — cfg-side disable_base_fee (plus defensive disable_balance_check) with construction-side gas_price = 0, both gated on Alpha; disable_nonce_check correctly left false.
  • One-shot SYSTEM_CALLER → 0 at activation via the apply_state_change boundary (EIP-2935-style), preserving nonce/code.
  • RPC replay parity for the trace/debug endpoints.

Ran a 3-validator real-node A/B across the Alpha boundary: SYSTEM_CALLER zeroes exactly at the activation block, stays 0 with its nonce still incrementing post-Alpha, unrelated account state is byte-identical across the boundary, and debug_traceBlockByNumber on a post-Alpha system-tx block replays without an insufficient funds failure. Not retroactive on mainnet — alphaTime is unset in the deployed genesis, so this is a clean future activation. LGTM.

…g helpers

Collapse the two independent declarations of the SYSTEM_CALLER address
literal (`0x…625f0000`) — one in `pipe-exec-layer-ext-v2/.../onchain_config/mod.rs`
and one in `rpc-eth-api/src/helpers/transaction.rs` — into a single source
of truth in `reth_chainspec::gravity`. Both consumers re-export / re-import
from the new location; downstream call sites continue to use the same
`SYSTEM_CALLER` path so behavior is unchanged.

Introduce two helpers used by upcoming gas-exempt work (system-tx
gas-exempt design §3.4):
  - `reth_chainspec::is_gravity_system_caller(addr)` — defensive check.
  - `reth_evm_ethereum::is_system_tx_gas_exempt(chain_spec, block_ts)`
    — single predicate for the Alpha-active gate, shared between serial
    executor, grevm executor, pipe-layer system-tx construction and all
    RPC trace-replay paths.

Pure refactor: no behavior change, no new gating yet (helper is added
but unused — wired up in the next commits).
Wire the dual-lever gas-exempt design from system-tx gas-exempt §2.1 / §3.1 / §3.2:

L1 (cfg-side) — `EthEvmConfig::transact_system_txn` (serial) and
`GrevmExecutor::transact_system_txn` (grevm) both set
`cfg_env.disable_base_fee = true` + `cfg_env.disable_balance_check = true`
when the block timestamp is at/after Gravity Alpha activation. The two
edits are byte-identical by construction; any drift here forks state
root on system-tx blocks (PR Galxe#363 教训).

L2 (construction-side) — `execute_system_transactions` in
`pipe-exec-layer-ext-v2` now picks `gas_price = 0` for both the metadata
txn and the validator (DKG/JWK) txns when the Alpha gate is active,
keeping pre-Alpha bytes identical (still `base_fee as u128`).

`disable_nonce_check` is left `false` everywhere — SYSTEM_CALLER's nonce
sequence is part of the protocol contract.

Enables `optional_balance_check` and `optional_no_base_fee` revm features
in `reth-evm-ethereum` so the two cfg fields are accessible from the
execution layer.

Single Alpha-active predicate `is_system_tx_gas_exempt` (added in the
previous commit) routes all three callsites through one query — the
serial / grevm / pipe-layer paths share one gate.
…nge)

Add `system_caller_migration::apply_state_changes_for_block` next to the
existing EIP-2935 hook in `execute_ordered_block`. On the Alpha activation
block (gated by `transitions_at_timestamp(current_ts, parent_ts)`) the
helper:

  - reads the pre-execution SYSTEM_CALLER `AccountInfo` (now captured
    alongside the existing `initial_nonce` read so we don't pay the cost
    twice and don't need a new `ParallelExecutor::basic_ref` trait method),
  - constructs an `EvmState` diff with `balance = U256::ZERO`, **nonce
    preserved**, **code / code_hash preserved**, `status = Touched`,
  - routes the diff through `executor.apply_state_change`, which has
    symmetric serial / grevm impls, so the migration is byte-identical on
    both backends with no PR Galxe#363-class divergence risk.

The diff lands BEFORE system transactions in the same block, so the
post-execution bundle reflects the zeroed balance and the system tx
itself runs against the migrated account. With `nonce > 0` post-migration,
EIP-161 `is_empty` stays false and state-clear never prunes the account.

No effect on pre-Alpha blocks (no-op when the fork doesn't transition).
Wire the Gravity Alpha gas-exempt design through the RPC block-family
replay paths so post-Alpha trace endpoints match canonical execution
byte-for-byte once the SYSTEM_CALLER balance has been zeroed.

trace.rs (`trace_block_until_with_inspector`):
  Replace `TxTracer::try_trace_many` with a handwritten loop so we can
  flip `cfg_env.disable_base_fee` / `disable_balance_check` on the EVM at
  the system-tx → user-tx boundary (sender-keyed classification).
  Sketch A1 from the route doc — system txns are positionally pinned at
  the front of the block by `metadata_txn.rs:120/:185`, so the typical
  block requires exactly one EVM rebuild via `Evm::finish` + re-build.
  The fork gate keys off the replayed block's timestamp, so pre-Alpha
  blocks under archive replay still go through unmodified canonical cfg.

  Introduce a local `GravityTracingCtx<'_, T, E>` that mirrors
  `alloy_evm::tracing::TracingCtx`'s 5 pub fields, exposes the previously
  crate-private `fused_inspector` / `was_fused` as pub so the
  handwritten loop can construct it, and reproduces `take_inspector`
  byte-for-byte. The 4 trait fns' F bound is updated; the 6 external
  callers' closure bodies need no change (param type is inferred). See
  R-A2 verify §2 for the (a) vs (b) decision.

  Manual fuse / `skip_last_commit` semantics reproduce
  `TxTracer::try_trace_many`'s defaults: every tx except the final one
  commits its state, and the inspector resets to the fused snapshot
  unless the hook calls `take_inspector`.

debug.rs (`debug_traceBlock*`):
  Already per-tx clones `evm_env` for `trace_transaction`. We pre-compute
  `exempt_fork_active` once, then on each per-tx clone toggle the two
  disables iff the tx's recovered sender == `SYSTEM_CALLER`. No
  structural change.

Cargo.toml: enable the `optional_balance_check` revm feature in
`reth-rpc-eth-api` so `disable_balance_check` is reachable.

R-A1 PASS — `TracingInspector` is per-tx reset by design (revm-inspectors
`mod.rs:244-250`), so the inspector discarded at `finish()` is already in
a post-fuse state semantically equivalent to a fresh `inspector_setup()`
call (verify trail §1).
Round out the system-tx gas-exempt RPC coverage for the single-tx family
endpoints: `debug_traceTransaction` / `trace_transaction` /
`trace_replayTransaction` / `trace_get`. Together with the previous
commit's block-family work, this closes the "post-Alpha replay diverges
from canonical" gap end-to-end.

`replay_transactions_until` (single EVM, sequential commit of pre-target
txs): track a `current_kind_system_exempt` toggle; when the kind flips
between consecutive txs, `Evm::finish` returns `(db, env)`, we flip the
two cfg disables on the env, and re-build the EVM via `evm_with_env` +
`register_custom_precompiles`. The fork gate keys off the replayed
block's timestamp (same predicate as the canonical execution layer and
the block-family path in `trace.rs`). With system txs pinned at the
front of the block, the typical case is at most one rebuild.

`spawn_trace_transaction_in_block_with_inspector` (the wrapper used by
both `debug_traceTransaction` and `trace_transaction`): if the *target*
tx's recovered sender is SYSTEM_CALLER, clone `evm_env` and toggle the
disables before passing it to `inspect`. `inspect` itself is unchanged
— the brief explicitly chose the caller-side patch.
Previously the Alpha migration hook took the previously-read AccountInfo
as an `Option<AccountInfo>` parameter, with the read performed at the
pipe-layer call site before `state` is moved into the executor. That
leaked the hook's data-needs into the call site.

Add a `basic(&mut self, addr) -> Result<Option<AccountInfo>, _>` accessor
to `Executor` / `ParallelExecutor` so the migration hook reads
SYSTEM_CALLER itself. Drop the `prev_account` parameter, simplify the
pipe-layer call to just an `executor.basic(...)`-free invocation.

Backends:
  - `BasicBlockExecutor` forwards to `RevmDatabase::basic(&mut self.db, ...)`.
  - `GrevmExecutor` forwards to its `ParallelState::basic`.
  - `Either<A, B>` forwards via match.
  - Test stub in `execute.rs` impls `unreachable!()`.

Symmetric with `apply_state_change`: read + write irregular state both
go through the trait now, so future hooks (e.g. additional fork-boundary
migrations) don't need bespoke pre-read scaffolding at the call site.

Pipe layer still pre-reads SYSTEM_CALLER nonce before `state` is moved —
that read serves `execute_system_transactions` nonce sequencing and is
unrelated to the migration.
…ases

Implements acceptance-tests-2026-06-26 §1.1 + §1.4 (must-pass tier):

1. crates/ethereum/evm/src/parallel_execute.rs (§1.1 — "承重墙" U-6):
   - `u6_test_system_tx_gas_exempt_bundle_equivalence` drives the same
     two-system-tx sequence (metadata + validator) through
     `WrapExecutor::transact_system_txn` (serial) and
     `GrevmExecutor::transact_system_txn` (grevm) under an Alpha-active
     chainspec. Asserts byte-equality on the state-root-bearing bundle
     fields (`state`, `contracts`, `state_size`, `reverts`). Any drift
     between the two backends would fork state root on system-tx blocks
     (PR Galxe#363-class regression).
   - Documents a known non-load-bearing discrepancy: grevm's
     `parallel_apply_transitions_and_create_reverts` updates `state_size`
     but not `reverts_size`. The `reverts` content itself is identical;
     only the size-hint accounting differs, so state root is unaffected.
     Flagged in-comment for grevm-side follow-up.

2. crates/pipe-exec-layer-ext-v2/execute/src/system_caller_migration.rs
   (§1.4 — migration hook edge cases):
   - `test_migration_at_activation_block_zeros_balance_preserves_rest`
   - `test_migration_idempotent_on_reexecution`
   - `test_migration_no_op_on_post_activation_blocks`
   - `test_migration_no_op_on_pre_activation_blocks`
   - `test_migration_account_not_pruned_by_eip161`
   - `test_migration_defensive_when_system_caller_absent`
   - `test_system_caller_address_literal_matches_canonical`

   These pin: balance zeroing + nonce/code preservation on the activation
   block; gate behavior on T-1 / T / T+1 boundaries; EIP-161 non-pruning
   via nonce>0; defensive handling of an absent pre-state SYSTEM_CALLER;
   and the SYSTEM_CALLER literal sanity (defends §6.1 grep Galxe#2). Tests
   live in the same crate as the `pub(crate)` hook so the function under
   test is directly callable.

All 13 new tests pass; no existing tests were modified.

Note: §1.4's matrix placement (`system_caller_migration.rs#tests`) is
preferred over the parent task's "parallel_execute.rs" placement because
the hook is `pub(crate)` to pipe-exec-layer-ext-v2 and cannot be called
from `reth-evm-ethereum` without a cyclic dependency.
…ive verify

Implements acceptance-tests-2026-06-26 §1.2 + §1.3 (nice-to-have tier):

- u7_test_system_tx_fee_zero_balance_unchanged (§1.2)
  Verifies that under the Alpha gate, SYSTEM_CALLER's balance is preserved
  bit-for-bit across a system tx (no fee debit) and the coinbase does NOT
  receive any tip (gas_price == 0). Also asserts gas_used stays positive,
  so the lever changes accounting only — not gas metering.

- u7b_test_system_tx_gas_used_equivalent_to_pre_fork (§1.2)
  Runs the same system tx through pre-Alpha (gas_price = base_fee,
  funded SYSTEM_CALLER) and post-Alpha (gas_price = 0, zeroed
  SYSTEM_CALLER) paths and asserts gas_used is identical. Locks the L1+L2
  levers as fee-only mutations.

- u8_test_disable_balance_check_zero_collateral (§1.3)
  Asserts that under Alpha-active cfg, dropping `disable_balance_check`
  while keeping `disable_base_fee` produces a byte-identical bundle —
  proving the second flag is currently a no-op defence layer (R5 verify).
  Protects against future revm prepay-path changes that might make
  `disable_balance_check` consequential without explicit re-gating.

All 3 new tests pass; runs alongside the existing U-1..U-7 / U-6 suite.
Implements acceptance-tests-2026-06-26 §5 / design §6.1 as 6 shell-out
`rg` invariants that run inside `cargo test` so they wire into the same
CI lane as the rest of the test suite.

Tests added in crates/ethereum/evm/tests/grep_checklist.rs:

  1. grep_transact_system_txn_two_callsites_only
     Pins that `fn transact_system_txn` lives in exactly two files —
     serial (lib.rs) and grevm (parallel_execute.rs). A third occurrence
     means somebody added a system-tx execution path that bypasses the
     shared `is_system_tx_gas_exempt` gate.

  2. grep_system_caller_address_literal_single_source
     Pins that the `625f0000` literal only appears in the canonical
     definition (`chainspec/src/gravity.rs`) and approved test files.
     Redeclarations are the "address-literal drift" regression from
     design §2.5.

  3. grep_rpc_replay_uses_historical_state
     Pins that `trace.rs` calls `state_at_block_id` / `parent_hash()`.
     If replay pivots to `latest` / node tip, the gate논증 from
     design §3.5.2 collapses and pre-Alpha blocks could silently get
     gas-exempt treatment.

  4. grep_execute_history_block_no_non_test_callers
     Pins that `push_history_block` / `execute_history_block` callers
     are restricted to pipe-exec-layer src/lib.rs + tests. A non-test
     caller would upgrade R6 to consensus-critical (design §3.6).

  5. grep_pipe_rpc_precompile_registration_in_sync
     Pins that both pipe layer (`custom_precompiles_for_ordered_block`)
     and RPC layer (`register_custom_precompiles`) register custom
     precompiles. If either disappears, sketch A1's EVM-rebuild path
     loses BLS / randomness on replay.

  6. grep_is_system_tx_gas_exempt_referenced_by_all_three_layers
     Pins that the shared predicate is referenced from evm, pipe, AND
     rpc crates — defends the "single source of truth" claim from
     design §3.4.

All 6 pass against the current branch state. Helper `rg_lines` shells
out to `rg --type rust` from the workspace root resolved via
`CARGO_MANIFEST_DIR` (no env var assumptions).
The Gravity-Alpha gas-exempt predicate previously lived in
`reth-evm-ethereum::is_system_tx_gas_exempt`, but `reth-rpc-eth-api`
does not depend on `reth-evm-ethereum` — so the five RPC replay sites
(block-family + single-tx-target loops in `helpers/trace.rs`,
`replay_transactions_until` in `helpers/call.rs`, `debug_traceBlock*`
per-tx loop in `rpc/src/debug.rs`, and `register_custom_precompiles`
in `rpc/src/eth/helpers/call.rs`) all reimplemented the gate inline:

    chain_spec.gravity_hardforks().is_fork_active_at_timestamp(
        GravityHardfork::Alpha, block_ts,
    )

That violates the "single predicate" promise from the system-tx
gas-exempt design (§3.4) and defeats the long-term grep checklist —
any tweak to the Alpha gate semantics (e.g. moving to a timestamp+block
dual-gate) would have to be hand-replayed across six call sites.

Move the helper to `reth-chainspec::gravity` next to `SYSTEM_CALLER` /
`is_gravity_system_caller`. Every consumer crate (reth-evm-ethereum
serial + grevm twins, reth-pipe-exec-layer-ext-v2 system-tx pricing,
both RPC crates) already depends on `reth-chainspec`, so the predicate
is now reachable everywhere without extra crate edges. `reth-evm-ethereum`
re-exports the helper for API stability.

Touches HP-1 from the system-tx gas-exempt code review:
- crates/chainspec/src/gravity.rs   — new `is_system_tx_gas_exempt`.
- crates/chainspec/src/lib.rs        — re-export.
- crates/ethereum/evm/src/lib.rs    — drop local def; re-export from chainspec.
- crates/pipe-exec-layer-ext-v2/.../lib.rs — switch to chainspec path.
- crates/rpc/rpc-eth-api/src/helpers/{trace,call}.rs — drop inline gate.
- crates/rpc/rpc/src/debug.rs                       — drop inline gate.
- crates/rpc/rpc/src/eth/helpers/call.rs            — drop inline gate.
`reth_chainspec::is_gravity_system_caller(addr)` was introduced in the
SYSTEM_CALLER address-collapsing commit (1c52fa8) precisely to give
"is this the protocol-injected sender?" check a named identity — so a
future address-shape change (or extending the gate to a set of system
addresses) has a single migration point. But the gas-exempt RPC wiring
that followed it never called the helper; five sites all open-coded
`tx.signer() == SYSTEM_CALLER` instead, leaving the helper defined-but-
never-invoked — a half-finished state flagged as HP-2 in the system-tx
gas-exempt code review.

Wire the helper through the five remaining sender checks so the helper
has actual consumers and the design intent from §2.5 ("地址字面量收口
+ 共享 helper") is realized:
- crates/rpc/rpc-eth-api/src/helpers/trace.rs — single-tx target,
  block-family `first_kind_system_exempt`, block-family per-tx
  classification.
- crates/rpc/rpc-eth-api/src/helpers/call.rs — `replay_transactions_until`
  per-tx kind detection.
- crates/rpc/rpc/src/debug.rs — `debug_traceBlock*` per-tx classification.

`SYSTEM_CALLER` is dropped from the imports of each touched file (only
comparison sites used it); pipe-layer and EVM-internal sites that still
construct from / look up by `SYSTEM_CALLER` keep importing it directly.
… verify)

The handwritten block-family tracing loop in `trace_block_until_with_inspector`
previously mirrored alloy-evm's `TxTracer` machinery byte-for-byte: it carried
a `fused_inspector` snapshot across iterations and used `was_fused` bookkeeping
to skip a conditional fuse step when the callback had `take_inspector`-ed.

R-A1 verify (§1.5) already proved this is unnecessary for sketch A1:
- `TracingInspector` is designed for per-tx reset (revm-inspectors:src/tracing/mod.rs:244-250
  documents it explicitly, `fuse()` clears every per-tx field).
- The EVM rebuild at every system→user-segment transition already calls
  `inspector_setup()` to seed a fresh inspector — equivalent in initial state
  to `fused_inspector.clone()`.
- No closure of the 6 existing callers (trace.rs's 5 + otterscan.rs's 1) reads
  either `ctx.fused_inspector` or `ctx.was_fused` directly; both were purely
  shape-aligned mirrors of alloy-evm's private fields used only by
  `take_inspector` and the surrounding loop.

This commit drops both fields from `GravityTracingCtx` (struct shrinks to the
5 pub fields `tx / result / state / inspector / db`), rewrites
`take_inspector` to `core::mem::take(self.inspector)` (needs only
`E::Inspector: Default`, which all callers' Insp types — `TracingInspector`,
`OpcodeGasInspector`, `StorageInspector` — satisfy), and unconditionally
re-seeds the inspector slot via `inspector_setup()` after every tx.

The trait F bound changes from `Insp: Clone + InspectorFor<…>` to
`Insp: Default + InspectorFor<…>` on both `trace_block_until_with_inspector`
and `trace_block_inspector`.

No callsite changes: otterscan and trace.rs continue to call
`ctx.take_inspector()` and `ctx.inspector.*` unchanged.
`replay_transactions_until` used to always build the initial EVM with
`current_kind_system_exempt = false` (disables OFF) and rely on the in-loop
transition to rebuild via `finish()` if the first replay tx happened to be a
SYSTEM_CALLER. For Alpha-active blocks the prelude almost always leads with
SYSTEM_CALLER (metadata + validator txs are pinned to the front of the block
by the pipe layer at `metadata_txn.rs:120/:185`), so the typical
`debug_trace*` / `trace_*` single-tx replay against a user tx in the tail
paid for one wasted `finish()` + `evm_with_env` + `register_custom_precompiles`
on entry.

Mirror the block-family path in `trace.rs:422-437` (`first_kind_system_exempt`):
peek the iterator, classify the first tx's sender, and pre-toggle the initial
cfg disables. The kind-transition rebuild inside the loop still fires when a
later tx flips kind (e.g. system→user at the boundary).

`register_custom_precompiles` is still invoked exactly once after every EVM
construction — once on the initial build and once on every transition rebuild.
No call is dropped; we just save one rebuild per replay when the prelude leads
with a system tx (the common case).

Aligns code style with the block-family loop and closes review MP-4.
… parity

Adds acceptance-matrix §2.1 (fork-activation block) + §2.2 (dual-backend
state-root equivalence) as a new pipe-layer integration test
`gravity_system_tx_gas_exempt_test.rs`.

§2.1 (`test_e2e_system_tx_alpha_activation_{grevm,disable_grevm}`):
- Boots a single reth node with `gravity_hardfork.json` patched so
  `alphaTime = ALPHA_TS_BASE + 100` aligns Alpha activation with block 100.
- Drives MockConsensus through blocks 1..=105 and asserts:
  * T-1 (block 99): SYSTEM_CALLER.balance still sentinel-sized (> 10^50),
    nonce == 99 (one metadata system tx per pre-Alpha block).
  * T (block 100): balance == 0 (migration hook fired), nonce preserved
    + incremented to 100.
  * T+1..T+5: balance stays 0, nonce monotonically increases — proves the
    L1 cfg lever + L2 `gas_price=0` keep working past the activation block.
- Runs under both grevm (default) and `--gravity.disable-grevm` (serial
  `WrapExecutor`) so a single-backend regression is caught immediately.

§2.2 (`test_e2e_dual_backend_state_root_equivalence_across_alpha_boundary`):
- Re-uses the same activation runner under both backends with isolated
  datadirs and asserts the per-block state-root maps match byte-for-byte
  across {T-1, T, T+1..T+5}. The pre-Alpha entry guards against baseline
  drift (anything that diverges before the fork is a pre-existing serial
  vs grevm bug, not the Alpha bundle).

Scaffolding mirrors `gravity_eip2935_test.rs` / `gravity_bls_precompile_test.rs`:
- `gravity_alpha_chainspec(ts)` patches `alphaTime` on the embedded JSON.
- `MockConsensus + push_empty_range` reuses the established pattern.
- The shared `run_pipe_e2e_test` harness drives `CliRunner` with the
  selected backend flag.
Adds acceptance-matrix §3.3 — pre-Alpha RPC replay regression coverage as
`gravity_system_tx_pre_alpha_replay_test.rs`. Pins the load-bearing "gate is
keyed on the replayed block timestamp" invariant: for any block whose
timestamp predates `alphaTime`, the RPC replay path (trace_block /
debug_traceBlock / trace_transaction / debug_traceTransaction) must NOT
activate the cfg-side `disable_base_fee` / `disable_balance_check` levers.
The system tx must traverse the standard fee path and pay
`gas_used × base_fee` out of SYSTEM_CALLER's sentinel-sized genesis alloc
balance.

Runner pushes 10 empty blocks with `alphaTime = 9_999_999_999` (never
fires), then exercises:

Phase 1 — block-family endpoints over every pre-Alpha block:
  - `trace_api.trace_block(BlockId::Number(n))` must return Ok(Some) with
    >= 1 trace per system tx.
  - `debug_api.debug_trace_block(...)` must return Ok with every entry as
    `TraceResult::Success` (no Err variants).
  - Trace count matches canonical receipt count to within +1 (reward trace
    appended for non-Alpha blocks per `extract_reward_traces`).

Phase 2 — single-tx family on a sampled block (Galxe#5):
  - `trace_api.trace_transaction(metadata_tx_hash)` must Ok(Some).
  - `debug_api.debug_trace_transaction(...)` must Ok; payload must not
    contain `InsufficientFunds` / `GasPriceLessThanBasefee` markers.

Phase 3 — gate verification:
  - For every pushed pre-Alpha block, assert
    `reth_chainspec::is_system_tx_gas_exempt(chain_spec, header.timestamp)`
    returns `false`. Guards against accidental gate-on-tip regressions
    (design §3.5.2).

Both grevm and `--gravity.disable-grevm` backends exercised independently.

Adds `alloy-rpc-types-trace` to dev-dependencies (for the `TraceResult` /
`GethDebugTracingOptions` enums the trace + debug APIs use as I/O).

Location note: §3.3 nominally targets `crates/rpc/rpc/tests/` but reth-rpc
has no tests directory and pulling in node-builder dev-deps purely for an
RPC mock would be invasive churn. The pipe-exec-layer harness already
exposes the full RPC registry (`handle.node.rpc_registry.trace_api()` /
`.debug_api()`) — same surface, smaller blast radius, consistent with
`gravity_eip2935_test.rs` exercising RPC paths from the same harness.
Adds acceptance-matrix §3.4 — pure-simulation anti-spoof regression as
`gravity_system_tx_simulation_anti_spoof_test.rs`. Pins the design §2.5
safety invariant: the gas-exempt exemption MUST be keyed on the recovered
sender of a persisted system tx (reachable only via
`RecoveredBlock::transactions_recovered()` against empty-signature
protocol-injected txs), NEVER on a user-supplied `from = SYSTEM_CALLER`
in a simulation `TransactionRequest`.

Strategy — each of 6 endpoints is invoked twice on identical post-Alpha
state:
  1. `from = SYSTEM_CALLER` (spoof attempt)
  2. `from = SPOOF_PROBE_ADDR` (fresh zero-balance counter-factual)

The runner asserts behavioral equivalence (both Ok or both Err) via the
`assert_results_equivalent` helper. Any divergence — spoof Ok while probe
Err — proves the exemption leaked through user input.

Endpoints exercised:
  - `eth_call`
  - `eth_estimateGas`
  - `eth_simulateV1`
  - `debug_traceCall`
  - `trace_call`
  - `trace_callMany`

Setup details:
  - `alphaTime = 1` so block 1 already activates Alpha; runner pushes
    10 empty blocks so SYSTEM_CALLER.balance = 0 (post-migration).
  - Sanity assertion verifies SYSTEM_CALLER.balance is 0 before exercising
    endpoints (test would be vacuous otherwise).
  - Both grevm and `--gravity.disable-grevm` backends exercised — spoof
    surfaces are codepath-independent.

`trace_rawTransaction` is omitted (it requires a signed raw payload, not a
TransactionRequest, so the from-spoof attack vector doesn't apply — the
sender is recovered from the signature).
…tency

Adds acceptance-matrix §3.1 (block family) + §3.2 (single-tx family) — the
load-bearing claim of the PR — as `gravity_system_tx_post_alpha_trace_test.rs`.
Once Alpha is active and SYSTEM_CALLER.balance == 0, the per-tx gas-exempt
levers (cfg-side `disable_base_fee` + `disable_balance_check`) must let
the RPC replay path reproduce canonical execution without failing on
`GasPriceLessThanBasefee` / `InsufficientFunds`.

§3.1 block-family — exercised on every post-Alpha block (1..=10):
  - `trace_api.trace_block`
  - `debug_api.debug_trace_block`        (every entry asserted TraceResult::Success)
  - `trace_api.replay_block_transactions`
  - `trace_api.trace_block_opcode_gas`   (Reth extension)
  - `trace_api.trace_block_storage_access` (Reth extension)
  - `trace_api.trace_filter` over 1..=10
  Trace counts cross-validated against canonical receipts from the
  provider's `receipts_by_block`.

§3.2 single-tx family — target = metadata system tx at block 5:
  - `trace_api.trace_transaction`
  - `trace_api.replay_transaction`        (trace + state_diff types)
  - `trace_api.trace_get` index 0
  - `debug_api.debug_trace_transaction`   (payload must not contain
    `InsufficientFunds` / `GasPriceLessThanBasefee` markers)
  - `trace_api.trace_transaction_opcode_gas`

Sanity assertion at the start of the runner: SYSTEM_CALLER.balance == 0
post-migration — otherwise the gas-exempt levers wouldn't be load-bearing
and this test would devolve into a trivial happy-path check.

Both grevm and `--gravity.disable-grevm` backends exercised independently.

Coverage delta vs. spec:
  - `ots_getContractCreator` skipped — needs an actual contract-creating
    tx, which empty blocks do not produce.
  - "Target = user tx with system-tx prelude" sub-case of §3.2 NOT
    covered — injecting signed user txs into the OrderedBlock alongside
    the protocol-injected metadata system tx would expand scaffolding by
    ~250 LOC. The current test still pins the load-bearing "system-tx
    segment runs gas-exempt without fee errors" half of §3.2; the
    user-tx-with-prelude half is documented as a follow-up sibling test,
    best built once the EIP-7702 helper from
    `gravity_bls_precompile_test.rs` is generalised.

Location note: as with T4/T5, RPC-surface assertions live in the
pipe-exec-layer harness because reth-rpc has no `tests/` directory and
the harness already exposes the full RPC registry — see the location-note
section in `gravity_system_tx_pre_alpha_replay_test.rs`.
Review MP-1 + MP-2 cleanup:
- Drop "_local/drafts/..." path leaks from production code comments —
  external drafts can move or vanish; the comments should stand alone.
- Drop "§/R" / "verify §N" internal-index references that depend on
  external review/design docs to decode. Keep the substantive technical
  explanation; drop only the cryptic anchor.

No semantic / functional change.
… (HP-1)

Promotes the post-Alpha trace consistency harness from "format-shaped"
weak assertions to per-tx byte-equal canonical checks across every
block-family and single-tx trace endpoint, and pins the Feishu L122
invariant that SYSTEM_CALLER system txs must still meter gas (>0)
under the L1 cfg-side exemption.

What this commit pins

1. `canonical_baseline` (the single source of per-tx truth for every
   downstream assertion) is upgraded along three axes:
   - monotonic cumulative_gas_used is enforced with `checked_sub` +
     explicit panic instead of `saturating_sub` (which would silently
     mask a non-monotonic receipt — exactly the bug shape it's
     meant to catch);
   - every per-tx `gas_used` is unconditionally asserted `> 0` with
     the message "SYSTEM_CALLER tx must still meter gas (Feishu L122
     invariant)". This fixture is pure-system-tx, so the assertion
     is unconditional rather than `if sender == SYSTEM_CALLER`; a
     mixed fixture would gate it. This is the load-bearing
     `gas_used > 0` invariant that was missing across all helpers.
   - inline doc captures the two invariants so future helpers
     inherit them for free.

2. `trace_block_opcode_gas` per-tx assertion goes from `!is_empty()`
   to `sum > 0 && sum <= cn.gas_used`. Strict equality (`sum ==
   gas_used`) is intentionally NOT asserted: opcode-gas sum does not
   equal receipt `gas_used` byte-for-byte because the receipt
   includes the per-tx intrinsic cost (21_000 + calldata) and is net
   of refunds. So `sum < gas_used` is normal; only `sum > gas_used`
   is a real regression. The doc-comment captures this intrinsic-gap
   reasoning so a future reader doesn't tighten it into a false
   equality.

3. `debug_trace_block` tx_hash check goes from `if let Some` skip-on-
   None to strict `expect(...) + assert_eq!`. reth always populates
   tx_hash for persisted block entries; the previous conditional
   would have silently masked a canonical-drift bug if the trace
   ever started returning None.

4. `trace_filter` root-trace count goes from `>=` to `==`. Post-Paris
   has no reward traces and we already filter on
   `trace_address.is_empty()` so subcalls are excluded by
   construction; equality is the right invariant. Drift in either
   direction means trace_filter is duplicating or dropping root
   traces relative to receipts.

5. ALPHA_TIME_ALWAYS / parent-ts comment: `1687126994` → `1687223762`
   (the actual decimal value of `gravity_hardfork.json` genesis
   `0x6490fdd2`). The previous value was off by ~96k seconds; the
   logical conclusion was still correct, just the verification math
   in the comment was wrong.

Note on the opcode-sum-vs-gas-used gap

Receipt `gas_used` = intrinsic (21_000 + calldata zero/non-zero
bytes per EIP-2028) + sum(opcode costs) − refunds. Opcode-gas only
sums the middle term. For the metadata/validator system txs in
this fixture, intrinsic + calldata is typically a few hundred
thousand gas (large encoded payload), so opcode sum is materially
less than gas_used — and we'd be wrong to assert equality.

Otterscan `getContractCreator` is not covered

This fixture only produces protocol-injected metadata/validator
system txs (all `to = system contract`, no CREATE/CREATE2). The
otterscan endpoint is meaningful only for blocks containing
contract-deploying user txs; covering it would need a new fixture
mixing user CREATE txs into the OrderedBlock alongside the
system-tx prelude — out of scope for this commit.

What this commit does NOT touch

- nonce pre-seed on SYSTEM_CALLER (alloc nonce=1) is a fixture
  mirror of the production precondition that pre-Alpha system txs
  have already bumped SYSTEM_CALLER nonce before Alpha activates.
  No production semantics changed.
- The datadir persistence issue at L256 (`runner expects a fresh
  datadir`) is a T6-introduced pre-existing kink and is left for an
  independent follow-up commit.
- HP-2 (`grep_checklist.rs` portability + missing fork-gate
  invariants) is its own concern and is addressed in the follow-up
  commit on this branch.

Verification

- `cargo check --test gravity_system_tx_post_alpha_trace_test -p
  reth-pipe-exec-layer-ext-v2` clean.
- `cargo +nightly fmt --all` clean.
- Test execution itself relies on the same datadir flow as the
  prior commit; the assertion logic upgrades stand independently.
… step

Why

`crates/ethereum/evm/tests/grep_checklist.rs` shelled out to `rg` from
`#[test]` functions via `Command::new("rg")`. This is an
anti-pattern in two ways:

  1. The invariants are lints, not unit tests — they don't exercise
     compiled code, they only check that source patterns hold. Living
     under `cargo test` makes them look like they verify behavior
     when they verify policy.
  2. ubuntu-latest CI runners don't ship ripgrep, so every
     `#[test]` in grep_checklist.rs panicked in `Command::new("rg")
     .output().expect(...)` with NotFound. This blocked the `test /
     ethereum` job on PR Galxe#367, while the underlying system-tx
     gas-exempt feature itself was correct.

Replace with `scripts/check-gravity-invariants.sh` and run it from the
`gravity-pipe-test` CI job (which already passes; ~5s ripgrep apt
install is acceptable on one job, not the matrix). Each invariant
prints OK on success or fails with a paste-able `rg` command on
stderr so a contributor can immediately reproduce locally.

Invariant count

8 total (6 carried over from the old grep_checklist.rs + 2 HP-2
additions that were missing):

  1. fn transact_system_txn lives in exactly 2 source files
     (serial + grevm impls).
  2. SYSTEM_CALLER address literal 625f0000 has a single source of
     truth in chainspec/src/gravity.rs (+ tests).
  3. trace.rs RPC replay uses historical state.
  4. execute_history_block has no non-test callers.
  5. Pipe + RPC custom precompile registration in sync.
  6. is_system_tx_gas_exempt predicate is wired in all three layers.
  7. (HP-2 new) disable_base_fee / disable_balance_check writes are
     either fork-gated (file co-references is_system_tx_gas_exempt /
     is_gravity_system_caller / GravityHardfork::Alpha /
     SYSTEM_CALLER) or in approved buckets (test files; the pure-
     simulation endpoint estimate.rs, which legitimately sets
     disable_base_fee unconditionally — gas-exempt-feature N/A
     because it doesn't replay user-signed txs).
  8. (HP-2 new) RPC replay paths
     (replay_transactions_until / trace_block_until_with_inspector)
     per-tx sender-check by referencing is_system_tx_gas_exempt or
     is_gravity_system_caller in the same file. Catches a future PR
     that adds a replay caller without wiring the SYSTEM_CALLER
     check.

Both HP-2 invariants were called out as missing in the design's
acceptance-tests §5 (Galxe#3 / Galxe#4); the original grep_checklist replaced
them with `is_system_tx_gas_exempt_referenced_by_all_three_layers`
which is strictly weaker (it checks the predicate is wired at
crate scope; the HP-2 invariants check fork-gate and replay-path
parity at file scope).

CI step placement

`gravity-pipe-test` job in `.github/workflows/integration.yml`
(already runs ubuntu-latest, already touches gravity-specific
e2e, already passes). The `test / ethereum` matrix job is
deliberately NOT extended — keeping the apt-get install on one job
saves CI time, and the matrix job's scope (full cargo nextest) is
orthogonal to a static-source lint.

Verification

  bash scripts/check-gravity-invariants.sh
  # All 8 invariants pass on HEAD as of this commit.
clippy::doc-markdown flagged the bare `SYSTEM_CALLER` ident in the
doc-comment bullet of `crates/chainspec/src/gravity.rs:43`. Under
`-D warnings` (the lint job's RUSTFLAGS) this fails the workspace
clippy check.

Wrap as intra-doc link `[`SYSTEM_CALLER`]` (the constant lives in the
same module, so the link resolves) — matches the existing style on
line 30 (`[\`SYSTEM_CALLER\`]`).
`clippy::clone-on-copy` (`-D warnings`) flagged `tx.clone()` in the
`current_evm.transact(tx.clone())` site introduced by the per-tx
gas-exempt cfg refactor. `Recovered<&SignedTx>` is `Copy` (the inner type
is a reference), so `transact(tx)` auto-copies and `tx` remains usable
for the later `GravityTracingCtx { tx, .. }`.

No behavior change.
Both RPC replay paths added in this PR build a cfg-rebuild optimization on
the protocol invariant that SYSTEM_CALLER-signed transactions occupy a
contiguous head prefix of every block:

- block-family `trace_block_until_with_inspector` (`trace.rs`) — "at most
  one EVM rebuild on the system→user boundary" via peek-and-pre-toggle of
  the initial cfg + per-tx classification
- single-tx `replay_transactions_until` (`call.rs`) — same pattern for the
  pre-target replay prelude

The invariant is upheld upstream by the pipe layer (`metadata_txn.rs:120` /
`:185`, system txs inserted at positions 0..k of the block body) and by
the practical impossibility of forging a SYSTEM_CALLER signature. A
violation would silently degrade each loop's "at most one rebuild" claim
to a multi-rebuild slow path in release (still correct: the loops rebuild
unconditionally on kind transition) and almost certainly indicate a
pipe-layer regression worth catching loudly.

Three artifacts:

1. **`reth_chainspec::system_txs_form_head_prefix(senders) -> Result<(),
   usize>`** — unit-tested algorithm of record for the predicate. Lives
   next to `is_gravity_system_caller` / `SYSTEM_CALLER` since it operates
   purely on sender addresses; no extra crate edges.
2. **Inline `debug_assert!` in each replay loop** — tracks
   `saw_non_system_caller_tx: bool` per-tx; panics in debug builds with
   `(idx, block_number)` context if a SYSTEM_CALLER tx surfaces after a
   non-system tx. Release builds compile out the check and the bool.
3. **Seven unit tests** in `crates/chainspec/src/gravity.rs#tests`
   exercising all block shapes the user feedback called out:
   - empty iter (`empty_block_holds`)
   - user-only block (`user_only_block_holds`)
   - system-only block — epoch-transition shape with no user tail
     (`system_only_block_holds`)
   - normal block — 1 metadata + user tail (`normal_block_holds`)
   - epoch-switch block — metadata + N validator txs + user tail
     (`epoch_switch_block_holds`)
   - violation: system after user (`system_after_user_is_detected`)
   - violation: user first then system (`user_first_then_system_is_detected`)

Note on epoch-switch integration coverage: every existing test fixture
across the pipe layer uses `extra_data: vec![]` (no DKG/JWK validator
txs). Building a DKG/JWK fixture requires real protocol event payloads
that no current test helper produces. The unit test pins the predicate
on the epoch-switch shape (multiple consecutive SYSTEM_CALLER senders at
the head) at the algorithm level; a dedicated DKG/JWK integration fixture
is a follow-up.
…ix doc

nightly-2026-02-01 clippy's `doc_lazy_continuation` now treats a `+`
appearing at the start of a doc-comment content line as a markdown list
marker, then flags the following 10 continuation lines as
"doc list item without indentation". Swap the lone `+` for the word
`plus` (semantically identical, naturally reads as English) so markdown
no longer parses the paragraph as a list. Pure doc-comment hygiene; no
code or test change.
@nekomoto911
nekomoto911 force-pushed the feat/system-tx-gas-exempt branch from 5aa7235 to 06ddc65 Compare July 1, 2026 08:27
U-6 (`u6_test_system_tx_gas_exempt_bundle_equivalence`) uses
`GravityHardfork::Alpha` in `alpha_active_chainspec`, but the earlier
hoist-to-chainspec refactor dropped the top-of-file `GravityHardfork`
import and never brought it back into the test module — CI compiled
green while local nextest kept passing because the test module wasn't
rebuilt in isolation.

Add `GravityHardfork` to the test module's `reth_chainspec` use-list.
@nekomoto911
nekomoto911 merged commit e75679c into Galxe:main Jul 2, 2026
30 checks passed
nekomoto911 added a commit to nekomoto911/gravity-reth that referenced this pull request Jul 3, 2026
CI on PR Galxe#377 revealed that `gravity_system_tx_gas_exempt_test::
test_e2e_dual_backend_state_root_equivalence_across_alpha_boundary`
drives `run_pipe_e2e_test` twice inside one `#[test]` fn (grevm + serial).
Each call routes through `NodeCommand::execute` → `init_gravity_config`,
which uses a process-global `OnceLock` guarded by `assert!(set().is_ok())`.
Nextest gives each `#[test]` its own process, but two invocations inside
one test share it — the second call panics with `"Global gravity config
already initialized"`.

The single-invocation `test_e2e_system_tx_alpha_activation_{grevm,
disable_grevm}` in the same binary pass fine and stay wired. Skip only
the dual-backend equivalence test until Galxe#367 follow-up splits it into
two `#[test]` fns with cross-process state persistence.
Lchangliang pushed a commit that referenced this pull request Jul 4, 2026
…rom #367) (#370)

* fix(rpc): register BLS pop-verify precompile unconditionally (gravity-audit§3.5.0)

The pipe execution layer registers the BLS12-381 pop-verify precompile
(0x…625f5001) on **every** block:
  - pre-Alpha via `pre_alpha_precompiles`
    (`pipe-exec-layer-ext-v2/execute/src/lib.rs:1495-1497`)
  - post-Alpha alongside randomness
    (`pipe-exec-layer-ext-v2/execute/src/lib.rs:393-396`)

The RPC side, however, only registered the randomness-by-height
precompile, and only when Alpha is active — BLS was never registered at
all. Any `debug_traceBlock` / `debug_traceTransaction` / `trace_block` /
`trace_replayTransaction` replay of a historical block (pre-Alpha **or**
post-Alpha) that calls `0x…625f5001` therefore diverges from canonical
(the precompile address has no code on the RPC side, so the CALL
returns 0/empty and any receipt-log assertions in the user contract
fail).

Fix: register BLS unconditionally at the top of
`register_custom_precompiles`, before the Alpha gate. Randomness
registration remains Alpha-gated to match the pipe's post-Alpha branch.
BLS helper + addr are re-used from `reth_pipe_exec_layer_ext_v2`
(`bls_precompile::create_bls_pop_verify_precompile` /
`onchain_config::BLS_PRECOMPILE_ADDR`); no cycle is introduced because
`reth-pipe-exec-layer-ext-v2` depends only on `reth-rpc-eth-api`, not on
`reth-rpc`.

No tests in this commit — RPC replay tests for BLS require trace-mock
infrastructure and are tracked separately per
`_local/drafts/system-tx-gas-exempt/acceptance-tests-2026-06-26.md §5.4`.

* refactor: move BLS pop-verify precompile to gravity-precompiles

Hygiene refactor: BLS pop-verify precompile lived in
`crates/pipe-exec-layer-ext-v2/execute/src/bls_precompile.rs` and the
`BLS_PRECOMPILE_ADDR` constant in `.../onchain_config/mod.rs`. After the
BLS RPC registration fix (23a5558), `reth-rpc` started using
`reth-pipe-exec-layer-ext-v2` as a prod dep to reach the BLS helper —
philosophically backwards since the pipe layer sits closer to consensus.

`gravity-precompiles` is the existing home for chain-specific precompiles
(`randomness_by_height` already lives there). Move the BLS helper to
`gravity-precompiles::bls_pop_verify`, switch both pipe-exec-layer and
reth-rpc to import from there. The pipe-exec-layer dep on reth-rpc is
no longer needed for BLS — it drops to dev-dependency status (still
needed for the R9 randomness-provider equivalence test).

Zero behaviour change: the precompile implementation, gas pricing
(POP_VERIFY_GAS = 110_000), and the `register_custom_precompiles`
unconditional-registration semantics are preserved verbatim.

* test(rpc): BLS pop-verify precompile replay byte-equal canonical (§3.5)

Adds the §3.5 BLS RPC replay must-pass row from
`acceptance-tests-2026-06-26.md` — the gate that has been outstanding since
commit `23a55587c4` ("fix(rpc): register BLS pop-verify precompile
unconditionally"), whose commit body explicitly noted "RPC replay tests for
BLS … are tracked separately".

Three test functions, each booting a full pipe execution layer harness and
asserting byte-equal canonical via the same callTracer output that downstream
consumers see:

  1. `test_rpc_bls_call_replay_byte_equal_canonical`
     post-Alpha block-family path. Block 5 ts is Alpha-active, contains a
     user tx calling `0x…625f5001` (144-byte input → 32-byte 0x00..00 output,
     `POP_VERIFY_GAS = 110_000` flat charge). `debug_traceBlock` + callTracer
     and `trace_block` (parity) are exercised; the BLS frame `to`, `gas_used`,
     `output.len`, and absence of error are byte-equal to canonical receipt.

  2. `test_rpc_bls_call_pre_alpha_replay_unchanged`
     Same fixture shape, but `alphaTime` is set to a far-future value so every
     block timestamp is strictly pre-Alpha. This is the long-term regression
     guard against accidentally re-gating BLS RPC registration behind Alpha
     — pre-fix, RPC routed BLS calls in pre-Alpha blocks to an empty account
     (no 110_000 gas charge), diverging from canonical for the entire pre-
     Alpha history. The fix is `call.rs:117-123` (`register_custom_precompiles`)
     where BLS is now registered *before* the Alpha gate.

  3. `test_rpc_bls_call_debug_trace_transaction_byte_equal`
     Single-tx path. `debug_traceTransaction` is invoked on the BLS tx hash
     and the call frame is walked to locate the BLS-targeted frame; the top
     frame's `gas_used` matches canonical, the BLS frame is error-free and
     emits a 32-byte output. Covers the `call.rs:733-807`
     `replay_transactions_until` family.

Coverage matrix:

  | dim                | test_1 | test_2 | test_3 |
  | endpoint family    | block  | block  | tx     |
  | Alpha regime       | post   | pre    | post   |
  | byte-equal channel | gas_used, output.len, frame.to, no error  |

BLS fixture: 144 zero bytes. The byte-equality holds regardless of input
validity — both pipe and RPC run the same handler against the same input, so
any valid 144-byte buffer drives the precompile through its full code path
and produces deterministic gas + output. This reuses the unit-test fixture
style from `gravity_precompiles::bls_pop_verify::tests` (no fresh blst
keypair generated). Re-declares `BLS_PRECOMPILE_ADDR` as a local const to
avoid pulling `gravity-precompiles` into the test crate's dev-deps (sibling
`gravity_bls_precompile_test.rs:73` does the same).

Run:
  cargo nextest run -p reth-pipe-exec-layer-ext-v2 --test gravity_system_tx_bls_replay_test
  → 3 passed.

* fix(ci): backtick PoP identifiers in bls_pop_verify doc comments

`clippy::doc-markdown` (`-D warnings`) flagged eight bare `PoP` references
in module/item-level doc comments of the BLS pop-verify precompile. Wrap
them in backticks so they're treated as inline code, matching the existing
treatment of `POP_VERIFY_GAS`, `PrecompileInput`, etc.

No behavior change.

* fix(ci): drop useless .into() conversion in bls_pop_verify error path

`clippy::useless-conversion` (`-D warnings`) flagged the `.into()` after
`format!(...)` since `PrecompileError::Other` already takes `String`. The
conversion is a no-op `String -> String`. Drop it.

No behavior change.

* fix(ci): backtick PoP in bls_pop_verify test-mod doc comment

The two CI hygiene cherry-picks (`f30bf2b37c` PoP backticks +
`d6caea4d72` useless `.into()`) covered the prod doc paths of
`bls_pop_verify.rs`, but a sibling doc comment inside the file's
`#[cfg(test)] mod tests` ("Generate a BLS12-381 keypair and PoP for
testing") still trips `clippy::doc-markdown` once the code moved from
`pipe-exec-layer-ext-v2` (which had a permissive module-level allow) to
`gravity-precompiles`. Backtick `PoP` to match the eight prod sites.
nekomoto911 added a commit to nekomoto911/gravity-reth that referenced this pull request Jul 4, 2026
…iants

U-6 (parallel_execute.rs:770) already proved single-tx bundle equivalence
between WrapExecutor and GrevmExecutor at Alpha. §2.2's E2E dual test
tried to extend that claim across three axes U-6 didn't cover: the
migration hook (sentinel → 0 transition), cross-block state carryover,
and pre-Alpha baseline. That E2E has been dead-on-arrival since Galxe#367
because two run_pipe_e2e_test calls inside one #[test] fn each go through
NodeCommand::execute → init_gravity_config, a process-global OnceLock
with a hard assert! that panics on the second call.

Rather than relax the OnceLock invariant (blast radius across 15
callers of get_gravity_config) or spawn subprocesses (~80 LOC of
plumbing), cover the same invariants at the unit level where no CLI
runner is involved:

- U-6b (system_caller_migration.rs) — calls apply_state_changes_for_block
  directly on both backends against seeded_db(sentinel, nonce=1) and
  asserts BundleState byte-equal. Proves the migration hook itself
  produces symmetric state.
- U-6c (system_caller_migration.rs) — six-block sequence over the Alpha
  activation boundary. Each block: run migration hook + system tx on
  both backends, apply block N's bundle back into the shared ParallelState
  cache, assert bundle byte-equal per block. Proves cross-block carryover
  stays symmetric.
- U-6d (parallel_execute.rs) — permanently pre-Alpha chainspec
  (alpha_time = u64::MAX). Runs a system tx through both backends, asserts
  bundle byte-equal + non-zero fee delta (proving the pre-Alpha fee path
  is actually exercised, not silently short-circuited).

All three run against CacheDB<EmptyDB> via pure WrapExecutor /
GrevmExecutor + direct migration hook invocation. No NodeCommand,
CliRunner, tokio, or init_gravity_config touched.

Design and adversarial review lived in the branch-private wiki; the
verdict was "SAFE with caveat" — the caveat covers pipe-layer factory
dispatch and payload→trie writeback, neither of which is a
backend-equivalence invariant and both of which are already exercised
by every other E2E test in the crate.
nekomoto911 added a commit to nekomoto911/gravity-reth that referenced this pull request Jul 4, 2026
test_e2e_dual_backend_state_root_equivalence_across_alpha_boundary was
dead-on-arrival since Galxe#367: it called run_pipe_e2e_test twice inside
one #[test] fn, and each call went through NodeCommand::execute →
init_gravity_config (process-global OnceLock with hard assert). The
second call always panicked. The test never passed in any environment.

Backend-equivalence coverage is now provided by U-6 + U-6b + U-6c +
U-6d as unit tests that bypass the CLI runner entirely. See the parent
commit for the coverage split. Adversarial review verdict was "SAFE
with caveat": pipe-layer factory dispatch (lib.rs:313-323) and
payload→trie writeback are not covered by the new unit tests, but
both are already exercised by every other E2E test in the crate
(gravity_pipe_test, gravity_eip2935_test, etc.), so removing this one
E2E does not expose those seams.

The two remaining #[test] fns in this file
(test_e2e_system_tx_alpha_activation_{grevm,disable_grevm}) each call
run_pipe_e2e_test exactly once and continue to pass under nextest's
process-per-test isolation.
nekomoto911 added a commit that referenced this pull request Jul 4, 2026
…rred #367 tests (#377)

* fix(rpc): toggle gas-exempt cfg for target-tx in debug_traceTransaction

`debug_trace_transaction` fetched `evm_env` via `evm_env_at`, cloned it
into `replay_transactions_until` (which correctly toggles for pre-target
system txs internally), then passed the *original untoggled* `evm_env`
straight into `trace_transaction` for the target tx. If the target tx
itself is a `SYSTEM_CALLER`-signed system tx on a post-Alpha block, the
tx has `gas_price = 0` while the block's `basefee > 0` — revm rejects
with `GasPriceLessThanBasefee` ("max fee per gas less than block base
fee") because `disable_base_fee` was never set on the cfg.

Mirror the target-tx toggle already present in
`spawn_trace_transaction_in_block_with_inspector`
(`crates/rpc/rpc-eth-api/src/helpers/trace.rs`) — parity/otterscan-side
`trace_transaction` / `trace_replayTransaction` / `trace_get` /
`trace_transaction_opcode_gas` were unaffected because they go through
that helper. Only debug-namespace `debug_traceTransaction` was broken.

Gate keys off the replayed block's timestamp (not node tip) so pre-Alpha
historical replays remain unchanged.

* ci(integration): wire deferred #367 tests + invariant 9 CI allowlist parity

The gravity-pipe-test job uses an explicit `--test <name>` allowlist
because the crate has integration binaries whose dev-deps are missing in
this workspace (e.g. `gravity_hardfork_test.rs`). That same allowlist
silently skipped every RPC replay integration test file added by #367
(and #370) — `gravity_system_tx_*_test` and `gravity_bls_precompile_test`
compiled locally but never ran on CI, so a latent
`debug_traceTransaction` cfg gap and #372 Track A / B gaps went
unnoticed under a "✅ CI green" PR body.

Wire the three tests that pass today:
  - gravity_system_tx_gas_exempt_test          (pipe-only)
  - gravity_system_tx_pre_alpha_replay_test    (pre-Alpha gate false)
  - gravity_system_tx_simulation_anti_spoof_test (simulation, no replay)

Defer the two blocked ones via invariant 9's `KNOWN_UNWIRED_TESTS`:
  - gravity_system_tx_post_alpha_trace_test    (needs #372 mint RPC reg
                                                + RPC-side Alpha migration
                                                hook; locally diverges
                                                by ~9.4k gas on block 1
                                                metadata tx)
  - gravity_bls_precompile_test                (needs #370 BLS RPC reg)

Invariant 9 now enforces: every `gravity_system_tx_*_test.rs` /
`gravity_bls_*_test.rs` file under the tests dir must be EITHER wired to
CI OR listed in `KNOWN_UNWIRED_TESTS` with a documented reason. It also
flags double-claimed files and stale skip-list entries whose target file
no longer exists. This prevents another silent-skip incident: any new
test file forces the author to make an explicit CI-wiring decision.

* ci: exclude broken dual-backend test from nextest run

CI on PR #377 revealed that `gravity_system_tx_gas_exempt_test::
test_e2e_dual_backend_state_root_equivalence_across_alpha_boundary`
drives `run_pipe_e2e_test` twice inside one `#[test]` fn (grevm + serial).
Each call routes through `NodeCommand::execute` → `init_gravity_config`,
which uses a process-global `OnceLock` guarded by `assert!(set().is_ok())`.
Nextest gives each `#[test]` its own process, but two invocations inside
one test share it — the second call panics with `"Global gravity config
already initialized"`.

The single-invocation `test_e2e_system_tx_alpha_activation_{grevm,
disable_grevm}` in the same binary pass fine and stay wired. Skip only
the dual-backend equivalence test until #367 follow-up splits it into
two `#[test]` fns with cross-process state persistence.

* test: add U-6b/c/d unit tests covering §2.2 backend-equivalence invariants

U-6 (parallel_execute.rs:770) already proved single-tx bundle equivalence
between WrapExecutor and GrevmExecutor at Alpha. §2.2's E2E dual test
tried to extend that claim across three axes U-6 didn't cover: the
migration hook (sentinel → 0 transition), cross-block state carryover,
and pre-Alpha baseline. That E2E has been dead-on-arrival since #367
because two run_pipe_e2e_test calls inside one #[test] fn each go through
NodeCommand::execute → init_gravity_config, a process-global OnceLock
with a hard assert! that panics on the second call.

Rather than relax the OnceLock invariant (blast radius across 15
callers of get_gravity_config) or spawn subprocesses (~80 LOC of
plumbing), cover the same invariants at the unit level where no CLI
runner is involved:

- U-6b (system_caller_migration.rs) — calls apply_state_changes_for_block
  directly on both backends against seeded_db(sentinel, nonce=1) and
  asserts BundleState byte-equal. Proves the migration hook itself
  produces symmetric state.
- U-6c (system_caller_migration.rs) — six-block sequence over the Alpha
  activation boundary. Each block: run migration hook + system tx on
  both backends, apply block N's bundle back into the shared ParallelState
  cache, assert bundle byte-equal per block. Proves cross-block carryover
  stays symmetric.
- U-6d (parallel_execute.rs) — permanently pre-Alpha chainspec
  (alpha_time = u64::MAX). Runs a system tx through both backends, asserts
  bundle byte-equal + non-zero fee delta (proving the pre-Alpha fee path
  is actually exercised, not silently short-circuited).

All three run against CacheDB<EmptyDB> via pure WrapExecutor /
GrevmExecutor + direct migration hook invocation. No NodeCommand,
CliRunner, tokio, or init_gravity_config touched.

Design and adversarial review lived in the branch-private wiki; the
verdict was "SAFE with caveat" — the caveat covers pipe-layer factory
dispatch and payload→trie writeback, neither of which is a
backend-equivalence invariant and both of which are already exercised
by every other E2E test in the crate.

* test: remove §2.2 dual-backend E2E (superseded by U-6b/c/d)

test_e2e_dual_backend_state_root_equivalence_across_alpha_boundary was
dead-on-arrival since #367: it called run_pipe_e2e_test twice inside
one #[test] fn, and each call went through NodeCommand::execute →
init_gravity_config (process-global OnceLock with hard assert). The
second call always panicked. The test never passed in any environment.

Backend-equivalence coverage is now provided by U-6 + U-6b + U-6c +
U-6d as unit tests that bypass the CLI runner entirely. See the parent
commit for the coverage split. Adversarial review verdict was "SAFE
with caveat": pipe-layer factory dispatch (lib.rs:313-323) and
payload→trie writeback are not covered by the new unit tests, but
both are already exercised by every other E2E test in the crate
(gravity_pipe_test, gravity_eip2935_test, etc.), so removing this one
E2E does not expose those seams.

The two remaining #[test] fns in this file
(test_e2e_system_tx_alpha_activation_{grevm,disable_grevm}) each call
run_pipe_e2e_test exactly once and continue to pass under nextest's
process-per-test isolation.

* ci: drop nextest -E filter now that dual test is removed

The filter workaround introduced in 01a219d excluded
test_e2e_dual_backend_state_root_equivalence_across_alpha_boundary from
the gravity-pipe-test job because it hit an init_gravity_config
double-init panic. The test has now been removed and its
backend-equivalence claim is proved by U-6/U-6b/U-6c/U-6d unit tests
instead, so the filter is no longer needed.

* ci: wire #370 tests to CI allowlist, unblock KNOWN_UNWIRED entry

#370 (BLS pop-verify RPC registration) merged upstream during this PR,
pulling in gravity_system_tx_bls_replay_test.rs and unblocking
gravity_bls_precompile_test. Invariant 9 caught that #370 landed
without touching the allowlist / KNOWN_UNWIRED_TESTS — exactly the
silent-skip class the invariant is designed to fail on.

Wire both binaries to the gravity-pipe-test workflow and drop the
gravity_bls_precompile_test entry from KNOWN_UNWIRED_TESTS.

* test: fix ALPHA_TIME_ALWAYS fixture in gravity_system_tx_simulation_anti_spoof_test

`test_rpc_simulation_anti_spoof_grevm` and `test_rpc_simulation_anti_spoof_disable_grevm`
panicked before any endpoint invocation with:

  [anti_spoof {label}] post-Alpha SYSTEM_CALLER.balance must be 0
    left: 11579208923731619542357098500868790785326998466564056403945758400791312963993
    right: 0

Root cause is the same fixture bug already documented and fixed in
`gravity_system_tx_post_alpha_trace_test.rs:82-87`:

  `transitions_at_timestamp(parent=1687223762, current=block_n_ts, activation=1)`
  returns `false` for every n, because the genesis header timestamp
  (`0x6490fdd2 = 1687223762`) already crosses `activation = 1`. The Alpha
  migration hook therefore never fires and `SYSTEM_CALLER.balance` stays
  at the pre-migration sentinel value.

Fix mirrors the pattern from `post_alpha_trace_test.rs`:

1. `ALPHA_TIME_ALWAYS` becomes `ALPHA_TS_BASE + 1` so block 1's timestamp
   equals `alphaTime`, and `transitions_at_timestamp` flips true exactly
   at block 1 (`parent = ALPHA_TS_BASE < alphaTime <= current`).
2. `gravity_alpha_chainspec` pre-seeds `SYSTEM_CALLER` with `nonce = 1`
   in the chainspec `alloc`, so the balance-zeroing transition produces
   `(balance=0, nonce=1, code=empty)` — non-empty under EIP-161 — instead
   of `(balance=0, nonce=0, code=empty)` which revm's AccountStatus state
   machine rejects with `Wrong state transition, touch empty is not
   possible from Loaded`.

The doc-comment reference to `alphaTime = 1` in the runner is also
updated to reflect the new value.

Fixture-only change: no test assertion or anti-spoof logic is touched —
those are precisely what the test is proving. Both `#[test]` fns now
pass in ~7s each (previously panicked in ~5s).

* ci: sharpen KNOWN_UNWIRED reason for gravity_system_tx_post_alpha_trace_test

The previous `KNOWN_UNWIRED_TESTS` entry conflated two independent
blockers ("mint precompile RPC registration + RPC-side Alpha migration
hook") and gave an imprecise "~9.4k gas" divergence figure. Now that the
fixture bug in `gravity_system_tx_post_alpha_trace_test.rs` is fixed
(ALPHA_TIME_ALWAYS = ALPHA_TS_BASE + 1, SYSTEM_CALLER pre-seeded with
nonce=1; see file header lines 82-113), the tests actually reach the
trace assertions and surface the real blocker unambiguously.

Empirical local run on this branch (nextest, both grevm and
disable_grevm variants) fails at the same point:

  [post_alpha_trace grevm] trace_block(1)[0] gas_used != canonical:
    got 292093, want 282665

That is the block-family byte-equal trace invariant on block 1's
metadata system tx, off by 9428 gas — matches the mint precompile RPC
gap tracked in #372 Track A (RPC-side execution path lacks the mint
precompile registration that the pipe-layer canonical path has). The
RPC-side migration hook the old reason referenced is not the load-
bearing blocker at this call site.

Update the reason string accordingly so the next person reading the
KNOWN_UNWIRED list knows exactly which fix unblocks the wiring and can
verify by rerunning the test without touching any fixture.

---------

Co-authored-by: Lightman <31928846+Lchangliang@users.noreply.github.com>
nekomoto911 added a commit to nekomoto911/gravity-reth that referenced this pull request Jul 10, 2026
…onto v2.3.0 API base

Merge Galxe/gravity-reth main (a379a86) into the v2.3.0 migration branch:
9 conflicted files / 18 hunks resolved, 3 auto-merged files needed revm-40
semantic fixes (ResultGas, AccountInfo::account_id, EvmOverrides signature),
system_caller_migration.rs ported to the new Account/AccountInfo API.

Upstream commits: Galxe#367 gas-exempt system txs + zero SYSTEM_CALLER at Alpha,
Galxe#370 unconditional BLS pop-verify registration, Galxe#371 randomness equivalence
tests, Galxe#374 hardfork test CI, Galxe#376 gravity-api-types bump, Galxe#377
debug_traceTransaction target-tx gas-exempt gap.

Resolution ledger + shutdown-race investigation:
docs/merge-v2.3.0/upstream-sync-2026-07-09.md

Verified: cargo check --workspace --all-features = 0 err; fmt clean;
gravity_pipe_test smoke 5/5 green (one flaky pre-existing shutdown race
observed once, root-caused as reth-tasks Arc-owned-runtime teardown race,
documented, not merge-introduced).
nekomoto911 added a commit that referenced this pull request Jul 10, 2026
Merge upstream(Galxe)/gravity-reth-merge-v2.3.0 (22 commits, changeset
static-file persistence + migrate-changesets CLI + RocksDB tuning) into
the local line (upstream/main #367 sync + genesis TTD fix + persistence
poll fix). Single conflict: both sides independently landed the same
try_poll_persistence fix in pipe_run_inner — kept team-side comment.

Verified: cargo check --workspace --all-features = 0 err.
nekomoto911 added a commit that referenced this pull request Jul 10, 2026
… API base

Linear replay of the Galxe main sync (a379a86): 9 conflicted files /
18 hunks resolved, 3 auto-merged files needed revm-40 semantic fixes
(ResultGas, AccountInfo::account_id, EvmOverrides signature),
system_caller_migration.rs ported to the new Account/AccountInfo API.

Upstream commits: #367 gas-exempt system txs + zero SYSTEM_CALLER at Alpha,
#370 unconditional BLS pop-verify registration, #371 randomness equivalence
tests, #374 hardfork test CI, #376 gravity-api-types bump, #377
debug_traceTransaction target-tx gas-exempt gap.

Resolution ledger + shutdown-race investigation:
docs/merge-v2.3.0/upstream-sync-2026-07-09.md

Verified: cargo check --workspace --all-features = 0 err; fmt clean;
gravity_pipe_test smoke 5/5 green.
nekomoto911 added a commit that referenced this pull request Jul 20, 2026
Advances the merge-base so PR #396 no longer conflicts with main. Brings in the
4 commits that landed after the 2026-07-09 sync: #383 (randomness from canonical
headers), #385 (EIP-7702 cross-account nonce halt + recoverable-error panic
hardening), #382 (mint precompile gas guard), #390 (withdrawal-invariant docs).

The 11 conflicts are all v2.3.0/revm40 API-form divergence against the
already-adapted #367-#374 batch; resolved take-ours (revm40 accessor methods,
PrecompileHalt, AccountInfo::account_id, no StateCacheDbRefMutWrapper). #382's
gas guard re-adapted from revm36 PrecompileError to revm40 PrecompileHalt.

Verified: cargo check --workspace --all-features = 0 err; fmt clean; pipe-exec
and gravity-storage --tests clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request hardfork

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants