test(rpc): equivalence of execution vs header randomness providers (split from #367)#371
Open
nekomoto911 wants to merge 1 commit into
Open
Conversation
R9 from `_local/drafts/system-tx-gas-exempt/system-tx-gas-exempt-design-2026-06-25.md` §3.5.0 / §6.2 / §7 decision 6: the `randomness_by_height` precompile is registered in two places with intentionally different data sources: - pipe `ExecutionRandomnessProvider` (`crates/pipe-exec-layer-ext-v2/execute/src/randomness_precompile.rs`) reads the in-flight `OrderedBlock.prev_randao` for the current block, `parent_header.mix_hash()` for the parent, and a `GravityStorage`-backed fallback for older heights. - RPC `HeaderRandomnessProvider` (`crates/rpc/rpc/src/eth/helpers/call.rs`) reads `HeaderProvider::header_by_number(h).mix_hash` for past heights and uses the EVM env's `prev_randao` (passed as `current_randomness`) for the simulated block. The two implementations diverge on data source by necessity (the in-flight block is not persisted while it is executing) but must produce byte-identical `RandomnessByHeightLookup`s on the same `(block_number, query_height)` input for RPC replay (`debug_trace*` / `trace_*`) to stay byte-equal with canonical on any historical block containing a `0x...625f5002` call. Any divergence is the same class of pipe/RPC gap as the BLS RPC-registration bug closed in 23a5558 — a silent state-root / receipts fork. Verify trail: - Both providers already implement the shared `RandomnessByHeightProvider` trait from `gravity-precompiles` (return type `Result<RandomnessByHeightLookup { value: Option<B256>, gas_used: u64 }, _>`), so route B applies: equivalence test only, no refactor. - Logic walk-through confirmed equivalence for the block-replay case (`current_randomness = Some(_)`) across every branch in both impls when the data sources are consistent: - `current_randomness`: pipe sets from `ordered_block.prev_randao`; RPC sets from EVM env. For replay these must agree by construction. - `parent_randomness` (pipe) vs `header_by_number(parent).mix_hash` (RPC): pipe wires `parent_header.mix_hash()` in `crates/pipe-exec-layer-ext-v2/execute/src/lib.rs:390`, so both read the same value at the parent. - Fallback (pipe `GravityStorage`) vs `header_by_number(h).mix_hash` (RPC): both source from the persisted header chain; data-source agreement is a separate engine-API ingestion invariant, not under test here. - One pathological edge identified: with `recent_window = 0`, pipe always treats parent as recent (special-cased) while RPC treats parent as storage tier (`reference - parent = 1 > 0`). No real policy uses `recent_window = 0` (Alpha default is 86_400). Not tested; documented in this commit body. Test: `execution_provider_and_header_provider_agree_byte_for_byte` feeds both providers the same in-memory data set and asserts `RandomnessByHeightLookup` byte-equality across 11 `query_height` points covering: 0, 1, `current / 2` (a height with no header), `current - window - 1`, `current - window`, `current - window + 1`, `current - 2`, `current - 1` (parent), `current`, `current + 1`, and `u64::MAX`. PASS. Placement: ideally the test lives in `crates/pipe-exec-layer-ext-v2/execute/src/randomness_precompile.rs#tests` next to `ExecutionRandomnessProvider`, but `HeaderRandomnessProvider` is a private struct local to `crates/rpc/rpc/src/eth/helpers/call.rs`. Moving it to a shared crate would be a refactor that R9 did not require (the shared trait already exists), so the test lives in the `reth-rpc` crate where both types are in scope (`reth-pipe-exec-layer-ext-v2` is already a prod dep of `reth-rpc`).
a0c66ab to
e81b1fc
Compare
9 tasks
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
Pin the byte-equality of the two
RandomnessByHeightProviderimplementations with a unit-level equivalence test. Single commit factored out of #367 — not dependent on Gravity Alpha.Why split out of #367
The
randomness_by_heightprecompile is registered in two places with intentionally distinct data sources:ExecutionRandomnessProvider(crates/pipe-exec-layer-ext-v2/execute/src/randomness_precompile.rs) reads the in-flightOrderedBlock.prev_randaofor the current block,parent_header.mix_hash()for the parent, and aGravityStorage-backed fallback for older heightsHeaderRandomnessProvider(crates/rpc/rpc/src/eth/helpers/call.rs) readsHeaderProvider::header_by_number(h).mix_hashfor past heights and uses the EVM env'sprev_randao(passed ascurrent_randomness) for the simulated blockThe two diverge on data source by necessity (the in-flight block is not persisted while it is executing) but the shared trait
RandomnessByHeightProviderrequires they produce byte-identicalRandomnessByHeightLookups on the same(block_number, query_height)input for RPC replay (debug_trace*/trace_*) to stay byte-equal canonical on any historical block calling0x...625f5002.Same class of pipe/RPC gap as the BLS RPC-registration bug — pinning it with a unit test is independent of Alpha.
What
Adds
execution_provider_and_header_provider_agree_byte_for_byteincrates/rpc/rpc/src/eth/helpers/call.rs#tests. Feeds both providers the same in-memory dataset and assertsRandomnessByHeightLookupbyte-equality across 11query_heightboundaries:0,1,current / 2(a height with no header),current - window - 1,current - window,current - window + 1,current - 2,current - 1(parent),current,current + 1,u64::MAX. The fallback (pipeGravityStorage) vsheader_by_number(h).mix_hash(RPC) data-source agreement is a separate engine-API ingestion invariant, not under test here.Adds
reth-pipe-exec-layer-ext-v2.workspace = truetocrates/rpc/rpc/Cargo.toml's[dev-dependencies]so both provider types are in scope from the#[cfg(test)]module (HeaderRandomnessProvideris local toreth-rpc;ExecutionRandomnessProviderlives in pipe-exec-layer).Test plan
cargo check -p reth-rpc --testscargo nextest run -p reth-rpc execution_provider_and_header_provider_agree_byte_for_byteCompatibility
Test-only + dev-dep. No prod code changes.
Coordination