Skip to content

op-reth: test the proofs multiproof V2 path - #23420

Draft
einar-oplabs wants to merge 1 commit into
einar/23387-proofs-reconcile-testsfrom
einar/23394-proofs-multiproof-v2
Draft

einar-oplabs wants to merge 1 commit into
einar/23387-proofs-reconcile-testsfrom
einar/23394-proofs-multiproof-v2

Conversation

@einar-oplabs

@einar-oplabs einar-oplabs commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

The reth 2.6 bump (#23046) made V2 multiproofs the default and added DatabaseProof::overlay_multiproof_v2 plus multiproof_v2 on both proofs state providers, with no test. This is the path reth's engine and RPC take through the proofs provider, so it gets the coverage #23394 asks for.

What the tests cover

rust/op-reth/crates/trie/src/proof/tests.rs, two rstest tables.

The fixture is two blocks on an empty proofs store. Block 1 creates a contract with two slots, an account with one slot, two EOAs, 256 filler accounts and 256 filler slots in each of the two storage tries. The tip, block 2, destroys the second account, sets one of the contract's slots to zero and updates one EOA. Both blocks' trie updates are computed with StateRoot::overlay_root_with_updates against the store, as the engine does.

fixture_tip_rewrites_stored_branch_nodes (both backends) pins the property the fillers exist for: the tip rewrites stored branch nodes of the account trie and of the contract's storage trie and removes the destroyed account's storage branch nodes, so the proofs below read stored and overlaid branch nodes, not leaves alone.

multiproof_v2_matches_multiproof requests the V1 multiproof and the V2 multiproof_v2 from the proofs state provider for the same targets and asserts that each resulting account proof verifies against the tip's state root (AccountProof::verify) and shows the tip's account info and storage values. The state root is computed from the plain tip state with state_root_unsorted/storage_root_unsorted, independently of the store, and the case asserts the store agrees with it before any proof is requested.

Targets (#[case]):

  • the contract with a live slot, the slot set to zero, and a slot never written
  • the destroyed account, with the slot it had
  • the updated EOA and the untouched EOA, each with a slot never written
  • an account that never existed
  • all of the above in one request

Matrix (#[values]):

  • storage backend: InMemory, Mdbx
  • where the tip block lives when proofs are requested: flushed to the store (OpProofsStateProviderRef at the tip); buffered in the engine (TrieBufferState::state_provider, so MemoryOverlayOpProofsStateProviderRef lays the block over the store at the parent); passed as the caller's TrieInput over the store at the parent. The third value is one beyond the issue's {flushed, buffered overlay}; it feeds overlay_multiproof_v2's input straight from OpProofsStateProviderRef rather than only through the overlay's prepend_self.

36 + 2 = 38 cells, 0.3 s in total.

Local verification

cd rust && cargo test -p reth-optimism-trie --lib proof::tests
test result: ok. 38 passed; 0 failed; 0 ignored; 0 measured; 249 filtered out; finished in 0.28s

cd rust && cargo test -p reth-optimism-trie --all-features
unit tests: 286 passed (30.3s); tests/lib.rs: 86 passed; tests/live.rs: 7 passed; doc-tests: 1 passed, 1 ignored

cd rust && cargo clippy -p reth-optimism-trie --all-targets --all-features
no warnings

Mutation checks, both reverted:

  • Dropping the InMemoryTrieCursorFactory node overlay from overlay_multiproof_v2 fails exactly the 24 buffered and trie-input cells, each on the V2 verification (ValueMismatch) while V1 still passes; the 14 flushed and fixture cells are unaffected. The rust-code-reviewer pass caught that the first fixture, with five accounts, never stored a branch node, so this mutation passed; the fillers and the fixture test are the fix.
  • Dropping input.prepend_self(self.trie_input().clone()) from MemoryOverlayOpProofsStateProviderRef::multiproof_v2 (run on that first fixture) fails exactly the 12 buffered cells on the V2 verification.

Skipped cells

None.

Findings

No bug reproduced: on this stack both paths agree on every cell, including the destroyed account and the zeroed slot, over both backends and all three placements.

Seams for later PRs

  • engine::buffer and engine::buffer::state are now pub(crate) and TrieBufferState::insert is pub(crate) (was pub(in crate::engine)), so tests outside the engine can build a buffered overlay provider with TrieBufferState::state_provider. The engine's live buffer stays inside the private engine::state, so nothing outside the engine can mutate it.
  • test_utils::destroyed_accounts gains INITIAL_ANCHOR (the anchor seed_initial_storage sets) and plain_slot.
  • rstest is now a dev-dependency of reth-optimism-trie.

Notes on the issue

The method under test does not exist on develop, so, as the issue's verification criterion says, this lands on the bump stack rather than against develop.

Stacked on #23419, #23358 and #23046.

Closes #23394

🤖 Generated with Claude Code

Request the V1 and V2 multiproofs of the proofs state providers for the
same targets and check that both verify against the state root and show
the tip's account and storage values, over the InMemory and MDBX stores,
with the proven block flushed to the store, buffered in the engine, or
passed as the caller's trie input. The targets cover a contract with a
live, a zeroed and a never-written slot, a destroyed account, an updated
and an untouched EOA, and an account that never existed.

The engine's buffer module and `TrieBufferState::insert` become crate
visible so the test can build the buffered overlay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant