feat(hardfork): gas-exempt system txs + zero SYSTEM_CALLER at Alpha#367
Open
nekomoto911 wants to merge 24 commits into
Open
feat(hardfork): gas-exempt system txs + zero SYSTEM_CALLER at Alpha#367nekomoto911 wants to merge 24 commits into
nekomoto911 wants to merge 24 commits into
Conversation
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.
This was referenced Jun 30, 2026
…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.
d95f237 to
b8fe95a
Compare
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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_checkretained as zero-cost defensive layer):disable_base_fee=true+disable_balance_check=trueontransact_system_txnwhen Alpha is active.disable_nonce_checkstaysfalse— nonce sequence is part of the protocol contract.gas_price = 0when Alpha is active (otherwisebase_fee).One-shot balance migration at Alpha activation:
apply_state_changechannel (same pattern as EIP-2935 boundary deployment), so serial == grevm by construction.AccountInfovia a newParallelExecutor::basicaccessor — hook is self-contained, no caller-side scaffolding.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).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 onsender == SYSTEM_CALLER(recovered, not user-supplied — pure simulation endpoints are explicitly anti-spoofed).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 byreth_chainspec::system_txs_form_head_prefixpredicate + per-loopdebug_assert!.debug_traceTransaction/trace_transaction/trace_replayTransaction/replay_transactions_until): per-tx EVM rebuild onkindtransition, with initial EVM peeked-and-pre-toggled to skip a redundant rebuild when the prelude leads with a system tx.eth_call/eth_estimateGas/eth_simulateV1/debug_traceCall/trace_call*/trace_rawTransaction): unchanged — they execute user-supplied tx envs, never replay history, and user-suppliedfrom=SYSTEM_CALLERMUST 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 toreth-chainspecso 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 loopsdebug_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:
fix(rpc): register BLS pop-verify precompile unconditionally (split from #367)— closes the BLS pop-verify RPC registration gap (gravity-audit §3.5.0) + hygiene refactor to move helper togravity-precompiles+ replay byte-equal-canonical test.test(rpc): equivalence of execution vs header randomness providers (split from #367)— pins byte-equality betweenExecutionRandomnessProvider(pipe) andHeaderRandomnessProvider(RPC) across 11 query-height boundaries (recent / storage / parent / future /u64::MAX).Merge-order coordination: any of the three can land first. The other two then rebase
upstream/main; git's--empty=dropauto-handles the identical patch-id commits. No manual conflict resolution needed.Commits (23)
02c4d892refactor: dedupe SYSTEM_CALLER + add gating helpers1cbc03f5refactor: hoist SYSTEM_CALLER read into ParallelExecutor::basic03c37d31refactor: hoist is_system_tx_gas_exempt helper to reth-chainspec3411118crefactor: use is_gravity_system_caller helper at sender comparison sites0d50df60feat: gas-exempt system transactions behind Alpha fork (L1+L2)7acf27effeat: zero SYSTEM_CALLER balance at Alpha activation (apply_state_change)ee261914feat(rpc): per-tx gas-exempt cfg for block-family trace endpoints274507c5feat(rpc): per-tx gas-exempt cfg for single-tx trace endpoints64c41f38refactor: drop fused_inspector machinery from GravityTracingCtxd1107e64perf(rpc): peek first prelude tx to seed replay EVM with matching cfgf8487835docs: clean up internal anchor references and stale doc paths31a9e5f4test: bundle equivalence + migration hook edge cases (load-bearing)86a068a9test(nice): fee-zero / balance-untouched + disable_balance_check defensive verify96eed10ftest(grep): long-term regression prevention checklist4d86493btest(e2e): Alpha-activation block + dual-backend system-tx gas-exempt parity7826cffatest(rpc): pre-Alpha block replay regression (no false exemption)8d73fcbftest(rpc): pure-simulation endpoints reject SYSTEM_CALLER spoofing348c5c05test(rpc): block-family + single-tx trace endpoints post-Alpha consistency339c0904test(rpc): tighten post-Alpha trace endpoints to byte-equal canonical (HP-1)f0afa12frefactor(ci): move grep invariants from Rust test to bash script + CI steped12dedbfix(ci): backtick SYSTEM_CALLER in is_system_tx_gas_exempt doc97728ad7fix(ci): drop tx.clone() in Trace::trace_block_with replay loopb8fe95a7test(rpc): pin system-txs-at-block-head invariant for trace replay loopsTest plan
WrapExecutor) and grevm (GrevmExecutor) ontransact_system_txn(crates/ethereum/evm/src/parallel_execute.rsU-6) — load-bearing serial==grevm invariant.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.disable_balance_checkzero collateral (U-8).gravity_system_tx_gas_exempt_test.rs).gravity_system_tx_pre_alpha_replay_test.rs).from=SYSTEM_CALLERfree-gas attempt (gravity_system_tx_simulation_anti_spoof_test.rs).gravity_system_tx_post_alpha_trace_test.rs).system_txs_form_head_prefix: 7 unit tests incrates/chainspec/src/gravity.rs#testscovering empty / user-only / system-only / normal / epoch-switch shapes + two violation shapes (system-after-user / user-first-then-system).scripts/check-gravity-invariants.sh, wired into theintegrationworkflow): system-tx callsites count, SYSTEM_CALLER address single-definition, RPC historical-state gating,execute_history_blockcaller audit, single-predicate referenced by all layers.Compatibility notes
falseforblock_ts < alphaTime. RPC replay regression test pins this.gas_used > 0butreceipt.gas_price = 0and no balance delta — explorers may want to label these distinctly.Not in this PR (deferred follow-ups)
parallel_apply_transitions_and_create_revertsaccounting fix (reverts_sizeis never updated, while revm serial updates bothstate_sizeandreverts_size). Discovered by U-6 bundle equivalence test; non-consensus (state root unaffected, onlyBundleState::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/main6ea2ba328c(PR #366, randomness-by-height precompile gas guard). Backup of pre-shrink tip atfeat/system-tx-gas-exempt-pre-shrinklocally (29 commits, included BLS+R9 trio + their CI hygiene fixes — now extracted into #370 / #371).