From 3493ac7ff31d960f8bd03cfe4c9af63421c63780 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 Jul 2026 22:05:21 +0800 Subject: [PATCH 01/28] docs: add reth v2.4.0 + JIT design note (working doc, drop before PR) Co-Authored-By: Claude Fable 5 --- .../2026-07-14-reth-v2.4.0-jit-design.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md diff --git a/docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md b/docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md new file mode 100644 index 0000000..44ea8c7 --- /dev/null +++ b/docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md @@ -0,0 +1,93 @@ +# Reth v2.4.0 bump + revmc JIT support (proving-isolated) + +Date: 2026-07-14 · Branch: `feat/reth-v2.4.0-jit` · Status: approved for autonomous execution + +## Goal + +1. Bump the reth dependency from rev `27bfddea` (pre-v2.3.0) to **v2.4.0** (`943af245c4d69c6c1df241df016c278ffb5d15df`). +2. Support revmc JIT execution (supersedes PR #218's vendored-revmc approach). +3. Guarantee JIT cannot affect proving: consensus gas accounting (Unzen zk-gas), RPC + execution, traced execution, and the proof-history subsystem stay interpreter/JIT-free. + +## Key facts driving the design + +- reth v2.4.0 ships **native experimental revmc JIT** (reth#23230): `reth_evm::JitBackend` + control trait, `ConfigureEvm::{with_jit_support_enabled, jit_backend}` hooks, + `reth_node_core::args::JitArgs` CLI, `reth_jit` action on the generic `RethApi` + (`reth` namespace), engine-tree/payload-builder opt-in via `.with_jit_support()`, + out-of-process compile helper (`maybe_run_jit_helper`). +- reth v2.4.0 locks revmc at `7e3536d` (branch main, revm 41, alloy-evm 0.37). We pin + **`56ed0063`** = fast-forward +3 commits, adding revmc#395 (dynamic-gas failure ordering — + consensus-relevant correctness fix). revmc#391 (non-blocking runtime controls) is already + in at `7e3536d`. This removes the need for PR #218's `vendor/revmc` tree entirely. +- Published-crate lattice: reth v2.4.0 ⇒ `reth-primitives-traits`/`reth-codecs` **0.5.0**; + v2.3.0 ⇒ 0.4.1. The OP monorepo (source of `reth-optimism-trie`) pins reth **v2.3.0** + + 0.4.1 on `develop` and has no v2.4.0 rev; `[patch]` cannot bridge a 0.4→0.5 registry split. + ⇒ **Vendor `reth-optimism-trie`** into `vendor/reth-optimism-trie` (from OP `develop`, + exact rev recorded in `TAIKO-PROVENANCE.md`), port it to v2.4.0 APIs. This also removes + the standing reth-pin ↔ OP-pin coupling. reth v2.4.0 still has no native historical-proofs + storage, so the crate remains required. +- MSRV: reth v2.4.0 and revmc require Rust **1.95** (toolchain bump 1.94.0 → 1.95.0); + revmc-llvm needs **LLVM 22** (PR #218's CI/Docker scripts are ported). + +## Dependency pin changes + +| dep | from | to | +|---|---|---| +| reth (all git crates) | rev 27bfddea | rev 943af245 (= tag v2.4.0) | +| reth-codecs / reth-primitives-traits | 0.3.0 | 0.5.0 | +| alloy-consensus family (consensus, eips, genesis, json-rpc, network, rpc-*, serde, signer*) | 2.0.0 | 2.1.1 | +| alloy-primitives / alloy-sol-types | 1.5.6 | 1.6.0 | +| alloy-evm | 0.33.0 | 0.37.0 | +| revm-database-interface | 11.0.0 | 41.0.0 | +| revmc (new) | — | git paradigmxyz/revmc rev 56ed0063 | +| reth-optimism-trie | OP git rev bcf489ea | path = vendor/reth-optimism-trie | + +## JIT architecture (upstream-aligned port of PR #218) + +- `crates/evm/src/jit.rs`: `JitConfig` (defaults mirror upstream `JitArgs`), `build_backend()` + → `revmc::runtime::JitBackend` (OutOfProcess mode), re-exports. PR #218's custom + `JitBackendControl` trait is dropped in favor of upstream `reth_evm::JitBackend`. +- `TaikoEvmFactory` (crates/evm/src/factory.rs): holds shared backend + disabled backend + + `jit_support` flag; wraps `TaikoEvm` in `revmc::revm_evm::JitEvm`. Implements + `reth_evm::JitBackend` by delegating to the shared backend. +- `TaikoEvmConfig` (crates/block/src/config.rs): implements + `ConfigureEvm::with_jit_support_enabled` (toggles factory flag) and + `ConfigureEvm::jit_backend`. No `for_rpc()` inversion needed — upstream model is opt-in. +- `TaikoExecutorBuilder::new(JitConfig)` builds the backend (dump dir under datadir when + `--jit.debug`); errors if `--jit` requested on a non-jit build. +- CLI: reuse upstream `reth_node_core::args::JitArgs` flattened into `TaikoCliExtArgs`; + `TaikoNodeExtArgs::jit_config()`; `TaikoNode::new(JitConfig)`. +- bin: `maybe_run_jit_helper()` first thing in `main` (out-of-process compile helper). +- RPC controls: upstream `RethApi` already exposes `reth_jit` (enable/disable/pause/unpause/ + clear) through `ConfigureEvm::jit_backend()` — no Taiko RPC code needed (PR #218's + `crates/rpc/src/eth/jit.rs` is dropped). + +## Proving isolation (four gates + tests) + +1. **Fork allowlist (hard consensus gate)**: `spec_supports_jit(TaikoSpecId)` — exhaustive + match; `UNZEN => false` (zk-gas metering needs per-opcode interpreter hooks). Belt-and- + suspenders re-check `schedule_for(spec).is_none()`. New forks fail compilation until + classified. +2. **Inspected execution** (tracers, zk-gas inspector paths) always selects the disabled + backend — compiled code never runs under an inspector. +3. **Local opt-in**: `jit_support` defaults **off**; only engine-tree canonical execution + (upstream generic code) and the Taiko payload builder call `.with_jit_support()`. All + RPC execution (eth_call/estimate/simulate/trace, taikoAuth preflight) uses the base + config ⇒ interpreter. +4. **Runtime enable**: compilation only starts with `--jit` (or `reth_jit` RPC), default off; + plus the `jit` cargo feature at build time. + +Proof-history reads state/tries and never executes the EVM — structurally unaffected. +Divergence tests (ported from PR #218's `zk_gas/tests.rs` + factory tests) assert +interpreter ≡ JIT results and that RPC/Unzen paths never touch the shared backend. + +## Not in scope + +- Enabling JIT by default (runtime default remains off). +- Dropping the zk-gas schedule (that's the Unzen-successor repricing work, PR #213 line). + +## Verification plan + +`just fmt` · `just clippy` · `just test` (workspace, all features) · non-jit build check · +JIT equality tests (requires local LLVM 22; noted in report if unavailable locally). From 65f8b6d30768790bc2eef0d1c1f85924d9701bfd Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 Jul 2026 22:22:56 +0800 Subject: [PATCH 02/28] chore(deps): bump reth to v2.4.0, vendor reth-optimism-trie, toolchain 1.95 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reth git pin -> 943af245 (tag v2.4.0); published reth crates -> 0.5 - alloy 2.1.1 / alloy-evm 0.37 / alloy-primitives 1.6 / revm 41 - reth default features minus jit (keeps LLVM out of non-jit builds) - vendor reth-optimism-trie from OP develop 9b802fdb (OP still pins reth v2.3.0; published-crate split 0.4/0.5 prevents a git dep) — see vendor/reth-optimism-trie/TAIKO-PROVENANCE.md Co-Authored-By: Claude Fable 5 --- Cargo.lock | 3538 ++++++++------- Cargo.toml | 174 +- justfile | 2 +- rust-toolchain.toml | 2 +- vendor/reth-optimism-trie/Cargo.toml | 112 + vendor/reth-optimism-trie/TAIKO-PROVENANCE.md | 25 + vendor/reth-optimism-trie/src/api.rs | 663 +++ .../src/backfill/changesets.rs | 104 + .../reth-optimism-trie/src/backfill/error.rs | 61 + vendor/reth-optimism-trie/src/backfill/job.rs | 499 ++ vendor/reth-optimism-trie/src/backfill/mod.rs | 25 + .../reth-optimism-trie/src/backfill/tests.rs | 821 ++++ vendor/reth-optimism-trie/src/cursor.rs | 129 + .../reth-optimism-trie/src/cursor_factory.rs | 196 + vendor/reth-optimism-trie/src/db/cursor.rs | 1465 ++++++ vendor/reth-optimism-trie/src/db/mod.rs | 24 + .../reth-optimism-trie/src/db/models/block.rs | 79 + .../src/db/models/change_set.rs | 128 + .../reth-optimism-trie/src/db/models/key.rs | 476 ++ vendor/reth-optimism-trie/src/db/models/kv.rs | 68 + .../reth-optimism-trie/src/db/models/mod.rs | 279 ++ .../src/db/models/snapshot.rs | 146 + .../src/db/models/storage.rs | 258 ++ .../reth-optimism-trie/src/db/models/value.rs | 210 + .../src/db/models/version.rs | 192 + vendor/reth-optimism-trie/src/db/schema.md | 366 ++ vendor/reth-optimism-trie/src/db/store.rs | 4014 +++++++++++++++++ .../src/db/store_v2/backfill.rs | 532 +++ .../src/db/store_v2/cursor/account.rs | 151 + .../src/db/store_v2/cursor/account_trie.rs | 236 + .../src/db/store_v2/cursor/mod.rs | 198 + .../src/db/store_v2/cursor/snapshot.rs | 286 ++ .../src/db/store_v2/cursor/snapshot_tests.rs | 254 ++ .../src/db/store_v2/cursor/storage.rs | 203 + .../src/db/store_v2/cursor/storage_trie.rs | 282 ++ .../src/db/store_v2/cursor/tests.rs | 2322 ++++++++++ .../src/db/store_v2/init.rs | 156 + .../src/db/store_v2/metrics.rs | 97 + .../reth-optimism-trie/src/db/store_v2/mod.rs | 121 + .../src/db/store_v2/provider_ro.rs | 148 + .../src/db/store_v2/provider_rw.rs | 192 + .../src/db/store_v2/read.rs | 195 + .../src/db/store_v2/snapshot_init.rs | 161 + .../src/db/store_v2/snapshot_read.rs | 117 + .../src/db/store_v2/snapshot_tests.rs | 773 ++++ .../src/db/store_v2/tests.rs | 2299 ++++++++++ .../src/db/store_v2/write.rs | 1127 +++++ .../src/engine/buffer/metrics.rs | 12 + .../src/engine/buffer/mod.rs | 12 + .../src/engine/buffer/overlay.rs | 287 ++ .../src/engine/buffer/state.rs | 341 ++ vendor/reth-optimism-trie/src/engine/error.rs | 73 + .../reth-optimism-trie/src/engine/handle.rs | 171 + .../reth-optimism-trie/src/engine/metrics.rs | 22 + vendor/reth-optimism-trie/src/engine/mod.rs | 86 + .../src/engine/persistence/error.rs | 18 + .../src/engine/persistence/handle.rs | 80 + .../src/engine/persistence/metrics.rs | 55 + .../src/engine/persistence/mod.rs | 13 + .../src/engine/persistence/service.rs | 152 + .../reth-optimism-trie/src/engine/runner.rs | 273 ++ .../src/engine/service_guard.rs | 26 + vendor/reth-optimism-trie/src/engine/state.rs | 331 ++ .../src/engine/tasks/execute_block.rs | 170 + .../src/engine/tasks/flush.rs | 30 + .../src/engine/tasks/index_block.rs | 103 + .../src/engine/tasks/mod.rs | 21 + .../src/engine/tasks/reorg.rs | 107 + .../src/engine/tasks/sync_to.rs | 30 + .../src/engine/tasks/unwind.rs | 63 + vendor/reth-optimism-trie/src/error.rs | 256 ++ vendor/reth-optimism-trie/src/in_memory.rs | 960 ++++ vendor/reth-optimism-trie/src/initialize.rs | 1634 +++++++ vendor/reth-optimism-trie/src/lib.rs | 85 + vendor/reth-optimism-trie/src/metrics.rs | 718 +++ vendor/reth-optimism-trie/src/proof.rs | 403 ++ vendor/reth-optimism-trie/src/provider.rs | 257 ++ vendor/reth-optimism-trie/src/prune/error.rs | 96 + .../reth-optimism-trie/src/prune/metrics.rs | 39 + vendor/reth-optimism-trie/src/prune/mod.rs | 11 + vendor/reth-optimism-trie/src/prune/pruner.rs | 683 +++ vendor/reth-optimism-trie/src/prune/task.rs | 61 + .../reth-optimism-trie/src/snapshot/error.rs | 70 + vendor/reth-optimism-trie/src/snapshot/job.rs | 675 +++ vendor/reth-optimism-trie/src/snapshot/mod.rs | 29 + .../reth-optimism-trie/src/snapshot/tests.rs | 487 ++ vendor/reth-optimism-trie/src/test_utils.rs | 331 ++ vendor/reth-optimism-trie/tests/lib.rs | 2163 +++++++++ vendor/reth-optimism-trie/tests/live.rs | 633 +++ 89 files changed, 34295 insertions(+), 1678 deletions(-) create mode 100644 vendor/reth-optimism-trie/Cargo.toml create mode 100644 vendor/reth-optimism-trie/TAIKO-PROVENANCE.md create mode 100644 vendor/reth-optimism-trie/src/api.rs create mode 100644 vendor/reth-optimism-trie/src/backfill/changesets.rs create mode 100644 vendor/reth-optimism-trie/src/backfill/error.rs create mode 100644 vendor/reth-optimism-trie/src/backfill/job.rs create mode 100644 vendor/reth-optimism-trie/src/backfill/mod.rs create mode 100644 vendor/reth-optimism-trie/src/backfill/tests.rs create mode 100644 vendor/reth-optimism-trie/src/cursor.rs create mode 100644 vendor/reth-optimism-trie/src/cursor_factory.rs create mode 100644 vendor/reth-optimism-trie/src/db/cursor.rs create mode 100644 vendor/reth-optimism-trie/src/db/mod.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/block.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/change_set.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/key.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/kv.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/mod.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/snapshot.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/storage.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/value.rs create mode 100644 vendor/reth-optimism-trie/src/db/models/version.rs create mode 100644 vendor/reth-optimism-trie/src/db/schema.md create mode 100644 vendor/reth-optimism-trie/src/db/store.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/backfill.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/account.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/account_trie.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/mod.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/snapshot.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/snapshot_tests.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/storage.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/storage_trie.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/tests.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/init.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/metrics.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/mod.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/provider_ro.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/provider_rw.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/read.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/snapshot_init.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/snapshot_read.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/snapshot_tests.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/tests.rs create mode 100644 vendor/reth-optimism-trie/src/db/store_v2/write.rs create mode 100644 vendor/reth-optimism-trie/src/engine/buffer/metrics.rs create mode 100644 vendor/reth-optimism-trie/src/engine/buffer/mod.rs create mode 100644 vendor/reth-optimism-trie/src/engine/buffer/overlay.rs create mode 100644 vendor/reth-optimism-trie/src/engine/buffer/state.rs create mode 100644 vendor/reth-optimism-trie/src/engine/error.rs create mode 100644 vendor/reth-optimism-trie/src/engine/handle.rs create mode 100644 vendor/reth-optimism-trie/src/engine/metrics.rs create mode 100644 vendor/reth-optimism-trie/src/engine/mod.rs create mode 100644 vendor/reth-optimism-trie/src/engine/persistence/error.rs create mode 100644 vendor/reth-optimism-trie/src/engine/persistence/handle.rs create mode 100644 vendor/reth-optimism-trie/src/engine/persistence/metrics.rs create mode 100644 vendor/reth-optimism-trie/src/engine/persistence/mod.rs create mode 100644 vendor/reth-optimism-trie/src/engine/persistence/service.rs create mode 100644 vendor/reth-optimism-trie/src/engine/runner.rs create mode 100644 vendor/reth-optimism-trie/src/engine/service_guard.rs create mode 100644 vendor/reth-optimism-trie/src/engine/state.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/execute_block.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/flush.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/index_block.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/mod.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/reorg.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/sync_to.rs create mode 100644 vendor/reth-optimism-trie/src/engine/tasks/unwind.rs create mode 100644 vendor/reth-optimism-trie/src/error.rs create mode 100644 vendor/reth-optimism-trie/src/in_memory.rs create mode 100644 vendor/reth-optimism-trie/src/initialize.rs create mode 100644 vendor/reth-optimism-trie/src/lib.rs create mode 100644 vendor/reth-optimism-trie/src/metrics.rs create mode 100644 vendor/reth-optimism-trie/src/proof.rs create mode 100644 vendor/reth-optimism-trie/src/provider.rs create mode 100644 vendor/reth-optimism-trie/src/prune/error.rs create mode 100644 vendor/reth-optimism-trie/src/prune/metrics.rs create mode 100644 vendor/reth-optimism-trie/src/prune/mod.rs create mode 100644 vendor/reth-optimism-trie/src/prune/pruner.rs create mode 100644 vendor/reth-optimism-trie/src/prune/task.rs create mode 100644 vendor/reth-optimism-trie/src/snapshot/error.rs create mode 100644 vendor/reth-optimism-trie/src/snapshot/job.rs create mode 100644 vendor/reth-optimism-trie/src/snapshot/mod.rs create mode 100644 vendor/reth-optimism-trie/src/snapshot/tests.rs create mode 100644 vendor/reth-optimism-trie/src/test_utils.rs create mode 100644 vendor/reth-optimism-trie/tests/lib.rs create mode 100644 vendor/reth-optimism-trie/tests/live.rs diff --git a/Cargo.lock b/Cargo.lock index a843e41..88071fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -85,7 +85,7 @@ dependencies = [ "alethia-reth-evm", "alethia-reth-primitives", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-evm", "alloy-hardforks", "alloy-primitives", @@ -118,7 +118,7 @@ version = "1.2.0" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-genesis", "alloy-hardforks", "alloy-primitives", @@ -223,7 +223,7 @@ dependencies = [ "alethia-reth-primitives", "alethia-reth-rpc", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-eth", "eyre", @@ -264,7 +264,7 @@ dependencies = [ "alethia-reth-evm", "alethia-reth-primitives", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-hardforks", "alloy-primitives", "alloy-rlp", @@ -305,7 +305,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "reth-chainspec", "reth-engine-local", "reth-engine-primitives", @@ -332,7 +332,7 @@ dependencies = [ "alethia-reth-primitives", "alethia-reth-rpc-types", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-hardforks", "alloy-json-rpc", "alloy-primitives", @@ -340,7 +340,7 @@ dependencies = [ "alloy-rpc-types-debug", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "async-trait", "eyre", "jsonrpsee", @@ -404,9 +404,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -419,27 +419,27 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.33" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" +checksum = "c36ddb69f5e41407e7a93aead3480f7c8ff076e73d01a951ae8f7ea4542c1ca0" dependencies = [ "alloy-primitives", "alloy-rlp", "num_enum", + "phf 0.14.0", "serde", - "strum", ] [[package]] name = "alloy-consensus" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbe4e5e9107bf6854e7550b666ca654ff2027eabf8153913e2e31ac4b089779" +checksum = "a6ff0c4adba2abdcd9fb5829ae5f4394c06f8585ed283a9ba79aa33763c802e1" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde", "alloy-trie", "alloy-tx-macros", "arbitrary", @@ -450,7 +450,7 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.5", + "rand 0.8.7", "secp256k1 0.30.0", "serde", "serde_json", @@ -460,24 +460,24 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88fc7bbfb98cf5605a35aadf0ba43a7d9f1608d6f220d05e4fbd5144d3b0b625" +checksum = "7cdf48932b1db3216175e19a2b476d89d53076e004850ee7983c6807ba0fde74" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde", "arbitrary", "serde", ] [[package]] name = "alloy-dyn-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -487,7 +487,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow 0.7.15", + "winnow 1.0.4", ] [[package]] @@ -500,7 +500,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "crc", - "rand 0.8.5", + "rand 0.8.7", "serde", "thiserror 2.0.18", ] @@ -515,7 +515,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "borsh", - "rand 0.8.5", + "rand 0.8.7", "serde", ] @@ -530,7 +530,7 @@ dependencies = [ "arbitrary", "borsh", "k256", - "rand 0.8.5", + "rand 0.8.7", "serde", "serde_with", "thiserror 2.0.18", @@ -538,45 +538,24 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "b3b12337f74cbfa451cb04dac173974814a6ff463079e1793aa09600ba8813ab" dependencies = [ "alloy-primitives", "alloy-rlp", "arbitrary", "borsh", + "once_cell", "serde", + "thiserror 2.0.18", ] [[package]] name = "alloy-eips" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" -dependencies = [ - "alloy-eip2124", - "alloy-eip2930", - "alloy-eip7702", - "alloy-eip7928", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 1.8.3", - "auto_impl", - "borsh", - "c-kzg", - "derive_more", - "either", - "serde", - "serde_with", - "sha2", -] - -[[package]] -name = "alloy-eips" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb4919fa34b268842f434bfafa9c09136ab7b1a87ce0dd40a61befa35b5408c" +checksum = "ea4c0453065b9206acc0f869a258dc8dcbbd595e144b4446f2c493a24a814d1f" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -584,7 +563,7 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde", "arbitrary", "auto_impl", "borsh", @@ -600,12 +579,12 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.33.2" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc4b83cb672156663e6094d098beb509965b7fe684bb3d6e44bb9ca2e9ae714" +checksum = "acde665074b478c97047dc2a461b4916cac77922d690e5b7415d1941ae7ff7bf" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -620,13 +599,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e111e22c1a2133e9ebfd9051ea0eaf63559594d2f50d43cbc6762fbb95fc3c2" +checksum = "027ba57264c5d05e4ff52e6090b3592e7526f5d5f5a844add6bbdd17c071c911" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde", "alloy-trie", "borsh", "serde", @@ -649,9 +628,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -661,9 +640,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b6af6f374c1eeef8ab8dc26232cd440db167322a4207a3debd3d1ee565ca47" +checksum = "c4691c60de5d628533752cd07e102d17c47874c06c04c91af33960fd94c484f4" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -676,19 +655,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a3f5a7f3678b71d33fcc45b714fab8928dbc647d5aff2145e72032d5c849bb" +checksum = "9d912bb639bf4ac31e83095afe9e907c8b9774ce0c405966228368309fcfc45f" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "alloy-signer", "alloy-sol-types", "async-trait", @@ -702,22 +681,22 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb50dc1fb0e0b2c8748d5bee1aa7acdd18f9e036311bc93a71d97be624030317" +checksum = "d875a11bd98595f57f73890f2e36f4a1cca0fa670623e59c9fb08b2389979c36" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", - "alloy-serde 2.0.0", + "alloy-serde", "serde", ] [[package]] name = "alloy-primitives" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" dependencies = [ "alloy-rlp", "arbitrary", @@ -726,33 +705,34 @@ dependencies = [ "const-hex", "derive_more", "fixed-cache", - "foldhash 0.2.0", - "getrandom 0.4.2", - "hashbrown 0.16.1", - "indexmap 2.13.1", + "foldhash", + "getrandom 0.4.3", + "hashbrown 0.17.1", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "proptest-derive", - "rand 0.9.2", + "proptest-derive 0.8.0", + "rand 0.9.5", "rapidhash", "ruint", "rustc-hash", + "secp256k1 0.31.1", "serde", - "sha3", + "sha3 0.11.0", ] [[package]] name = "alloy-provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ba5468f78c8893be2d68a7f2fda61753336e5653f006af19781001b5f99e6c" +checksum = "0e7d526d184d392a8fbcf4293e457789b307bb70646e4f8b902989a246529450" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -775,10 +755,10 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru", + "lru 0.16.4", "parking_lot", "pin-project", - "reqwest 0.13.2", + "reqwest", "serde", "serde_json", "thiserror 2.0.18", @@ -790,9 +770,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffcefb5d3391a320eadb95d398e4135f8cc35c7bf29a6bdb357eadcfc5ee5638" +checksum = "6fa5976a77e32a63152e937fa90d0dae1f8142b41a9a99a6386d6ea58934cfa6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -812,9 +792,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -823,20 +803,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +checksum = "9d4311c03125e8a18296504560b9de3d75ecbd0dcda7f71e6cf2a196d57e6fba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "alloy-rpc-client" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222fd4efff0fb9a25184684742c44fe9fa9a16c4ab5bf97583e71c86598ef8f0" +checksum = "755447dc13a04c6fa6db8dedb5d32ab5edfa4dd7aa34487ff192a3d873e0e8cf" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -847,7 +827,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.13.2", + "reqwest", "serde", "serde_json", "tokio", @@ -860,22 +840,22 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974df1e56405c27cb8242381f45d8b212ba9df5006046ccf704764a2a4634366" +checksum = "96deb317fe224e98a8ae17a7ed7ea63478867385bf50b7abd53642b24cfd811a" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "serde", ] [[package]] name = "alloy-rpc-types-admin" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1d057dcbacf8be8f689a7737e0d697fd40a2dc5b664c9035f182ff016649ea" +checksum = "3279cb55f7069c2fb1dbcd9ab2c6e79a02d63c92fd0b850addea0a3715d6b05f" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -885,38 +865,38 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06bc10b0dca4f5bfc3cd30ed46eab5d651b5bb2cd300d683bdcdf5d2bfe6e82c" +checksum = "9f89d27756bf3effdac25d07692724e840ce90ca1ff956a6b47dd2ae1612f597" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949c0f16a94ae33cdb1139b8dbf9e34d7f26ebfe97962e2a4d620b5f65f48fe4" +checksum = "911a513723ef0b90c3b072f65eebf54d6ebc8b651d06734e252d024bd89b88ac" dependencies = [ "alloy-consensus-any", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8f7fa8ca056bb797a368aeed329e6ace6b62ee4271432ac36ab8ae87a5e60d" +checksum = "81133671b8da58169589aac996f3c4da23bbdee85b13ecee7098065f3eae718a" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -932,9 +912,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301249e3c9e43661cfd7ebbb4746a00af6ce1ef58b5c968451882cd60438417d" +checksum = "b9e9c1f9ac81c56b2d96901201db7eb95c4e9fc3c21237a4690f8c652aa62089" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -945,37 +925,37 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59bc947935732cae5b072753e5e034c0b70a8b031c2839f45e2659ba07df9ae" +checksum = "5b1f682c4881aa7000ac7cc86d7aeeb3b09836a77a90b329820140233651aeea" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", "jsonwebtoken", - "rand 0.8.5", + "rand 0.8.7", "serde", - "strum", + "strum 0.27.2", ] [[package]] name = "alloy-rpc-types-eth" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc280a41931bd419af86e9e859dd9726b73313aaa2e479b33c0e344f4b892ddb" +checksum = "4e1bd19904581ee2075b61faabab1a4cefa8b96d8f2c85343adeae8ecf615e2b" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 2.0.0", + "alloy-serde", "alloy-sol-types", "arbitrary", "itertools 0.14.0", @@ -987,28 +967,28 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "286c40ce0d715217a5bfa9fb452779b11e6769e56680afa0de691ae8f3a848ac" +checksum = "54c635f0c45a871b55c276653e3838d1687d86282eeaf3d83a1914e02563b3f2" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-trace" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede0458c51bef23620aa6bd01a0b4f608be7bcb61d98e91b8530208ae545f3c2" +checksum = "796729a4e1783904c6040f11a503c4d55ddb16f69dc199ad15e50c4ad039f371" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "serde", "serde_json", "thiserror 2.0.18", @@ -1016,32 +996,21 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f4df183248b57f3e0b99054b1b6786769d3fdff6d01a702234068140c8ba76" +checksum = "8533c450895de79eb581875bfc317d1a239ae71aba79e20ee158fa153268f163" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", - "serde", -] - -[[package]] -name = "alloy-serde" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" -dependencies = [ - "alloy-primitives", + "alloy-serde", "serde", - "serde_json", ] [[package]] name = "alloy-serde" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4848831ff994c88b1c32b7df9c4c1c3eedea4b535bde5eb3c421ef0bdc5ac052" +checksum = "c1e97b3e0b9f816b25083045dcfa69431bd059a078e828e4d82d296d1949b96c" dependencies = [ "alloy-primitives", "arbitrary", @@ -1051,9 +1020,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b8ad9890b212e224291024b1aecfeef72127d27a2f6eebc5e347c40275c4bf" +checksum = "6913d06ccc0d9c6ab67e2f82e2fbe292706da1f91442f30cded2d04b56bf0d58" dependencies = [ "alloy-primitives", "async-trait", @@ -1066,9 +1035,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67d2372aada343130d41e249b59a3cef29b1678dcd3fd80f1c2c4d6b5318f2" +checksum = "c880813cd26bd1ddf8c2236e31295896efd1fcc5cc761d8894ae894503aeea53" dependencies = [ "alloy-consensus", "alloy-network", @@ -1079,48 +1048,48 @@ dependencies = [ "coins-bip39", "eth-keystore", "k256", - "rand 0.8.5", + "rand 0.8.7", "thiserror 2.0.18", "zeroize", ] [[package]] name = "alloy-sol-macro" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.1", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", - "sha3", - "syn 2.0.117", + "sha3 0.11.0", + "syn 2.0.118", "syn-solidity", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" dependencies = [ "const-hex", "dunce", @@ -1128,25 +1097,25 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" dependencies = [ "serde", - "winnow 0.7.15", + "winnow 1.0.4", ] [[package]] name = "alloy-sol-types" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -1156,9 +1125,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b7b755e64ae6b5de0d762ed2c780e072167ea5e542076a559e00314352a0bf" +checksum = "999adfe5c91035c6bf4c4210e0eb8d0caed79d76fbf5e1b70d78721f6097ac04" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -1179,14 +1148,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29980e69119444ed26b75e7ee5bed2043870f904a64318297e55800db686564" +checksum = "0e79a2c1793afc61eed9ca0963da370a988b27d2bbfa67c78013e262d47424c4" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest", "serde_json", "tower", "tracing", @@ -1195,9 +1164,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b27802653330740c88c28394cdaf1d55190b15b48ef9b99a946f37c9cdb19fa" +checksum = "c24422d5159996688b0c094babf1969cb0197d453902da483711c92c1624fea2" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -1215,9 +1184,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b71dc951db66795cfb52eef835f64cf15163bc93b656e061b457ce5ebff370" +checksum = "1bea36621cd4e4f06c1243e556b32fdc9c4db7b9b09b72a95a87383c0ffcc625" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1245,7 +1214,7 @@ dependencies = [ "derive_more", "nybbles", "proptest", - "proptest-derive", + "proptest-derive 0.7.0", "serde", "smallvec", "thiserror 2.0.18", @@ -1254,14 +1223,14 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8228b9236479ff16b03041b64b86c2bd4e53da1caa45d59b5868cd1571131e" +checksum = "406bc1183f6843e0aba09f7b3365e828b597213d60793ba5cb41befc863e3a78" dependencies = [ - "darling 0.23.0", + "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1325,9 +1294,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] [[package]] name = "aquamarine" @@ -1340,7 +1318,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1352,6 +1330,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1455,6 +1439,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "educe", + "num-bigint", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -1482,7 +1483,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.118", ] [[package]] @@ -1520,7 +1531,20 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -1594,13 +1618,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ - "ark-serialize-derive", + "ark-serialize-derive 0.5.0", "ark-std 0.5.0", "arrayvec", "digest 0.10.7", "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "num-bigint", + "serde_with", +] + [[package]] name = "ark-serialize-derive" version = "0.5.0" @@ -1609,7 +1646,18 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -1619,7 +1667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.7", ] [[package]] @@ -1629,7 +1677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.7", ] [[package]] @@ -1639,7 +1687,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.7", +] + +[[package]] +name = "ark-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand 0.8.7", ] [[package]] @@ -1650,9 +1708,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" dependencies = [ "serde", ] @@ -1665,9 +1723,9 @@ checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -1694,7 +1752,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1705,7 +1763,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1743,20 +1801,20 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -1765,14 +1823,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -1807,6 +1866,12 @@ dependencies = [ "match-lookup", ] +[[package]] +name = "base45" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240e56f4d3c453c36faacb695c535a4d5f8c7d23dac175014f32eb0a71012a03" + [[package]] name = "base64" version = "0.21.7" @@ -1872,7 +1937,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1880,8 +1945,8 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", - "syn 2.0.117", + "shlex 1.3.0", + "syn 2.0.118", ] [[package]] @@ -1900,41 +1965,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "bitcoin-io" -version = "0.1.4" +name = "bitcoin-consensus-encoding" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals", +] [[package]] -name = "bitcoin_hashes" -version = "0.14.1" +name = "bitcoin-internals" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" dependencies = [ - "bitcoin-io", - "hex-conservative", + "hex-conservative 0.3.2", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -1945,9 +2031,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -1967,12 +2053,12 @@ dependencies = [ ] [[package]] -name = "block-padding" -version = "0.3.3" +name = "block-buffer" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -1993,11 +2079,11 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6339a700715bda376f5ea65c76e8fe8fc880930d8b0638cea68e7f3da6538e0a" dependencies = [ - "bitflags 2.11.0", + "bitflags", "boa_interner", "boa_macros", "boa_string", - "indexmap 2.13.1", + "indexmap 2.14.0", "num-bigint", "rustc-hash", ] @@ -2010,7 +2096,7 @@ checksum = "1521be326f8a5c8887e95d4ce7f002917a002a23f7b93b9a6a2bf50ed4157824" dependencies = [ "aligned-vec", "arrayvec", - "bitflags 2.11.0", + "bitflags", "boa_ast", "boa_gc", "boa_interner", @@ -2029,7 +2115,7 @@ dependencies = [ "futures-lite", "hashbrown 0.16.1", "icu_normalizer", - "indexmap 2.13.1", + "indexmap 2.14.0", "intrusive-collections", "itertools 0.14.0", "num-bigint", @@ -2038,7 +2124,7 @@ dependencies = [ "num_enum", "paste", "portable-atomic", - "rand 0.9.2", + "rand 0.9.5", "regress", "rustc-hash", "ryu-js", @@ -2075,9 +2161,9 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.16.1", - "indexmap 2.13.1", + "indexmap 2.14.0", "once_cell", - "phf", + "phf 0.13.1", "rustc-hash", "static_assertions", ] @@ -2092,7 +2178,7 @@ dependencies = [ "cow-utils", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -2102,7 +2188,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd957fa9fa93e3a001a8aba5a5cd40c2bbfde486378be4c4b472fd304aaddb" dependencies = [ - "bitflags 2.11.0", + "bitflags", "boa_ast", "boa_interner", "boa_macros", @@ -2128,11 +2214,36 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "bon" +version = "3.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.118", +] + [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "borsh-derive", "bytes", @@ -2141,31 +2252,31 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "boyer-moore-magiclen" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7441b4796eb8a7107d4cd99d829810be75f5573e1081c37faa0e8094169ea0d6" +checksum = "43f0fdabfbc8017645223fd529c6077df2c491277e44e88ed8afd5afe54e715a" dependencies = [ "debug-helper", ] [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -2174,9 +2285,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -2194,9 +2305,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "by_address" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "byte-slice-cast" @@ -2206,22 +2323,22 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2232,9 +2349,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ "serde", ] @@ -2251,9 +2368,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.7" +version = "2.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +checksum = "38d04308254695569fdb9bfe3bacc1c91837a670d0806605eb82d63748fbd3a6" dependencies = [ "arbitrary", "blst", @@ -2266,62 +2383,29 @@ dependencies = [ ] [[package]] -name = "camino" -version = "1.2.2" +name = "castaway" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ - "serde_core", + "rustversion", ] [[package]] -name = "cargo-platform" -version = "0.3.2" +name = "cc" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ - "serde", - "serde_core", + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", ] [[package]] -name = "cargo_metadata" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.28", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.2.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" @@ -2346,18 +2430,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2366,7 +2461,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -2383,9 +2478,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -2405,14 +2500,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2457,7 +2552,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2 0.12.2", - "rand 0.8.5", + "rand 0.8.7", "sha2", "thiserror 1.0.69", ] @@ -2477,7 +2572,7 @@ dependencies = [ "ripemd", "serde", "sha2", - "sha3", + "sha3 0.10.9", "thiserror 1.0.69", ] @@ -2510,9 +2605,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -2524,9 +2619,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "brotli", "compression-core", @@ -2538,9 +2633,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concat-kdf" @@ -2553,9 +2648,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -2577,11 +2672,12 @@ checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -2610,6 +2706,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -2626,15 +2732,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "cow-utils" version = "0.1.3" @@ -2670,9 +2767,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" @@ -2691,18 +2788,18 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -2710,27 +2807,27 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crossterm" @@ -2738,7 +2835,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", "derive_more", "document-features", @@ -2788,6 +2885,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2821,17 +2927,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", + "syn 2.0.118", ] [[package]] @@ -2840,22 +2936,8 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "darling_core", + "darling_macro", ] [[package]] @@ -2869,18 +2951,7 @@ dependencies = [ "quote", "serde", "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2889,16 +2960,16 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.23.0", + "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "arbitrary", "cfg-if", @@ -2912,15 +2983,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "data-encoding-macro" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2928,19 +2999,19 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "debug-helper" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" +checksum = "80a4af69c60438a1a82af89d362f4729fd38db7b73f305a237636fad31ceb2bf" [[package]] name = "delay_map" @@ -2969,7 +3040,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -2992,7 +3062,7 @@ checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3003,38 +3073,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3056,7 +3095,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.117", + "syn 2.0.118", "unicode-xid", ] @@ -3081,12 +3120,22 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -3111,8 +3160,7 @@ dependencies = [ [[package]] name = "discv5" version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7999df38d0bd8f688212e1a4fae31fd2fea6d218649b9cd7c40bf3ec1318fc" +source = "git+https://github.com/sigp/discv5?rev=7663c00#7663c00ee0837ea98547caaedede95d9d6736f4d" dependencies = [ "aes", "aes-gcm", @@ -3131,7 +3179,7 @@ dependencies = [ "more-asserts", "multiaddr", "parking_lot", - "rand 0.8.5", + "rand 0.8.7", "smallvec", "socket2", "tokio", @@ -3142,13 +3190,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3166,6 +3214,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "dunce" version = "1.0.5" @@ -3195,7 +3249,7 @@ checksum = "1ec431cd708430d5029356535259c5d645d60edd3d39c54e5eea9782d46caa7d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3247,14 +3301,14 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -3292,43 +3346,31 @@ dependencies = [ "hex", "k256", "log", - "rand 0.8.5", + "rand 0.8.7", "secp256k1 0.30.0", "serde", - "sha3", + "sha3 0.10.9", "zeroize", ] -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "enum-ordinalize" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3348,7 +3390,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3379,12 +3421,12 @@ dependencies = [ "hex", "hmac", "pbkdf2 0.11.0", - "rand 0.8.5", + "rand 0.8.7", "scrypt", "serde", "serde_json", "sha2", - "sha3", + "sha3 0.10.9", "thiserror 1.0.69", "uuid 0.8.2", ] @@ -3402,9 +3444,9 @@ dependencies = [ [[package]] name = "ethereum_serde_utils" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +checksum = "38df44a7a271ab43835678f9215b53cc2523e4714a215da6643d83dc110245da" dependencies = [ "alloy-primitives", "hex", @@ -3415,9 +3457,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +checksum = "e462875ad8693755ea8913d6e905715c76ea4836e2254e18c9cf0f7a8f8c2a13" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -3430,14 +3472,25 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" +checksum = "daf022360bdbe9456eda5f35718a50476d5b2a0d51a97ed4eae27420737a6fba" dependencies = [ - "darling 0.23.0", + "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", ] [[package]] @@ -3456,6 +3509,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "2.4.1" @@ -3512,13 +3571,12 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -3529,9 +3587,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed-cache" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41c7aa69c00ebccf06c3fa7ffe2a6cf26a58b5fe4deabfe646285ff48136a8f" +checksum = "2fe63500644ef0269fe6b744e7e5dc5c20b5eebf3d881bc2be53f194636f6583" dependencies = [ "equivalent", "rapidhash", @@ -3545,7 +3603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.7", "rustc-hex", "static_assertions", ] @@ -3568,7 +3626,7 @@ checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3603,12 +3661,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -3624,6 +3676,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3727,7 +3788,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3744,12 +3805,12 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" dependencies = [ "gloo-timers", - "send_wrapper 0.4.0", + "send_wrapper", ] [[package]] @@ -3775,6 +3836,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3806,24 +3882,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -3838,15 +3913,14 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ - "bitflags 2.11.0", + "bitflags", "libc", "libgit2-sys", "log", - "url", ] [[package]] @@ -3878,9 +3952,9 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" dependencies = [ "futures-channel", "futures-core", @@ -3901,6 +3975,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gmp-mpfr-sys" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db155b537cb791b133341f99f68371d86ee7fa4c79aacfbc376d72d23c70531" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "group" version = "0.13.0" @@ -3914,9 +3998,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -3924,7 +4008,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.1", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -3937,6 +4021,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + [[package]] name = "hashbrown" version = "0.12.3" @@ -3962,7 +4052,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", - "foldhash 0.1.5", ] [[package]] @@ -3973,16 +4062,27 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", "serde", "serde_core", ] [[package]] name = "hashlink" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ "hashbrown 0.16.1", ] @@ -4025,48 +4125,81 @@ dependencies = [ ] [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hex-conservative" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", + "hickory-proto", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.2", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", "idna", "ipnet", + "jni 0.22.4", "once_cell", - "rand 0.9.2", + "prefix-trie", + "rand 0.10.2", "ring", "serde", "thiserror 2.0.18", "tinyvec", - "tokio", "tracing", "url", ] [[package]] name = "hickory-resolver" -version = "0.25.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", + "ipnet", + "jni 0.22.4", "moka", + "ndk-context", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.10.2", "resolv-conf", "serde", "smallvec", + "system-configuration", "thiserror 2.0.18", "tokio", "tracing", @@ -4092,9 +4225,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -4102,9 +4235,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -4112,9 +4245,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -4149,9 +4282,9 @@ checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" [[package]] name = "humantime" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" [[package]] name = "humantime-serde" @@ -4163,11 +4296,20 @@ dependencies = [ "serde", ] +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -4187,17 +4329,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "log", "rustls", - "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -4251,7 +4391,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4353,14 +4493,8 @@ dependencies = [ ] [[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "ident_case" -version = "1.0.1" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" @@ -4395,6 +4529,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "imbl" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e525189e5f603908d0c6e0d402cb5de9c4b2c8866151fabc4ebd771ed2630a2e" +dependencies = [ + "archery", + "bitmaps", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -4412,7 +4571,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4453,13 +4612,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -4475,20 +4634,20 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" dependencies = [ - "bitflags 2.11.0", + "bitflags", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" dependencies = [ "libc", ] @@ -4499,7 +4658,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", "generic-array", ] @@ -4509,18 +4667,18 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling 0.23.0", + "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -4528,7 +4686,7 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4549,7 +4707,7 @@ dependencies = [ "socket2", "widestring", "windows-registry", - "windows-result 0.4.1", + "windows-result", "windows-sys 0.61.2", ] @@ -4558,14 +4716,7 @@ name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ - "memchr", "serde", ] @@ -4624,6 +4775,36 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.118", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -4649,28 +4830,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -4733,7 +4913,7 @@ dependencies = [ "jsonrpsee-types", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.5", "rustc-hash", "serde", "serde_json", @@ -4778,7 +4958,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4848,9 +5028,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.3.0" +version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ "aws-lc-rs", "base64 0.22.1", @@ -4861,6 +5041,7 @@ dependencies = [ "serde_json", "signature", "simple_asn1", + "zeroize", ] [[package]] @@ -4898,21 +5079,46 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "keccak-asm" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" dependencies = [ "digest 0.10.7", "sha3-asm", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "kqueue" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ "kqueue-sys", "libc", @@ -4920,11 +5126,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", + "bitflags", "libc", ] @@ -4935,22 +5141,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "left-right" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.3+1.9.2" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" dependencies = [ "cc", "libc", @@ -4965,7 +5176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4976,9 +5187,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libp2p-identity" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +checksum = "9525f3831544f7ae497bde79adf114ef127b0fbbb97edbbf692a80408636421c" dependencies = [ "asn1_der", "bs58", @@ -4986,7 +5197,7 @@ dependencies = [ "hkdf", "k256", "multihash", - "quick-protobuf", + "prost", "sha2", "thiserror 2.0.18", "tracing", @@ -5006,14 +5217,11 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.11.0", "libc", - "plain", - "redox_syscall 0.7.3", ] [[package]] @@ -5034,9 +5242,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.28" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -5050,7 +5258,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -5099,19 +5307,41 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber 0.3.23", +] [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +dependencies = [ + "hashbrown 0.17.1", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -5139,9 +5369,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c23545df7ecf1b16c303910a69b079e8e251d60f7dd2cc9b4177f2afaf1746" +checksum = "90071f8077f8e40adfc4b7fe9cd495ce316263f19e75c2211eeff3fdf475a3d9" [[package]] name = "mach2" @@ -5160,13 +5390,13 @@ checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "macro-string" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5177,7 +5407,7 @@ checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5191,15 +5421,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -5215,12 +5445,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -5231,17 +5461,18 @@ checksum = "161ab904c2c62e7bda0f7562bf22f96440ca35ff79e66c800cbac298f2f4f5ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "metrics-exporter-prometheus" -version = "0.18.1" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" +checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" dependencies = [ "base64 0.22.1", - "indexmap 2.13.1", + "evmap", + "indexmap 2.14.0", "metrics", "metrics-util", "quanta", @@ -5261,22 +5492,23 @@ dependencies = [ "once_cell", "procfs", "rlimit", - "windows 0.62.2", + "windows", ] [[package]] name = "metrics-util" -version = "0.20.1" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13" dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.5", "rand_xoshiro", + "rapidhash", "sketches-ddsketch", ] @@ -5314,9 +5546,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", @@ -5324,6 +5556,32 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mockall" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "modular-bitfield" version = "0.13.1" @@ -5342,7 +5600,7 @@ checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5359,7 +5617,7 @@ dependencies = [ "portable-atomic", "smallvec", "tagptr", - "uuid 1.23.0", + "uuid 1.23.5", ] [[package]] @@ -5389,26 +5647,32 @@ dependencies = [ [[package]] name = "multibase" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +checksum = "7e0e4a371cbf1dfd666b658ba137763edb23c45beb43cfe369b5593cd6b437b6" dependencies = [ "base-x", "base256emoji", + "base45", "data-encoding", "data-encoding-macro", ] [[package]] name = "multihash" -version = "0.19.3" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" dependencies = [ - "core2", "unsigned-varint", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nom" version = "7.1.3" @@ -5419,13 +5683,22 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" +dependencies = [ + "serde", +] + [[package]] name = "notify" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags", "fsevent-sys", "inotify", "kqueue", @@ -5443,7 +5716,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -5480,9 +5753,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -5500,9 +5773,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -5515,11 +5788,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -5574,7 +5846,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5607,7 +5879,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -5656,9 +5928,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -5670,9 +5942,9 @@ dependencies = [ [[package]] name = "opentelemetry-appender-tracing" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +checksum = "2c0080f0dc1d7c786f467cd85a4e395fcab11ee852004f39a29a18ab7c25d837" dependencies = [ "opentelemetry", "tracing", @@ -5682,22 +5954,22 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" dependencies = [ "async-trait", "bytes", "http", "opentelemetry", - "reqwest 0.12.28", + "reqwest", ] [[package]] name = "opentelemetry-otlp" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ "http", "opentelemetry", @@ -5705,18 +5977,18 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest 0.12.28", + "reqwest", "thiserror 2.0.18", "tokio", "tonic", - "tracing", + "tonic-types", ] [[package]] name = "opentelemetry-proto" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -5727,22 +5999,23 @@ dependencies = [ [[package]] name = "opentelemetry-semantic-conventions" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" +checksum = "c913ac17a6c451661ee255f4625d143e51647ae78ebd969b75e41c4442f4fe47" [[package]] name = "opentelemetry_sdk" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ "futures-channel", "futures-executor", "futures-util", "opentelemetry", "percent-encoding", - "rand 0.9.2", + "portable-atomic", + "rand 0.9.5", "thiserror 2.0.18", ] @@ -5768,6 +6041,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -5794,7 +6091,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5821,9 +6118,9 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5869,9 +6166,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" dependencies = [ "memchr", "ucd-trie", @@ -5894,10 +6191,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6" +dependencies = [ + "phf_shared 0.14.0", +] + [[package]] name = "phf_generator" version = "0.13.1" @@ -5905,7 +6211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.13.1", ] [[package]] @@ -5915,10 +6221,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5930,24 +6236,33 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5974,15 +6289,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain_hasher" @@ -6035,6 +6344,43 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -6052,7 +6398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6103,7 +6449,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6121,7 +6467,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.11.0", + "bitflags", "chrono", "flate2", "procfs-core", @@ -6134,7 +6480,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.11.0", + "bitflags", "chrono", "hex", ] @@ -6147,9 +6493,9 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.0", + "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.5", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -6176,14 +6522,25 @@ checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "proptest-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57924a81864dddafba92e1bf92f9bf82f97096c44489548a60e888e1547549b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -6191,15 +6548,24 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", ] [[package]] @@ -6223,20 +6589,11 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-protobuf" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" -dependencies = [ - "byteorder", -] - [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -6254,15 +6611,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.2", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", "rustls", @@ -6276,23 +6634,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -6317,9 +6675,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -6329,15 +6687,26 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", "serde", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -6377,6 +6746,21 @@ dependencies = [ "serde", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -6397,40 +6781,42 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.4.1" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" dependencies = [ - "rand 0.9.2", + "getrandom 0.3.4", "rustversion", ] [[package]] name = "ratatui" -version = "0.30.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-widgets", + "serde", ] [[package]] name = "ratatui-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.11.0", + "bitflags", "compact_str", - "hashbrown 0.16.1", - "indoc", + "hashbrown 0.17.1", "itertools 0.14.0", "kasuari", - "lru", - "strum", + "lru 0.18.1", + "palette", + "serde", + "strum 0.28.0", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", @@ -6439,9 +6825,9 @@ dependencies = [ [[package]] name = "ratatui-crossterm" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ "cfg-if", "crossterm", @@ -6451,18 +6837,19 @@ dependencies = [ [[package]] name = "ratatui-widgets" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.16.1", + "bitflags", + "hashbrown 0.17.1", "indoc", "instability", "itertools 0.14.0", "line-clipping", "ratatui-core", - "strum", + "serde", + "strum 0.28.0", "time", "unicode-segmentation", "unicode-width", @@ -6474,14 +6861,14 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -6509,16 +6896,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -6549,14 +6927,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -6566,9 +6944,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -6577,9 +6955,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "regress" @@ -6593,49 +6971,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "reqwest" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -6655,7 +6993,7 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", - "rustls-platform-verifier 0.6.2", + "rustls-platform-verifier 0.7.0", "serde", "serde_json", "serde_urlencoded", @@ -6681,8 +7019,8 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "reth" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -6722,11 +7060,11 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "futures-core", "futures-util", @@ -6749,11 +7087,11 @@ dependencies = [ [[package]] name = "reth-chain-state" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-signer", "alloy-signer-local", @@ -6761,7 +7099,7 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.5", "rayon", "reth-chainspec", "reth-errors", @@ -6770,9 +7108,10 @@ dependencies = [ "reth-metrics", "reth-primitives-traits", "reth-storage-api", + "reth-tasks", "reth-trie", - "revm-database", - "revm-state", + "reth-trie-sparse", + "revm", "serde", "tokio", "tokio-stream", @@ -6781,12 +7120,12 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -6801,8 +7140,8 @@ dependencies = [ [[package]] name = "reth-cli" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-genesis", "clap", @@ -6814,12 +7153,12 @@ dependencies = [ [[package]] name = "reth-cli-commands" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "backon", @@ -6838,7 +7177,7 @@ dependencies = [ "parking_lot", "ratatui", "rayon", - "reqwest 0.13.2", + "reqwest", "reth-chainspec", "reth-cli", "reth-cli-runner", @@ -6886,6 +7225,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "serde_json", + "socket2", "tar", "tokio", "tokio-stream", @@ -6897,8 +7237,8 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "reth-tasks", "tokio", @@ -6907,15 +7247,15 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "cfg-if", "eyre", "libc", - "rand 0.8.5", + "rand 0.8.7", "reth-fs-util", "secp256k1 0.30.0", "serde", @@ -6926,12 +7266,12 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a79b3247ae4fbb1d4d35ce83a11fc596428a4c6ea836c98a75a55340192578a4" +checksum = "eb43f4858fef4e3b42b80954d2edf1ff67c8ad7498347083cdc7d0f6eff2f081" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -6947,19 +7287,19 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5dbae40c272b8a1b4fcc08ee2d4e77d3b0ccdb187c1313f412a73ff54ff2a2" +checksum = "5798ae4d6b264764bc0d742468bd32c7fb515e774645d4930f95ff6311f3ae14" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "reth-config" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "eyre", "humantime-serde", @@ -6974,10 +7314,11 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", + "alloy-eip7928", "alloy-primitives", "auto_impl", "reth-execution-types", @@ -6987,11 +7328,11 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -7000,11 +7341,11 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-json-rpc", "alloy-primitives", "alloy-provider", @@ -7014,9 +7355,8 @@ dependencies = [ "derive_more", "eyre", "futures", - "reqwest 0.13.2", + "reqwest", "reth-node-api", - "reth-primitives-traits", "reth-tracing", "ringbuffer", "serde", @@ -7026,12 +7366,13 @@ dependencies = [ [[package]] name = "reth-db" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "derive_more", "eyre", + "libc", "metrics", "page_size", "parking_lot", @@ -7045,7 +7386,7 @@ dependencies = [ "reth-storage-errors", "reth-tracing", "rustc-hash", - "strum", + "strum 0.27.2", "sysinfo", "tempfile", "thiserror 2.0.18", @@ -7054,8 +7395,8 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7080,8 +7421,8 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7110,10 +7451,10 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "arbitrary", "bytes", @@ -7125,8 +7466,8 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7134,7 +7475,7 @@ dependencies = [ "enr", "itertools 0.14.0", "parking_lot", - "rand 0.8.5", + "rand 0.8.7", "reth-ethereum-forks", "reth-net-banlist", "reth-net-nat", @@ -7150,8 +7491,8 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7161,7 +7502,7 @@ dependencies = [ "futures", "itertools 0.14.0", "metrics", - "rand 0.9.2", + "rand 0.9.5", "reth-chainspec", "reth-ethereum-forks", "reth-metrics", @@ -7174,8 +7515,8 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "dashmap", @@ -7198,11 +7539,11 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "async-compression", @@ -7229,13 +7570,12 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "aes", "alloy-primitives", "alloy-rlp", - "block-padding", "byteorder", "cipher", "concat-kdf", @@ -7244,7 +7584,7 @@ dependencies = [ "futures", "hmac", "pin-project", - "rand 0.8.5", + "rand 0.8.7", "reth-network-peers", "secp256k1 0.30.0", "sha2", @@ -7257,8 +7597,8 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7280,11 +7620,11 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -7293,10 +7633,12 @@ dependencies = [ "reth-errors", "reth-ethereum-primitives", "reth-evm", + "reth-execution-errors", "reth-execution-types", "reth-payload-builder-primitives", "reth-payload-primitives", "reth-primitives-traits", + "reth-storage-api", "reth-trie-common", "serde", "thiserror 2.0.18", @@ -7305,12 +7647,12 @@ dependencies = [ [[package]] name = "reth-engine-tree" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-eip7928", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -7318,10 +7660,9 @@ dependencies = [ "crossbeam-channel", "derive_more", "futures", - "indexmap 2.13.1", + "indexmap 2.14.0", "metrics", "moka", - "parking_lot", "rayon", "reth-chain-state", "reth-consensus", @@ -7348,7 +7689,6 @@ dependencies = [ "reth-trie-parallel", "reth-trie-sparse", "revm", - "revm-primitives", "schnellru", "thiserror 2.0.18", "tokio", @@ -7357,10 +7697,12 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", + "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "eyre", "futures", @@ -7385,29 +7727,32 @@ dependencies = [ [[package]] name = "reth-era" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", + "alloy-rpc-types-beacon", + "alloy-rpc-types-engine", "ethereum_ssz", "ethereum_ssz_derive", + "sha2", "snap", "thiserror 2.0.18", ] [[package]] name = "reth-era-downloader" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "bytes", "eyre", "futures-util", - "reqwest 0.13.2", + "reqwest", "reth-era", "reth-fs-util", "sha2", @@ -7416,11 +7761,12 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", + "alloy-rlp", "eyre", "futures-util", "reth-db-api", @@ -7438,8 +7784,8 @@ dependencies = [ [[package]] name = "reth-errors" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -7449,8 +7795,8 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7477,13 +7823,13 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-eip7928", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-hardforks", "alloy-primitives", "alloy-rlp", @@ -7499,8 +7845,8 @@ dependencies = [ [[package]] name = "reth-ethereum" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -7519,8 +7865,8 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "clap", "eyre", @@ -7542,11 +7888,11 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -7558,10 +7904,10 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "reth-engine-primitives", @@ -7574,8 +7920,8 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -7587,11 +7933,11 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", @@ -7617,11 +7963,11 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-eth", "reth-codecs", @@ -7631,8 +7977,8 @@ dependencies = [ [[package]] name = "reth-etl" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "rayon", "reth-db-api", @@ -7641,11 +7987,12 @@ dependencies = [ [[package]] name = "reth-evm" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eip7928", + "alloy-eips", "alloy-evm", "alloy-primitives", "auto_impl", @@ -7665,11 +8012,11 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -7685,8 +8032,8 @@ dependencies = [ [[package]] name = "reth-execution-cache" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "fixed-cache", @@ -7703,8 +8050,8 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7716,11 +8063,11 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-evm", "alloy-primitives", "alloy-rlp", @@ -7735,11 +8082,11 @@ dependencies = [ [[package]] name = "reth-exex" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "eyre", "futures", @@ -7773,10 +8120,10 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "reth-chain-state", "reth-execution-types", @@ -7787,8 +8134,8 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "serde", "serde_json", @@ -7797,8 +8144,8 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7817,16 +8164,14 @@ dependencies = [ "reth-tracing", "reth-trie", "revm", - "revm-bytecode", - "revm-database", "serde", "serde_json", ] [[package]] name = "reth-ipc" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "bytes", "futures", @@ -7845,10 +8190,10 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "bitflags 2.11.0", + "bitflags", "byteorder", "crossbeam-queue", "dashmap", @@ -7862,8 +8207,8 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "bindgen", "cc", @@ -7871,20 +8216,21 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "futures", "metrics", "metrics-derive", + "reth-primitives-traits", "tokio", "tokio-util", ] [[package]] name = "reth-net-banlist" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "ipnet", @@ -7892,12 +8238,12 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "futures-util", "if-addrs", - "reqwest 0.13.2", + "reqwest", "serde_with", "thiserror 2.0.18", "tokio", @@ -7906,11 +8252,11 @@ dependencies = [ [[package]] name = "reth-network" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "aquamarine", @@ -7923,8 +8269,8 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.7", + "rand 0.9.5", "rayon", "reth-chainspec", "reth-consensus", @@ -7953,6 +8299,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "smallvec", + "socket2", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -7962,8 +8309,8 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7987,11 +8334,12 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eip7928", + "alloy-eips", "alloy-primitives", "auto_impl", "derive_more", @@ -8009,8 +8357,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8024,8 +8372,8 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -8038,8 +8386,8 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "anyhow", "bincode 1.3.3", @@ -8055,8 +8403,8 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -8079,11 +8427,11 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -8147,11 +8495,11 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "clap", @@ -8161,7 +8509,7 @@ dependencies = [ "futures", "humantime", "ipnet", - "rand 0.9.2", + "rand 0.9.5", "reth-chainspec", "reth-cli-util", "reth-config", @@ -8191,7 +8539,7 @@ dependencies = [ "reth-transaction-pool", "secp256k1 0.30.0", "serde", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "toml", "tracing", @@ -8202,14 +8550,20 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-consensus", + "alloy-eips", "alloy-network", + "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "ethereum_ssz", + "ethereum_ssz_derive", "eyre", + "http-body-util", + "jsonrpsee", "reth-chainspec", "reth-engine-local", "reth-engine-primitives", @@ -8222,6 +8576,7 @@ dependencies = [ "reth-network", "reth-node-api", "reth-node-builder", + "reth-node-core", "reth-payload-primitives", "reth-primitives-traits", "reth-provider", @@ -8235,13 +8590,16 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "revm", + "serde", + "serde_json", "tokio", + "tower", ] [[package]] name = "reth-node-ethstats" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8264,11 +8622,11 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -8288,8 +8646,8 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "bytes", "eyre", @@ -8301,7 +8659,7 @@ dependencies = [ "metrics-process", "metrics-util", "procfs", - "reqwest 0.13.2", + "reqwest", "reth-metrics", "reth-tasks", "tikv-jemalloc-ctl", @@ -8312,8 +8670,8 @@ dependencies = [ [[package]] name = "reth-node-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "reth-chainspec", "reth-db-api", @@ -8325,31 +8683,46 @@ dependencies = [ [[package]] name = "reth-optimism-trie" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism?rev=bcf489eaca2218e5063ca2d188a25f9c83ccd3f3#bcf489eaca2218e5063ca2d188a25f9c83ccd3f3" dependencies = [ - "alloy-eips 2.0.0", + "alloy-consensus", + "alloy-eips", + "alloy-genesis", "alloy-primitives", "auto_impl", "bincode 2.0.1", "bytes", + "crossbeam-channel", "derive_more", "eyre", "metrics", + "mockall", "parking_lot", + "reth-chainspec", "reth-codecs", "reth-db", + "reth-db-api", + "reth-db-common", "reth-ethereum-primitives", "reth-evm", + "reth-evm-ethereum", "reth-execution-errors", "reth-metrics", + "reth-node-api", + "reth-optimism-trie", "reth-primitives-traits", "reth-provider", "reth-revm", + "reth-storage-errors", "reth-tasks", "reth-trie", "reth-trie-common", + "reth-trie-db", + "secp256k1 0.31.1", "serde", - "strum", + "serial_test", + "strum 0.27.2", + "tempfile", + "test-case", "thiserror 2.0.18", "tokio", "tracing", @@ -8357,8 +8730,8 @@ dependencies = [ [[package]] name = "reth-payload-builder" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8381,8 +8754,8 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8393,17 +8766,16 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "auto_impl", "either", - "reth-chain-state", "reth-chainspec", "reth-errors", "reth-execution-types", @@ -8417,8 +8789,8 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8427,12 +8799,12 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc759fd87c3f65440e5d3bfa3107fe8a13a61a6807cd485c62c49d63c7bf6717" +checksum = "f59c928b2865af5bcdbce94800270b7f7798f27a22ca0aabdd2b7d3e6587c9ce" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -8460,11 +8832,12 @@ dependencies = [ [[package]] name = "reth-provider" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eip7928", + "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", @@ -8494,27 +8867,26 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-tasks", + "reth-tokio-util", "reth-trie", "reth-trie-db", - "revm-database", - "revm-state", + "revm", "rocksdb", - "strum", + "smallvec", + "strum 0.27.2", "tokio", "tracing", ] [[package]] name = "reth-prune" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", "alloy-primitives", "itertools 0.14.0", "metrics", - "rayon", "reth-config", "reth-db-api", "reth-errors", @@ -8535,8 +8907,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "arbitrary", @@ -8544,15 +8916,15 @@ dependencies = [ "modular-bitfield", "reth-codecs", "serde", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "tracing", ] [[package]] name = "reth-revm" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8566,12 +8938,13 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.0", + "alloy-eip7928", + "alloy-eips", "alloy-evm", "alloy-genesis", "alloy-network", @@ -8587,7 +8960,7 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.0", + "alloy-serde", "alloy-signer", "alloy-signer-local", "async-trait", @@ -8615,6 +8988,7 @@ dependencies = [ "reth-network-peers", "reth-network-types", "reth-node-api", + "reth-payload-primitives", "reth-primitives-traits", "reth-revm", "reth-rpc-api", @@ -8625,12 +8999,10 @@ dependencies = [ "reth-rpc-server-types", "reth-storage-api", "reth-tasks", - "reth-tracing", "reth-transaction-pool", "reth-trie-common", "revm", "revm-inspectors", - "revm-primitives", "serde", "serde_json", "sha2", @@ -8643,10 +9015,10 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-genesis", "alloy-json-rpc", "alloy-primitives", @@ -8660,7 +9032,7 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 2.0.0", + "alloy-serde", "jsonrpsee", "reth-chain-state", "reth-engine-primitives", @@ -8673,8 +9045,8 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-network", "alloy-provider", @@ -8716,8 +9088,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-evm", @@ -8736,10 +9108,10 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", @@ -8767,13 +9139,13 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eip7928", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-evm", "alloy-json-rpc", "alloy-network", @@ -8781,7 +9153,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-rpc-types-mev", - "alloy-serde 2.0.0", + "alloy-serde", "async-trait", "auto_impl", "dyn-clone", @@ -8796,6 +9168,7 @@ dependencies = [ "reth-network-api", "reth-node-api", "reth-primitives-traits", + "reth-prune-types", "reth-revm", "reth-rpc-convert", "reth-rpc-eth-types", @@ -8813,16 +9186,19 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ + "alloy-chains", "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eip7928", + "alloy-eips", "alloy-evm", "alloy-network", "alloy-primitives", "alloy-rpc-client", "alloy-rpc-types-eth", + "alloy-serde", "alloy-sol-types", "alloy-transport", "derive_more", @@ -8831,8 +9207,8 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", - "rand 0.9.2", - "reqwest 0.13.2", + "rand 0.9.5", + "reqwest", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -8861,8 +9237,8 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-rpc-types-engine", "http", @@ -8875,10 +9251,10 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "jsonrpsee-core", @@ -8886,14 +9262,14 @@ dependencies = [ "reth-errors", "reth-network-api", "serde", - "strum", + "strum 0.27.2", ] [[package]] name = "reth-rpc-traits" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b766da61ec7c46596386b4bc88d9b57d1939d3da2bc9e927567a8a23650e5ce9" +checksum = "ace77dbcdd59c014fda4565ebfec76cd1fe6f5d98584b4655e8d22a947b9eee3" dependencies = [ "alloy-consensus", "alloy-network", @@ -8906,11 +9282,10 @@ dependencies = [ [[package]] name = "reth-stages" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", "eyre", @@ -8919,7 +9294,7 @@ dependencies = [ "num-traits", "page_size", "rayon", - "reqwest 0.13.2", + "reqwest", "reth-chainspec", "reth-codecs", "reth-config", @@ -8955,10 +9330,10 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "aquamarine", "auto_impl", @@ -8983,8 +9358,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "arbitrary", @@ -8997,8 +9372,8 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "parking_lot", @@ -9017,8 +9392,8 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "clap", @@ -9026,17 +9401,18 @@ dependencies = [ "fixed-map", "reth-stages-types", "serde", - "strum", + "strum 0.27.2", "tracing", ] [[package]] name = "reth-storage-api" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eip7928", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9049,17 +9425,18 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "reth-storage-errors", + "reth-tokio-util", "reth-trie-common", - "revm-database", + "revm", "serde_json", ] [[package]] name = "reth-storage-errors" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "derive_more", @@ -9067,15 +9444,14 @@ dependencies = [ "reth-primitives-traits", "reth-prune-types", "reth-static-file-types", - "revm-database-interface", - "revm-state", + "revm", "thiserror 2.0.18", ] [[package]] name = "reth-tasks" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "crossbeam-utils", "dashmap", @@ -9095,8 +9471,8 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "tokio", "tokio-stream", @@ -9105,8 +9481,8 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "clap", "eyre", @@ -9114,6 +9490,7 @@ dependencies = [ "rolling-file", "tracing", "tracing-appender", + "tracing-chrome", "tracing-journald", "tracing-logfmt", "tracing-samply", @@ -9122,9 +9499,10 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ + "base64 0.22.1", "clap", "eyre", "opentelemetry", @@ -9140,22 +9518,23 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "aquamarine", "auto_impl", - "bitflags 2.11.0", + "bitflags", "futures-util", + "imbl", "metrics", "parking_lot", "paste", "pin-project", - "rand 0.9.2", + "rand 0.9.5", "reth-chain-state", "reth-chainspec", "reth-eth-wire-types", @@ -9169,8 +9548,6 @@ dependencies = [ "reth-storage-api", "reth-tasks", "revm", - "revm-interpreter", - "revm-primitives", "rustc-hash", "schnellru", "serde", @@ -9184,11 +9561,11 @@ dependencies = [ [[package]] name = "reth-trie" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", - "alloy-eips 2.0.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -9203,21 +9580,20 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "reth-trie-sparse", - "revm-database", "tracing", "triehash", ] [[package]] name = "reth-trie-common" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde 2.0.0", + "alloy-serde", "alloy-trie", "arbitrary", "arrayvec", @@ -9230,15 +9606,15 @@ dependencies = [ "rayon", "reth-codecs", "reth-primitives-traits", - "revm-database", + "revm", "serde", "serde_with", ] [[package]] name = "reth-trie-db" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", "metrics", @@ -9247,7 +9623,6 @@ dependencies = [ "reth-execution-errors", "reth-metrics", "reth-primitives-traits", - "reth-stages-types", "reth-storage-api", "reth-storage-errors", "reth-trie", @@ -9257,19 +9632,15 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ - "alloy-eip7928", "alloy-evm", "alloy-primitives", "alloy-rlp", "crossbeam-channel", "crossbeam-utils", - "derive_more", - "itertools 0.14.0", "metrics", - "rayon", "reth-execution-errors", "reth-metrics", "reth-primitives-traits", @@ -9277,21 +9648,19 @@ dependencies = [ "reth-storage-errors", "reth-tasks", "reth-trie", - "reth-trie-sparse", - "revm-state", + "revm", "thiserror 2.0.18", "tracing", ] [[package]] name = "reth-trie-sparse" -version = "2.0.0" -source = "git+https://github.com/paradigmxyz/reth?rev=27bfddeada3953edc22759080a3659ccea62ca1f#27bfddeada3953edc22759080a3659ccea62ca1f" +version = "2.4.0" +source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" dependencies = [ "alloy-primitives", - "alloy-rlp", "alloy-trie", - "auto_impl", + "either", "metrics", "rayon", "reth-execution-errors", @@ -9302,23 +9671,24 @@ dependencies = [ "serde_json", "slotmap", "smallvec", + "strum 0.27.2", "tracing", ] [[package]] name = "reth-zstd-compressors" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa54c341d926ec9b0f7fc0c87f831aa4959de699e69caab1a0bfd914326c09" +checksum = "e1bd4ae4f54a2ba813eef082910bc646bd31cab8ed134308fa71f1068331fb84" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "38.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" +checksum = "9d51c254839fb36357d0b02d02e46ba57e683d7b017e2da33b47ae74685a9e59" dependencies = [ "revm-bytecode", "revm-context", @@ -9335,21 +9705,21 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "10.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" +checksum = "e33493cf6d996e9242db4f2002caef48ec9aa8c4f3fff3b18aed10ef3942eec7" dependencies = [ "bitvec", - "phf", + "phf 0.13.1", "revm-primitives", "serde", ] [[package]] name = "revm-context" -version = "16.0.1" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" +checksum = "86e48fc736d9542c082ea5305be44b6a14b53a615f0147ad57f62189fd813068" dependencies = [ "bitvec", "cfg-if", @@ -9364,9 +9734,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "17.0.1" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" +checksum = "ac68282a454318246817486835a446519291dd0a8167d09de14c7e96f2147696" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -9380,11 +9750,13 @@ dependencies = [ [[package]] name = "revm-database" -version = "13.0.1" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" +checksum = "d761681a43e48408ea23f7f66ff56ff7b6128cadb8f9135d1b94787d0c0fc6b4" dependencies = [ - "alloy-eips 1.8.3", + "alloy-eips", + "derive_more", + "either", "revm-bytecode", "revm-database-interface", "revm-primitives", @@ -9394,9 +9766,9 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "11.0.1" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" +checksum = "b96e37d62fa60b45987b408486c84ebef4af4e7ddf1b3b96b4955a7263fd4e92" dependencies = [ "auto_impl", "either", @@ -9408,9 +9780,9 @@ dependencies = [ [[package]] name = "revm-handler" -version = "18.1.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" +checksum = "f5e043ebfc0899c435e44475796285898e4f822e637bd67856da79cc170bd9df" dependencies = [ "auto_impl", "derive-where", @@ -9427,9 +9799,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "19.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" +checksum = "2e470b2a5e177918944d8cd26174455b61b8fbfbbe7fe411d28b4097e410d11e" dependencies = [ "auto_impl", "either", @@ -9445,9 +9817,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.39.0" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731b682530a732ef9c189ef831589128e2ce34d4a306c956322ae2dffe009715" +checksum = "54e22e1aa328f78e8c3dea6a3a860a3a3c3a77c4a7e775e3fdd3dcbb24a152ac" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -9465,9 +9837,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "35.0.1" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" +checksum = "fa5547f653f9e4c47b4f9a9075a0b7c0faea5fa29b7873f392733a943837825d" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -9478,9 +9850,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "34.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" +checksum = "6d785931e4b8750cf9d79c808c22f05979c8d191baafc8badd4651db82584c1c" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -9489,9 +9861,11 @@ dependencies = [ "ark-serialize 0.5.0", "arrayref", "aurora-engine-modexp", + "aws-lc-rs", "blst", "c-kzg", "cfg-if", + "gmp-mpfr-sys", "k256", "p256", "revm-context-interface", @@ -9503,24 +9877,24 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "23.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" +checksum = "66f39151a31afe93d97f7ac894dd9dcc989368d5ccba19ce079bfc1d73b77452" dependencies = [ "alloy-primitives", - "num_enum", "once_cell", "serde", ] [[package]] name = "revm-state" -version = "11.0.1" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" +checksum = "73ef2fffc28acc41e8ef7525f2b1d16288a10f80579a1b33e7e62abc9892314a" dependencies = [ "alloy-eip7928", - "bitflags 2.11.0", + "bitflags", + "nonmax", "revm-bytecode", "revm-primitives", "serde", @@ -9605,9 +9979,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -9640,15 +10014,16 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "ruint" -version = "1.17.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "45caf26f647c19115bf9c453c70ffe4a4a3a6390dceebd942610584f99b8ddce" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "ark-ff 0.5.0", + "ark-ff 0.6.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -9658,8 +10033,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.7", + "rand 0.9.5", "rlp", "ruint-macro", "serde_core", @@ -9675,11 +10050,11 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" dependencies = [ - "rand 0.8.5", + "rand 0.9.5", ] [[package]] @@ -9721,7 +10096,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -9730,9 +10105,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -9746,9 +10121,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -9758,9 +10133,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -9772,9 +10147,9 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -9789,13 +10164,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.22.4", "log", "once_cell", "rustls", @@ -9804,7 +10179,7 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.8", "windows-sys 0.61.2", ] @@ -9816,9 +10191,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -9828,9 +10203,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "rusty-fork" @@ -9852,9 +10227,18 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "ryu-js" -version = "1.0.2" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d056b875a9d2e6cb9a61d127afee9ac5999b9f87bcb32079d1318e505be714" + +[[package]] +name = "safe_arch" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] [[package]] name = "salsa20" @@ -9918,6 +10302,12 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -9958,7 +10348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand 0.8.7", "secp256k1-sys 0.10.1", "serde", ] @@ -9970,7 +10360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.2", + "rand 0.9.5", "secp256k1-sys 0.11.0", ] @@ -9998,8 +10388,8 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", - "core-foundation", + "bitflags", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -10038,10 +10428,6 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "semver-parser" @@ -10058,12 +10444,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -10097,16 +10477,16 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -10137,15 +10517,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64 0.22.1", + "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.1", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -10156,14 +10537,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ - "darling 0.23.0", + "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10176,11 +10557,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -10200,19 +10606,29 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", - "keccak", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] name = "sha3-asm" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" dependencies = [ "cc", "cfg-if", @@ -10233,6 +10649,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -10275,10 +10697,26 @@ dependencies = [ ] [[package]] -name = "simd-adler32" -version = "0.3.9" +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple_asn1" @@ -10294,9 +10732,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "sketches-ddsketch" @@ -10330,9 +10768,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "arbitrary", "serde", @@ -10346,9 +10784,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -10366,7 +10804,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand 0.8.7", "sha1", ] @@ -10404,7 +10842,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -10416,7 +10863,19 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -10425,6 +10884,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -10438,9 +10903,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -10449,14 +10914,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10476,7 +10941,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10490,7 +10955,28 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.62.2", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -10513,9 +10999,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -10529,17 +11015,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "test-case-core", +] + [[package]] name = "thin-vec" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" [[package]] name = "thiserror" @@ -10567,7 +11092,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10578,28 +11103,28 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "thread-priority" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210811179577da3d54eb69ab0b50490ee40491a25d95b8c6011ba40771cb721" +checksum = "8d2e834949be5111506bb252643498af1514f600d9e1dceedaa42afae155b67f" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cfg-if", "libc", "log", "rustversion", - "windows 0.61.3", + "windows", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] @@ -10646,12 +11171,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -10663,15 +11187,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -10690,9 +11214,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -10705,9 +11229,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -10728,7 +11252,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10791,7 +11315,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", @@ -10820,14 +11344,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.4", ] [[package]] @@ -10836,20 +11360,20 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "base64 0.22.1", @@ -10873,15 +11397,26 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", "tonic", ] +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -10891,7 +11426,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.13.1", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -10904,13 +11439,13 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.11.0", + "bitflags", "bytes", "futures-core", "futures-util", @@ -10919,7 +11454,6 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", @@ -10930,7 +11464,8 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid 1.23.0", + "url", + "uuid 1.23.5", ] [[package]] @@ -10959,11 +11494,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber 0.3.23", @@ -10977,7 +11513,18 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "tracing-chrome" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0a738ed5d6450a9fb96e86a23ad808de2b727fd1394585da5cdd6788ffe724" +dependencies = [ + "serde_json", + "tracing-core", + "tracing-subscriber 0.3.23", ] [[package]] @@ -11036,9 +11583,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" dependencies = [ "js-sys", "opentelemetry", @@ -11098,9 +11645,11 @@ dependencies = [ "serde", "serde_json", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", "tracing-serde", ] @@ -11123,10 +11672,10 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" dependencies = [ - "darling 0.23.0", + "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -11156,7 +11705,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.5", "rustls", "rustls-pki-types", "sha1", @@ -11172,9 +11721,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -11226,9 +11775,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" @@ -11259,7 +11808,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -11336,11 +11885,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -11359,14 +11908,12 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "9.1.0" +version = "10.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" +checksum = "b5574dd2f922b1a46a06a4b1dc11193a4012108fd54cf725e1816cb8183d8778" dependencies = [ "anyhow", - "cargo_metadata", - "derive_builder", - "regex", + "bon", "rustversion", "time", "vergen-lib", @@ -11374,12 +11921,12 @@ dependencies = [ [[package]] name = "vergen-git2" -version = "9.1.0" +version = "10.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51ab55ddf1188c8d679f349775362b0fa9e90bd7a4ac69838b2a087623f0d57" +checksum = "410e2f72acce10471037150cd9c585ee7b54560f348bf70cd5fc8e87d3433e45" dependencies = [ "anyhow", - "derive_builder", + "bon", "git2", "rustversion", "time", @@ -11389,12 +11936,12 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "9.1.0" +version = "10.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +checksum = "8cd42fd155c2c2971f6d00face12ec245fbb604fce011ccaf2306d014c2e97ca" dependencies = [ "anyhow", - "derive_builder", + "bon", "rustversion", ] @@ -11418,7 +11965,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -11457,27 +12004,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -11488,9 +12026,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -11498,9 +12036,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11508,48 +12046,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.1", - "wasm-encoder", - "wasmparser", -] - [[package]] name = "wasm-streams" version = "0.5.0" @@ -11563,18 +12079,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.1", - "semver 1.0.28", -] - [[package]] name = "wasmtimer" version = "0.4.3" @@ -11591,9 +12095,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -11615,14 +12119,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.8", ] [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] @@ -11633,18 +12137,28 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.8", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.1" @@ -11682,38 +12196,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -11722,20 +12214,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -11746,20 +12225,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -11768,9 +12236,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -11781,7 +12249,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -11792,39 +12260,23 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-core", + "windows-link", ] [[package]] @@ -11833,18 +12285,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -11853,16 +12296,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -11871,7 +12305,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -11916,7 +12350,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -11956,7 +12390,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -11967,22 +12401,13 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -12128,106 +12553,21 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.13.1", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.1", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.1", - "log", - "semver 1.0.28", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "write16" @@ -12253,7 +12593,7 @@ dependencies = [ "log", "pharos", "rustc_version 0.4.1", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", @@ -12293,9 +12633,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -12310,35 +12650,35 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -12351,28 +12691,28 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -12407,14 +12747,14 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 51c25d9..f9f1ec2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,41 +12,42 @@ members = [ "crates/primitives", "crates/rpc", "crates/rpc-types", + "vendor/reth-optimism-trie", ] resolver = "2" [workspace.package] version = "1.2.0" edition = "2024" -rust-version = "1.94" +rust-version = "1.95" license = "MIT" [workspace.dependencies] # alloy alloy-chains = { version = "0.2.33", default-features = false } -alloy-consensus = { version = "2.0.0", default-features = false } -alloy-eips = { version = "2.0.0", default-features = false } -alloy-evm = { version = "0.33.0", default-features = false } -alloy-genesis = { version = "2.0.0", default-features = false } +alloy-consensus = { version = "2.1.1", default-features = false } +alloy-eips = { version = "2.1.1", default-features = false } +alloy-evm = { version = "0.37.0", default-features = false } +alloy-genesis = { version = "2.1.1", default-features = false } alloy-hardforks = "0.4.7" -alloy-json-rpc = { version = "2.0.0", default-features = false } -alloy-network = { version = "2.0.0", default-features = false } -alloy-primitives = { version = "1.5.6", default-features = false, features = [ +alloy-json-rpc = { version = "2.1.1", default-features = false } +alloy-network = { version = "2.1.1", default-features = false } +alloy-primitives = { version = "1.6.0", default-features = false, features = [ "map-foldhash", ] } -alloy-rlp = { version = "0.3.13", default-features = false, features = [ +alloy-rlp = { version = "0.3.16", default-features = false, features = [ "core-net", ] } -alloy-rpc-client = { version = "2.0.0", default-features = false } -alloy-rpc-types-debug = { version = "2.0.0", default-features = false } -alloy-rpc-types-engine = { version = "2.0.0", default-features = false } -alloy-rpc-types-eth = { version = "2.0.0", default-features = false } -alloy-rpc-types-trace = { version = "2.0.0", default-features = false } -alloy-rpc-types-txpool = { version = "2.0.0", default-features = false } -alloy-serde = { version = "2.0.0", default-features = false } -alloy-signer = { version = "2.0.0", default-features = false } -alloy-signer-local = { version = "2.0.0", default-features = false } -alloy-sol-types = { version = "1.5.6", default-features = false } +alloy-rpc-client = { version = "2.1.1", default-features = false } +alloy-rpc-types-debug = { version = "2.1.1", default-features = false } +alloy-rpc-types-engine = { version = "2.1.1", default-features = false } +alloy-rpc-types-eth = { version = "2.1.1", default-features = false } +alloy-rpc-types-trace = { version = "2.1.1", default-features = false } +alloy-rpc-types-txpool = { version = "2.1.1", default-features = false } +alloy-serde = { version = "2.1.1", default-features = false } +alloy-signer = { version = "2.1.1", default-features = false } +alloy-signer-local = { version = "2.1.1", default-features = false } +alloy-sol-types = { version = "1.6.0", default-features = false } # general utilities arbitrary = "1.3" @@ -73,66 +74,85 @@ flate2 = "1.1.5" parity-scale-codec = "3.2.1" # reth -reth = { git = "https://github.com/paradigmxyz/reth", package = "reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-chain-state = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-cli = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-codecs = { version = "0.3.0", default-features = false } -reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-db-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-engine-local = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-node-core = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-primitives = { git = "https://github.com/paradigmxyz/reth", package = "reth-ethereum-primitives", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-primitives-traits = { version = "0.3.0", default-features = false } -reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-rpc = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } -reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-trie-common = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f", default-features = false } -reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "27bfddeada3953edc22759080a3659ccea62ca1f" } - -reth-optimism-trie = { git = "https://github.com/ethereum-optimism/optimism", rev = "bcf489eaca2218e5063ca2d188a25f9c83ccd3f3", package = "reth-optimism-trie", default-features = false, features = ["serde-bincode-compat"] } +# Default features minus `jit`: reth's own JIT wiring (reth-node-ethereum) would drag the +# LLVM toolchain into every build; Alethia wires revmc into its own EVM factory instead and +# gates it behind the workspace `jit` features. `reth-revm/portable` (a default) is applied +# via this workspace's reth-revm dependency below. +reth = { git = "https://github.com/paradigmxyz/reth", package = "reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false, features = [ + "jemalloc", + "otlp", + "otlp-logs", + "js-tracer", + "keccak-cache-global", + "asm-keccak", + "gmp", + "min-trace-logs", +] } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-cli = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-codecs = { version = "0.5.0", default-features = false } +reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-db-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-engine-local = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-primitives = { git = "https://github.com/paradigmxyz/reth", package = "reth-ethereum-primitives", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-primitives-traits = { version = "0.5.0", default-features = false } +reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false, features = [ + "portable", +] } +reth-rpc = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-trie-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } + +# Vendored from the Optimism monorepo; see vendor/reth-optimism-trie/TAIKO-PROVENANCE.md. +reth-optimism-trie = { path = "vendor/reth-optimism-trie", default-features = false, features = ["serde-bincode-compat"] } # revm -revm-database-interface = { version = "11.0.0", default-features = false } +revm-database-interface = { version = "41.0.0", default-features = false } # serialization serde = { version = "1.0", default-features = false } diff --git a/justfile b/justfile index be359d5..bd1b1c7 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -toolchain := "1.94.0" +toolchain := "1.95.0" fmt_toolchain := "nightly" fmt: diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6a89faa..38ab2c6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.94.0" +channel = "1.95.0" components = ["clippy", "rustfmt"] diff --git a/vendor/reth-optimism-trie/Cargo.toml b/vendor/reth-optimism-trie/Cargo.toml new file mode 100644 index 0000000..3827165 --- /dev/null +++ b/vendor/reth-optimism-trie/Cargo.toml @@ -0,0 +1,112 @@ +[package] +name = "reth-optimism-trie" +version = "1.11.3" +edition = "2024" +rust-version.workspace = true +license = "MIT" +homepage = "https://paradigmxyz.github.io/reth" +repository = "https://github.com/paradigmxyz/reth" +description = "Trie node storage for serving proofs in FP window fast" +publish = false + +[dependencies] +# reth +reth-db = { workspace = true, features = ["mdbx"] } +reth-evm.workspace = true +reth-execution-errors.workspace = true +reth-primitives-traits.workspace = true +reth-provider.workspace = true +reth-revm.workspace = true +reth-trie = { workspace = true, features = ["serde"] } +reth-trie-common = { workspace = true, features = ["serde"] } +reth-trie-db.workspace = true +reth-codecs.workspace = true +reth-tasks.workspace = true + +# workaround: reth-trie/serde-bincode-compat activates serde-bincode-compat on +# reth-ethereum-primitives (a transitive dep) without also activating its serde feature, +# breaking compilation. Adding it as an optional dep lets us enable both features together. +reth-ethereum-primitives = { workspace = true, optional = true } + +# `metrics` feature +metrics = { version = "0.24.3", default-features = false, optional = true } +reth-metrics = { workspace = true, features = ["common"], optional = true } + +# ethereum +alloy-primitives.workspace = true +alloy-eips.workspace = true + +# async +tokio = { workspace = true, features = ["sync"] } + +# codec +bytes = { version = "1.11.1", default-features = false } +serde.workspace = true +bincode = { version = "2.0.1", features = ["serde"] } + +# misc +parking_lot = "0.12.5" +thiserror.workspace = true +auto_impl.workspace = true +eyre = { workspace = true, optional = true } +strum = { version = "0.27", default-features = false } +tracing.workspace = true +crossbeam-channel = "0.5.13" +derive_more.workspace = true + +[dev-dependencies] +reth-optimism-trie = { path = ".", features = ["test-utils"] } +reth-codecs = { workspace = true, features = ["test-utils"] } +tempfile.workspace = true +tokio = { workspace = true, features = ["test-util", "rt-multi-thread", "macros"] } +test-case = "3" +reth-db = { workspace = true, features = ["test-utils"] } +# workaround for failing doc test +reth-db-api = { workspace = true, features = ["test-utils"] } +reth-trie = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-node-api.workspace = true +alloy-consensus.workspace = true +alloy-genesis.workspace = true +reth-chainspec.workspace = true +reth-db-common.workspace = true +reth-ethereum-primitives.workspace = true +reth-evm-ethereum.workspace = true +reth-storage-errors.workspace = true +secp256k1 = { version = "0.31.1", default-features = false, features = ["rand", "std"] } +mockall = "0.14.0" +eyre.workspace = true + +# misc +serial_test = "3" + +[features] +test-utils = [ + "reth-codecs/test-utils", + "reth-db/test-utils", + "reth-ethereum-primitives?/test-utils", + "reth-evm/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-revm/test-utils", + "reth-tasks/test-utils", + "reth-trie/test-utils", + "reth-trie-common/test-utils", + "reth-trie-db/test-utils" +] +serde-bincode-compat = [ + "reth-trie-common/serde-bincode-compat", + "alloy-consensus/serde-bincode-compat", + "alloy-eips/serde-bincode-compat", + "reth-trie/serde-bincode-compat", + "dep:reth-ethereum-primitives", + "reth-ethereum-primitives?/serde", +] +metrics = [ + "reth-trie/metrics", + "reth-trie-db/metrics", + "dep:reth-metrics", + "dep:metrics", + "dep:eyre", + "reth-evm/metrics" +] diff --git a/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md b/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md new file mode 100644 index 0000000..447dba9 --- /dev/null +++ b/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md @@ -0,0 +1,25 @@ +# reth-optimism-trie provenance + +This directory vendors the `reth-optimism-trie` crate from the Optimism monorepo at +commit `9b802fdb62c96a1cd70b2144ce89979d4e41f4ec` +(, path `rust/op-reth/crates/trie`). + +It retains the crate's original MIT license. + +## Why vendored + +Alethia-Reth pins reth **v2.4.0**, which builds against the published +`reth-primitives-traits`/`reth-codecs` **0.5.x** crates. The Optimism monorepo pins reth +v2.3.0 (published crates 0.4.x). Cargo cannot unify a 0.4→0.5 split across a git +dependency, so consuming `reth-optimism-trie` as a git dependency would force Alethia's +reth pin to always match Optimism's. Vendoring decouples the two pins. + +## Local changes + +- `Cargo.toml`: rewritten against the Alethia workspace (explicit versions for externals + the workspace does not declare; `publish = false`; upstream `[lints]` table dropped). +- Source adjustments required by the reth v2.3.0 → v2.4.0 API migration are kept minimal + and are visible in this repository's history for this directory. + +The crate is excluded from Alethia's `missing_docs` clippy gate (see `justfile`) to keep +the vendored source close to upstream. diff --git a/vendor/reth-optimism-trie/src/api.rs b/vendor/reth-optimism-trie/src/api.rs new file mode 100644 index 0000000..9c5f7b7 --- /dev/null +++ b/vendor/reth-optimism-trie/src/api.rs @@ -0,0 +1,663 @@ +//! Storage API for external storage of intermediary trie nodes. + +use crate::{ + OpProofsStorageResult, + db::{HashedStorageKey, StorageTrieKey}, +}; +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use alloy_primitives::{B256, U256}; +use auto_impl::auto_impl; +use derive_more::{AddAssign, Constructor}; +use reth_primitives_traits::Account; +use reth_trie::{ + hashed_cursor::{HashedCursor, HashedStorageCursor}, + trie_cursor::{TrieCursor, TrieStorageCursor}, +}; +use reth_trie_common::{ + BranchNodeCompact, HashedPostStateSorted, Nibbles, StoredNibbles, updates::TrieUpdatesSorted, +}; +use std::{fmt::Debug, time::Duration}; + +/// Duration metrics for block processing. +#[derive(Debug, Default, Clone)] +pub struct OperationDurations { + /// Total time to process a block (end-to-end) in seconds + pub total_duration_seconds: Duration, + /// Time spent executing the block (EVM) in seconds + pub execution_duration_seconds: Duration, + /// Time spent calculating state root in seconds + pub state_root_duration_seconds: Duration, + /// Time spent writing trie updates to storage in seconds + pub write_duration_seconds: Duration, +} + +/// Diff of trie updates and post state for a block. +#[derive(Debug, Clone, Default)] +pub struct BlockStateDiff { + /// Trie updates for branch nodes + pub sorted_trie_updates: TrieUpdatesSorted, + /// Post state for leaf nodes (accounts and storage) + pub sorted_post_state: HashedPostStateSorted, +} + +impl BlockStateDiff { + /// Extend the [` BlockStateDiff`] from other latest [`BlockStateDiff`] + pub fn extend_ref(&mut self, other: &Self) { + self.sorted_trie_updates.extend_ref_and_sort(&other.sorted_trie_updates); + self.sorted_post_state.extend_ref_and_sort(&other.sorted_post_state); + } +} + +/// The block range covered by the proof window: earliest persisted block and latest persisted +/// block. Both endpoints are inclusive. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ProofWindowRange { + /// Earliest block stored. + pub earliest: NumHash, + /// Latest block stored. + pub latest: NumHash, +} + +/// Counts of trie updates written to storage. +#[derive(Debug, Clone, Default, AddAssign, Constructor, Eq, PartialEq)] +pub struct WriteCounts { + /// Number of account trie updates written + pub account_trie_updates_written_total: u64, + /// Number of storage trie updates written + pub storage_trie_updates_written_total: u64, + /// Number of hashed accounts written + pub hashed_accounts_written_total: u64, + /// Number of hashed storages written + pub hashed_storages_written_total: u64, +} + +/// Provider for interacting with the proofs storage within a transaction. +#[auto_impl(Arc)] +pub trait OpProofsProviderRO: Send + Sync + Debug { + /// Cursor for iterating over trie branches. + type StorageTrieCursor<'tx>: TrieStorageCursor + 'tx + where + Self: 'tx; + + /// Cursor for iterating over account trie branches. + type AccountTrieCursor<'tx>: TrieCursor + 'tx + where + Self: 'tx; + + /// Cursor for iterating over storage leaves. + type StorageCursor<'tx>: HashedStorageCursor + Send + 'tx + where + Self: 'tx; + + /// Cursor for iterating over account leaves. + type AccountHashedCursor<'tx>: HashedCursor + Send + 'tx + where + Self: 'tx; + + /// Get the earliest block number and hash that has been stored. Returns + /// [`crate::OpProofsStorageError::NoBlocksFound`] if the proof window is empty. + fn get_earliest_block(&self) -> OpProofsStorageResult; + + /// Get the latest block number and hash that has been stored. Returns + /// [`crate::OpProofsStorageError::NoBlocksFound`] if the proof window is empty. + fn get_latest_block(&self) -> OpProofsStorageResult; + + /// Get the proof window range — earliest and latest persisted blocks — in a single read. + /// Prefer this over calling [`Self::get_earliest_block`] and [`Self::get_latest_block`] + /// separately when you need both. Returns [`crate::OpProofsStorageError::NoBlocksFound`] if the + /// proof window is empty. + fn get_proof_window(&self) -> OpProofsStorageResult; + + /// Get a trie cursor for the storage backend + fn storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult>; + + /// Get a trie cursor for the account backend + fn account_trie_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult>; + + /// Get a storage cursor for the storage backend + fn storage_hashed_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult>; + + /// Get an account hashed cursor for the storage backend + fn account_hashed_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult>; + + /// Fetch all updates for a given block number. + fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult; +} + +/// Blanket [`OpProofsProviderRO`] for shared references. +impl<'a, T: OpProofsProviderRO + 'a> OpProofsProviderRO for &'a T { + type StorageTrieCursor<'tx> + = T::StorageTrieCursor<'tx> + where + Self: 'tx, + T: 'tx; + type AccountTrieCursor<'tx> + = T::AccountTrieCursor<'tx> + where + Self: 'tx, + T: 'tx; + type StorageCursor<'tx> + = T::StorageCursor<'tx> + where + Self: 'tx, + T: 'tx; + type AccountHashedCursor<'tx> + = T::AccountHashedCursor<'tx> + where + Self: 'tx, + T: 'tx; + + fn get_earliest_block(&self) -> OpProofsStorageResult { + T::get_earliest_block(self) + } + + fn get_latest_block(&self) -> OpProofsStorageResult { + T::get_latest_block(self) + } + + fn get_proof_window(&self) -> OpProofsStorageResult { + T::get_proof_window(self) + } + + fn storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::storage_trie_cursor(self, hashed_address, max_block_number) + } + + fn account_trie_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::account_trie_cursor(self, max_block_number) + } + + fn storage_hashed_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::storage_hashed_cursor(self, hashed_address, max_block_number) + } + + fn account_hashed_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::account_hashed_cursor(self, max_block_number) + } + + fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { + T::fetch_trie_updates(self, block_number) + } +} + +/// Provider for writing to the proofs storage within a transaction. +pub trait OpProofsProviderRw: OpProofsProviderRO { + /// Store trie updates for a block. + fn store_trie_updates( + &self, + block_ref: BlockWithParent, + block_state_diff: BlockStateDiff, + ) -> OpProofsStorageResult; + + /// Store a batch of trie updates for a block. + fn store_trie_updates_batch( + &self, + updates: Vec<(BlockWithParent, BlockStateDiff)>, + ) -> OpProofsStorageResult; + + /// Applies [`BlockStateDiff`] to the earliest state (updating/deleting nodes) and updates the + /// earliest block number. + fn prune_earliest_state( + &self, + new_earliest_block_ref: BlockWithParent, + ) -> OpProofsStorageResult; + + /// Remove account, storage and trie updates from historical storage for all blocks till + /// the specified block (inclusive). + fn unwind_history(&self, to: BlockWithParent) -> OpProofsStorageResult<()>; + + /// Deletes all updates > `latest_common_block` and replaces them with the new updates. + fn replace_updates( + &self, + latest_common_block: BlockNumHash, + blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)>, + ) -> OpProofsStorageResult<()>; + + /// Commit the changes to the database. + /// Consumes the provider. + fn commit(self) -> OpProofsStorageResult<()>; +} + +/// Provider for writing historical records for blocks older than the current window boundary, +/// and for the snapshot RW operations that ride on the same transaction. +/// +/// Unlike [`OpProofsProviderRw::store_trie_updates`], which is strictly append-only (validates +/// parent hash against `latest` and advances `latest`), this provider is designed for +/// **prepend-style** writes that extend the window backward. It does not touch the `latest` +/// marker, and it does not enforce parent-hash ordering against `latest`. +/// +/// The typical call sequence for one snapshot-accelerated backfill step is: +/// ```ignore +/// let bp = storage.backfill_provider()?; +/// bp.update_snapshot(new_anchor, &diff)?; +/// bp.prepend_block(block_ref, diff)?; +/// bp.commit()?; // commits backfill + snapshot writes atomically +/// ``` +pub trait OpProofsBackfillProvider: OpProofsSnapshotProviderRO + OpProofsProviderRO { + /// Write historical changeset and history-bitmap entries for `block_ref`, and move the + /// `earliest` marker to `block_ref.parent`. + /// + /// `diff` contains: + /// - `sorted_trie_updates`: trie node **before-values** for `block_ref.block.number` (i.e. what + /// each changed node looked like *before* the block executed). + /// - `sorted_post_state`: account / storage **before-values** for the same block. + /// + /// The implementation must **not** update the `latest` marker and must **not** + /// validate `diff` against the current `latest` block. + fn prepend_block( + &self, + block_ref: BlockWithParent, + diff: BlockStateDiff, + ) -> OpProofsStorageResult; + + /// Wipe all snapshot tables and the meta row. + fn clear_snapshot(&self) -> OpProofsStorageResult<()>; + + /// Project `diff` onto the snapshot and advance the anchor to + /// `new_anchor` atomically. Trie direction is implicit: + /// `(path, Some(node))` sets, `(path, None)` deletes. Leaf direction + /// follows the same convention: `Some(value)` upserts the leaf, `None` + /// deletes it. + /// + /// The returned [`WriteCounts`] reports per-table row counts (mirrors + /// [`OpProofsBackfillProvider::prepend_block`]'s return shape). + /// + /// Requires status `Ready`; errors with + /// [`OpProofsStorageError::SnapshotUpdateNotReady`](crate::OpProofsStorageError::SnapshotUpdateNotReady) + /// otherwise. + fn update_snapshot( + &self, + new_anchor: BlockNumHash, + diff: &BlockStateDiff, + ) -> OpProofsStorageResult; + + /// Commit the transaction. Consumes the provider. Flushes both the backfill writes + /// and any pending snapshot writes atomically. + fn commit(self) -> OpProofsStorageResult<()>; +} + +/// Factory trait for creating providers to interact with the proofs storage. +#[auto_impl(Arc)] +pub trait OpProofsStore: Send + Sync + Debug { + /// The read-only provider type created by the factory. + type ProviderRO<'a>: OpProofsProviderRO + Clone + 'a + where + Self: 'a; + + /// The read-write provider type created by the factory. + type ProviderRw<'a>: OpProofsProviderRw + 'a + where + Self: 'a; + + /// The initialization provider type created by the factory. + type Initializer<'a>: OpProofsInitProvider + 'a + where + Self: 'a; + + /// Create a read-only provider for interacting with the proofs storage. + fn provider_ro<'a>(&'a self) -> OpProofsStorageResult>; + + /// Create a read-write provider for interacting with the proofs storage. + fn provider_rw<'a>(&'a self) -> OpProofsStorageResult>; + + /// Create an initialization provider for interacting with the proofs storage. + fn initialization_provider<'a>(&'a self) -> OpProofsStorageResult>; +} + +/// Factory extension for stores that support backfill — extending the `earliest` block of +/// the proof window backward. +/// +/// Bundles the trie-state snapshot machinery (snapshot RO / RW / init providers) on the +/// same trait because snapshot is internal infrastructure that accelerates backfill. +#[auto_impl(Arc)] +pub trait OpProofsBackfillStore: OpProofsStore { + /// The backfill provider type created by the factory. + type BackfillProvider<'a>: OpProofsBackfillProvider + 'a + where + Self: 'a; + + /// The snapshot RO provider type created by the factory. + type SnapshotProviderRO<'a>: OpProofsSnapshotProviderRO + Clone + 'a + where + Self: 'a; + + /// Init-time bulk writer (used by [`crate::snapshot::SnapshotInitJob`]). + type SnapshotInitializer<'a>: OpProofsSnapshotInitProvider + 'a + where + Self: 'a; + + /// Open the writer provider for backfill and snapshot RW operations. Backfill writes, + /// snapshot updates, and snapshot teardown all share this single tx and commit + /// atomically through [`OpProofsBackfillProvider::commit`]. + fn backfill_provider<'a>(&'a self) -> OpProofsStorageResult>; + + /// Open a RO snapshot provider. + fn snapshot_provider_ro<'a>(&'a self) -> OpProofsStorageResult>; + + /// Open an init-time snapshot provider. + fn snapshot_initialization_provider<'a>( + &'a self, + ) -> OpProofsStorageResult>; +} + +/// Status of the initial state anchor. +#[derive(Debug, Clone, Copy, Default)] +pub enum InitialStateStatus { + /// Init isn't yet started + #[default] + NotStarted, + /// Init is in progress (some tables may already be populated) + InProgress, + /// Init completed successfully (all tables done + earliest block set) + Completed, +} + +/// Anchor for the initial state. +#[derive(Debug, Clone, Default)] +pub struct InitialStateAnchor { + /// The block for which the initial state is being initialized. None if initialization is not + /// yet started. + pub block: Option, + /// Whether initialization is still running or completed. + pub status: InitialStateStatus, + /// The latest key stored for `AccountTrieHistory`. + pub latest_account_trie_key: Option, + /// The latest key stored for `StorageTrieHistory`. + pub latest_storage_trie_key: Option, + /// The latest key stored for `HashedAccountHistory`. + pub latest_hashed_account_key: Option, + /// The latest key stored for `HashedStorageHistory`. + pub latest_hashed_storage_key: Option, +} + +/// Trait for storing and retrieving the initial state anchor. +pub trait OpProofsInitProvider: Send + Sync + Debug { + /// Read the current anchor. + fn initial_state_anchor(&self) -> OpProofsStorageResult; + + /// Create the anchor if it doesn't exist. + /// Returns `Err` if an anchor already exists (prevents accidental overwrite). + fn set_initial_state_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()>; + + /// Store a batch of account trie branches. Used for saving existing state. For live state + /// capture, use [`store_trie_updates`](OpProofsProviderRw::store_trie_updates). + fn store_account_branches( + &self, + account_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()>; + + /// Store a batch of storage trie branches. Used for saving existing state. + fn store_storage_branches( + &self, + hashed_address: B256, + storage_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()>; + + /// Store a batch of account trie leaf nodes. Used for saving existing state. + fn store_hashed_accounts( + &self, + accounts: Vec<(B256, Option)>, + ) -> OpProofsStorageResult<()>; + + /// Store a batch of storage trie leaf nodes. Used for saving existing state. + fn store_hashed_storages( + &self, + hashed_address: B256, + storages: Vec<(B256, U256)>, + ) -> OpProofsStorageResult<()>; + + /// Commit the initial state - mark the anchor as completed and also set the earliest block + /// number to anchor. + fn commit_initial_state(&self) -> OpProofsStorageResult; + + /// Commit the changes to the database. + /// Consumes the provider. + fn commit(self) -> OpProofsStorageResult<()>; +} + +/// Read access to the trie-state snapshot. +/// +/// Cursors read the snapshot tables directly (no history-bitmap lookups) and +/// are only valid at [`Self::snapshot_anchor`]. +#[auto_impl(Arc)] +pub trait OpProofsSnapshotProviderRO: OpProofsProviderRO { + /// Cursor over the snapshot's account trie table. + type SnapshotAccountTrieCursor<'tx>: TrieCursor + 'tx + where + Self: 'tx; + + /// Cursor over the snapshot's storage trie table. + type SnapshotStorageTrieCursor<'tx>: TrieStorageCursor + 'tx + where + Self: 'tx; + + /// Cursor over the snapshot's hashed-account leaf table. + type SnapshotHashedAccountCursor<'tx>: HashedCursor + Send + 'tx + where + Self: 'tx; + + /// Cursor over the snapshot's hashed-storage leaf table. + type SnapshotHashedStorageCursor<'tx>: HashedStorageCursor + Send + 'tx + where + Self: 'tx; + + /// Anchor block of a `Ready` snapshot. Errors with + /// [`OpProofsStorageError::SnapshotNotReady`](crate::OpProofsStorageError::SnapshotNotReady) + /// otherwise. + fn snapshot_anchor(&self) -> OpProofsStorageResult; + + /// Open a cursor over the snapshot's account trie table. + fn snapshot_account_trie_cursor<'tx>( + &self, + ) -> OpProofsStorageResult>; + + /// Open a cursor over the snapshot's storage trie table for `hashed_address`. + fn snapshot_storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + ) -> OpProofsStorageResult>; + + /// Open a cursor over the snapshot's hashed-account leaf table. + fn snapshot_hashed_account_cursor<'tx>( + &self, + ) -> OpProofsStorageResult>; + + /// Open a cursor over the snapshot's hashed-storage leaf table for `hashed_address`. + fn snapshot_hashed_storage_cursor<'tx>( + &self, + hashed_address: B256, + ) -> OpProofsStorageResult>; +} + +/// Blanket [`OpProofsSnapshotProviderRO`] for shared references — mirrors the +/// equivalent impl on [`OpProofsProviderRO`] above. Lets callers pass `&bp` to +/// owning cursor factories (e.g., [`crate::SnapshotTrieCursorFactory::new`]) +/// without wrapping in [`std::sync::Arc`]. +impl<'a, T: OpProofsSnapshotProviderRO + 'a> OpProofsSnapshotProviderRO for &'a T { + type SnapshotAccountTrieCursor<'tx> + = T::SnapshotAccountTrieCursor<'tx> + where + Self: 'tx, + T: 'tx; + type SnapshotStorageTrieCursor<'tx> + = T::SnapshotStorageTrieCursor<'tx> + where + Self: 'tx, + T: 'tx; + type SnapshotHashedAccountCursor<'tx> + = T::SnapshotHashedAccountCursor<'tx> + where + Self: 'tx, + T: 'tx; + type SnapshotHashedStorageCursor<'tx> + = T::SnapshotHashedStorageCursor<'tx> + where + Self: 'tx, + T: 'tx; + + fn snapshot_anchor(&self) -> OpProofsStorageResult { + T::snapshot_anchor(self) + } + + fn snapshot_account_trie_cursor<'tx>( + &self, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::snapshot_account_trie_cursor(self) + } + + fn snapshot_storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::snapshot_storage_trie_cursor(self, hashed_address) + } + + fn snapshot_hashed_account_cursor<'tx>( + &self, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::snapshot_hashed_account_cursor(self) + } + + fn snapshot_hashed_storage_cursor<'tx>( + &self, + hashed_address: B256, + ) -> OpProofsStorageResult> + where + 'a: 'tx, + { + T::snapshot_hashed_storage_cursor(self, hashed_address) + } +} + +/// Lifecycle of the snapshot init job. Mirrors [`InitialStateStatus`]. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum SnapshotInitStatus { + /// Init has never run. + #[default] + NotStarted, + /// Init is running; snapshot tables may be partially populated. + InProgress, + /// Init completed. Use [`OpProofsSnapshotProviderRO::snapshot_anchor`] to read. + Completed, +} + +/// Status + anchor block + resume keys for the snapshot init job. Mirrors +/// [`InitialStateAnchor`]'s shape. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct SnapshotInitAnchor { + /// Anchor block, or `None` if init has never run. + pub block: Option, + /// Lifecycle status. + pub status: SnapshotInitStatus, + /// Last key in [`crate::db::V2AccountsTrieSnapshot`]; resumes the account-trie phase. + pub last_account_trie_key: Option, + /// Last entry in [`crate::db::V2StoragesTrieSnapshot`]; resumes the storage-trie phase. + pub last_storage_trie_key: Option, + /// Last key in [`crate::db::V2HashedAccountsSnapshot`]; resumes the hashed-accounts phase. + pub last_hashed_account_key: Option, + /// Last entry in [`crate::db::V2HashedStoragesSnapshot`]; resumes the + /// hashed-storages phase. + pub last_hashed_storage_key: Option, +} + +/// Init-time read + write surface for the trie-state snapshot. Mirrors +/// [`OpProofsInitProvider`]'s role. Driven by [`crate::snapshot::SnapshotInitJob`] +/// over short chunked rw-tx; meta stays `Building` mid-init so a crash +/// resumes from [`Self::snapshot_init_anchor`]. +pub trait OpProofsSnapshotInitProvider: Send + Sync + Debug { + /// Read status + anchor block + resume keys in one call. + fn snapshot_init_anchor(&self) -> OpProofsStorageResult; + + /// Plant the meta row at `anchor` with status `Building`. Errors if a + /// meta row already exists — call + /// [`OpProofsBackfillProvider::clear_snapshot`] first to rebuild. + fn set_snapshot_init_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()>; + + /// Append a chunk to [`crate::db::V2AccountsTrieSnapshot`]. Entries must + /// be sorted and strictly greater than the table's current last key. + fn store_account_trie_snapshot_branches( + &self, + entries: Vec<(StoredNibbles, BranchNodeCompact)>, + ) -> OpProofsStorageResult<()>; + + /// Append a chunk to [`crate::db::V2StoragesTrieSnapshot`]. Entries must + /// be sorted and strictly greater than the table's current last entry. + fn store_storage_trie_snapshot_branches( + &self, + hashed_address: B256, + storage_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()>; + + /// Append a chunk to [`crate::db::V2HashedAccountsSnapshot`]. Entries must + /// be sorted by `hashed_address` and strictly greater than the table's + /// current last key. + fn store_hashed_accounts_snapshot( + &self, + entries: Vec<(B256, Account)>, + ) -> OpProofsStorageResult<()>; + + /// Append a chunk to [`crate::db::V2HashedStoragesSnapshot`] for + /// `hashed_address`. Entries must be sorted by storage key and strictly + /// greater than the table's current last entry for this address. + fn store_hashed_storages_snapshot( + &self, + hashed_address: B256, + entries: Vec<(B256, U256)>, + ) -> OpProofsStorageResult<()>; + + /// Transition the meta row from `Building` to `Ready`. Errors if no meta + /// exists or if it isn't `Building`. + fn commit_snapshot(&self) -> OpProofsStorageResult<()>; + + /// Commit the transaction. Consumes the provider. + fn commit(self) -> OpProofsStorageResult<()>; +} diff --git a/vendor/reth-optimism-trie/src/backfill/changesets.rs b/vendor/reth-optimism-trie/src/backfill/changesets.rs new file mode 100644 index 0000000..c102053 --- /dev/null +++ b/vendor/reth-optimism-trie/src/backfill/changesets.rs @@ -0,0 +1,104 @@ +//! Per-block backfill diff computation. See [`compute_block_backfill_diff`]. +//! +//! Equivalent to `reth_trie_db::changesets::compute_block_trie_changesets_inner`, +//! but reads the trie via caller-supplied cursor factories — typically a +//! history-aware factory at `max_block_number = N` over the **op-reth proofs +//! storage** (making per-block cost scale with the block's diff size, not the +//! tail size of changesets between N and the DB tip), or a snapshot-backed +//! factory when a [`SnapshotStatus::Ready`](crate::db::SnapshotStatus::Ready) +//! snapshot is available at the right anchor. + +use crate::backfill::error::BackfillError; +use alloy_primitives::BlockNumber; +use reth_primitives_traits::AlloyBlockHeader; +use reth_provider::{ + BlockNumReader, ChangeSetReader, DBProvider, HeaderProvider, ProviderError, + StorageChangeSetReader, StorageSettingsCache, +}; +use reth_trie::{ + StateRoot, + hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory}, + trie_cursor::TrieCursorFactory, +}; +use reth_trie_common::{HashedPostStateSorted, updates::TrieUpdatesSorted}; +use reth_trie_db::from_reverts_auto; + +/// Compute the backfill diff for `block_number`: +/// - `HashedPostStateSorted` — per-block leaf revert (state before block N ran), reused as +/// `BlockStateDiff::sorted_post_state`. +/// - `TrieUpdatesSorted` — trie-node before-values for paths block N touched, written into the four +/// `V2*TrieChangeSets` tables by `prepend_block`. +/// +/// The trie + hashed cursor factories must read trie state **at the start of +/// this iteration** (`earliest == block_number`). For the history-aware path +/// this means cursors built with `max_block_number = block_number`; for the +/// snapshot path the snapshot's anchor must equal `block_number`. +pub(super) fn compute_block_backfill_diff( + reth_provider: &P, + trie_cursor_factory: T, + hashed_cursor_factory: H, + block_number: BlockNumber, +) -> Result<(TrieUpdatesSorted, HashedPostStateSorted), BackfillError> +where + P: ChangeSetReader + + StorageChangeSetReader + + BlockNumReader + + DBProvider + + HeaderProvider + + StorageSettingsCache, + T: TrieCursorFactory + Clone, + H: HashedCursorFactory + Clone, +{ + // Per-block leaf revert: doubles as `post_state` for `prepend_block` and + // as the state overlay for the trie@N-1 reconstruction below. + let individual_state_revert = from_reverts_auto(reth_provider, block_number..=block_number)?; + + // Reth prunes the per-block changesets together with the body. An empty revert means either: + // (a) the block genuinely changed nothing — header[N].state_root == header[N-1].state_root, or + // (b) reth has pruned the changesets we need. + // Fail fast on (b) with an accurate error; (a) is a legal no-op and falls through. + if individual_state_revert.is_empty() && block_number > 0 { + let state_root_n = reth_provider + .header_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))? + .state_root(); + let state_root_prev = reth_provider + .header_by_number(block_number - 1)? + .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))? + .state_root(); + if state_root_n != state_root_prev { + return Err(BackfillError::BlockBodyPruned(block_number)); + } + } + + let trie_changesets = compute_trie_changesets_against_proofs( + trie_cursor_factory, + hashed_cursor_factory, + &individual_state_revert, + )?; + Ok((trie_changesets, individual_state_revert)) +} + +fn compute_trie_changesets_against_proofs( + trie_cursor_factory: T, + hashed_cursor_factory: H, + individual_state_revert: &HashedPostStateSorted, +) -> Result +where + T: TrieCursorFactory + Clone, + H: HashedCursorFactory + Clone, +{ + // Apply block N's leaf revert as a state overlay on top of the supplied + // trie cursor at max=N. The returned `TrieUpdates` describes trie@N-1 + // relative to the cursor's view at max=N for the paths block N touched — + // which is the changeset we want (`Some` for modified/destroyed, `None` + // for newly created branches). + let prefix_sets = individual_state_revert.construct_prefix_sets().freeze(); + let (_, trie_updates) = StateRoot::new( + trie_cursor_factory, + HashedPostStateCursorFactory::new(hashed_cursor_factory, individual_state_revert), + ) + .with_prefix_sets(prefix_sets) + .root_with_updates()?; + Ok(trie_updates.into_sorted()) +} diff --git a/vendor/reth-optimism-trie/src/backfill/error.rs b/vendor/reth-optimism-trie/src/backfill/error.rs new file mode 100644 index 0000000..9b1b6d7 --- /dev/null +++ b/vendor/reth-optimism-trie/src/backfill/error.rs @@ -0,0 +1,61 @@ +//! Error type for backfill operations. + +use crate::{OpProofsStorageError, snapshot::SnapshotError}; +use alloy_eips::BlockNumHash; +use alloy_primitives::B256; +use reth_execution_errors::StateRootError; +use reth_provider::ProviderError; + +/// Error type for backfill operations. +#[derive(Debug, thiserror::Error)] +pub enum BackfillError { + /// Error bubbled up from proofs storage operations. + #[error(transparent)] + Storage(#[from] OpProofsStorageError), + /// Error from reth provider operations. + #[error(transparent)] + Provider(#[from] ProviderError), + /// State root computation failed. + #[error(transparent)] + StateRoot(#[from] StateRootError), + /// Error from snapshot-init / state operations during snapshot-accelerated backfill. + #[error(transparent)] + Snapshot(#[from] SnapshotError), + /// Reth has pruned data needed to recompute the per-block diff. + /// + /// Backfill reconstructs each block's state delta from MDBX changesets (via + /// `from_reverts_auto`). When reth prunes a block it typically prunes the body AND the + /// account/storage changesets together; the empty revert that comes back would otherwise + /// surface as a misleading [`Self::StateRootMismatch`]. Detected when the per-block revert is + /// empty *and* the block actually changed state (`header_n.state_root != + /// header_prev.state_root`). + #[error( + "Block #{0} has been pruned by reth (changesets missing); cannot backfill. \ + Re-sync reth without history pruning, or reduce the backfill window." + )] + BlockBodyPruned(u64), + /// Computed state root does not match the expected root from the header. + #[error( + "State root mismatch at block {block_number}: computed {computed:?}, expected {expected:?}" + )] + StateRootMismatch { + /// Block number being validated (the block whose before-state is being checked). + block_number: u64, + /// Computed root from the proofs storage overlay. + computed: B256, + /// Expected root from reth's block header. + expected: B256, + }, + /// Snapshot-accelerated backfill requires the snapshot anchor to equal + /// the proofs window's current `earliest`. + #[error( + "snapshot anchor {found:?} does not match proofs `earliest` {expected:?}; \ + drop the snapshot and re-init at {expected:?} or run backfill without --use-snapshot" + )] + SnapshotAnchorMismatch { + /// Current `earliest` block of the proofs window. + expected: BlockNumHash, + /// Anchor block of the existing snapshot. + found: BlockNumHash, + }, +} diff --git a/vendor/reth-optimism-trie/src/backfill/job.rs b/vendor/reth-optimism-trie/src/backfill/job.rs new file mode 100644 index 0000000..78fa54e --- /dev/null +++ b/vendor/reth-optimism-trie/src/backfill/job.rs @@ -0,0 +1,499 @@ +//! [`BackfillJob`] implementation. + +use super::{changesets::compute_block_backfill_diff, error::BackfillError}; +use crate::{ + BlockStateDiff, OpProofsBackfillProvider, OpProofsBackfillStore, + OpProofsHashedAccountCursorFactory, OpProofsProviderRO, OpProofsSnapshotInitProvider, + OpProofsSnapshotProviderRO, OpProofsTrieCursorFactory, SnapshotHashedCursorFactory, + SnapshotInitJob, SnapshotInitStatus, SnapshotTrieCursorFactory, proof::DatabaseStateRoot, +}; +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use alloy_primitives::BlockNumber; +use reth_primitives_traits::AlloyBlockHeader; +use reth_provider::{ + BlockHashReader, BlockNumReader, ChangeSetReader, DBProvider, HeaderProvider, ProviderError, + StageCheckpointReader, StorageChangeSetReader, StorageSettingsCache, +}; +use reth_trie::{HashedPostState, StateRoot, hashed_cursor::HashedPostStateCursorFactory}; +use std::time::{Duration, Instant}; +use tracing::info; + +/// How often to emit a progress line during a long backfill, measured in +/// blocks committed. +const LOG_EVERY: u64 = 1_000; + +/// Run a fallible closure and return its value alongside the wall-clock +/// duration on success. Errors are propagated; the duration is not returned +/// when the closure fails. +#[inline] +fn timed(f: F) -> Result<(R, Duration), E> +where + F: FnOnce() -> Result, +{ + let start = Instant::now(); + let r = f()?; + Ok((r, start.elapsed())) +} + +/// Cumulative time spent in each phase of [`BackfillJob::backfill_block`]. +/// Reported alongside the progress line so operators can see which phase +/// dominates a slow backfill. +#[derive(Debug, Default, Clone, Copy)] +struct PhaseTimings { + compute: Duration, + prepend: Duration, + validate: Duration, + commit: Duration, +} + +impl PhaseTimings { + fn add(&mut self, other: Self) { + self.compute += other.compute; + self.prepend += other.prepend; + self.validate += other.validate; + self.commit += other.commit; + } + + /// Per-block average. `done` must be > 0. + fn averages(&self, done: u64) -> Self { + let n = done as u32; + Self { + compute: self.compute / n, + prepend: self.prepend / n, + validate: self.validate / n, + commit: self.commit / n, + } + } +} + +/// Default number of blocks written per MDBX transaction. +/// +/// `25` measured ~2.6× throughput on a 1.296M-block op-mainnet backfill and sits at the sweet +/// spot on the K sweep: bitmap + commit amortization has done most of its work, but the open +/// tx's dirty-page pressure hasn't yet started slowing cursor reads. Tune via +/// [`BackfillJob::with_batch_size`] / `--proofs-history.backfill-batch-size` — see that flag +/// for the throughput / memory / restart-loss trade-offs. +pub const DEFAULT_BACKFILL_BATCH_SIZE: usize = 25; + +/// Backfill job for proofs storage. +#[derive(Debug)] +pub struct BackfillJob { + provider: P, + storage: S, + /// Number of blocks written per MDBX transaction. Amortizes commit cost across blocks at the + /// price of restart granularity. See [`DEFAULT_BACKFILL_BATCH_SIZE`]. + batch_size: usize, +} + +impl BackfillJob { + /// Create a new backfill job using [`DEFAULT_BACKFILL_BATCH_SIZE`]. + pub const fn new(provider: P, storage: S) -> Self { + Self { provider, storage, batch_size: DEFAULT_BACKFILL_BATCH_SIZE } + } + + /// Override the batch size (number of blocks per MDBX transaction). + /// + /// `batch_size` is clamped to `1` if zero — backfill needs to make per-block progress. + pub fn with_batch_size(mut self, batch_size: usize) -> Self { + self.batch_size = batch_size.max(1); + self + } +} + +impl BackfillJob +where + P: DBProvider + + StageCheckpointReader + + ChangeSetReader + + StorageChangeSetReader + + BlockNumReader + + BlockHashReader + + HeaderProvider + + StorageSettingsCache + + Send, + S: OpProofsBackfillStore + Send, +{ + /// Backfill proofs data down to `target_earliest_block`. + /// + /// Extends the stored proof window from `[earliest, latest]` backward to + /// `[target_earliest_block, latest]`. Blocks are written in batches of + /// [`Self::with_batch_size`] (default [`DEFAULT_BACKFILL_BATCH_SIZE`]) — each batch holds + /// one MDBX transaction open across K blocks and commits once, amortizing fsync cost. + /// A crash mid-batch loses at most K blocks of progress; on restart, resume from the + /// current `earliest`. + /// + /// Returns immediately if `target_earliest_block >= current earliest`. + pub fn run(&self, target_earliest_block: u64) -> Result<(), BackfillError> { + let current_earliest = self.storage.provider_ro()?.get_earliest_block()?; + + if target_earliest_block >= current_earliest.number { + return Ok(()); + } + self.drive_batched_loop(current_earliest, target_earliest_block, "standard", |bp, n| { + self.backfill_block(bp, n) + }) + } + + /// Shared batched per-block driver, used by [`Self::run`] and [`Self::run_with_snapshot`]. + /// + /// Holds one RW backfill provider open across `batch_size` blocks, dispatches per-block work + /// to the supplied closure (which uses the same `bp` for reads + writes, so MDBX same-tx + /// visibility makes in-flight writes from earlier blocks of the batch visible), and commits + /// once per batch. Mirrors the previous per-block `drive_loop` shape but with the bp opened + /// at the batch boundary instead of per call. + fn drive_batched_loop( + &self, + current_earliest: NumHash, + target_earliest_block: u64, + kind: &'static str, + mut process_block: F, + ) -> Result<(), BackfillError> + where + F: FnMut(&S::BackfillProvider<'_>, BlockNumber) -> Result, + { + let total = current_earliest.number - target_earliest_block; + let start = Instant::now(); + let mut phase_totals = PhaseTimings::default(); + info!( + target: "trie::backfill::job", + from = current_earliest.number, + to = target_earliest_block, + total, + batch_size = self.batch_size, + kind, + "Starting proofs backfill" + ); + + let mut next_block = current_earliest.number; + while next_block > target_earliest_block { + let batch_end = + next_block.saturating_sub(self.batch_size as u64).max(target_earliest_block); + let batch_low = batch_end + 1; + let batch_high = next_block; + + let bp = self.storage.backfill_provider()?; + + for block_number in (batch_low..=batch_high).rev() { + let timings = process_block(&bp, block_number)?; + phase_totals.add(timings); + + let done = current_earliest.number - block_number + 1; + let is_final = block_number == target_earliest_block + 1; + if done.is_multiple_of(LOG_EVERY) || is_final { + self.log_progress(start, done, total, &phase_totals); + } + } + + let (_, commit_duration) = timed(|| bp.commit())?; + // Amortize the batch's commit time across the blocks in it so per-block averages + // remain comparable across batch sizes. Note: intermediate `log_progress` lines + // fire before this add (inside the inner loop), so per-batch progress lines + // under-report `avg_commit` by one batch. The final summary logged after the + // outer loop exits is exact. + phase_totals.commit += commit_duration; + next_block = batch_end; + } + + let final_avg = phase_totals.averages(total); + info!( + target: "trie::backfill::job", + blocks = total, + elapsed = ?start.elapsed(), + avg_compute = ?final_avg.compute, + avg_prepend = ?final_avg.prepend, + avg_validate = ?final_avg.validate, + avg_commit = ?final_avg.commit, + kind, + "Proofs backfill complete" + ); + + Ok(()) + } + + /// Per-block work for the standard backfill path. Called inside [`Self::drive_batched_loop`] + /// with the batch's shared open RW provider; reads in [`Self::compute_diff_via`] go through + /// it so same-tx writes from earlier iterations are visible. + fn backfill_block( + &self, + bp: &S::BackfillProvider<'_>, + block_number: BlockNumber, + ) -> Result { + let block_ref = self.resolve_block_ref(block_number)?; + let (diff, compute) = self.compute_diff_via(bp, block_number)?; + let (_, prepend) = timed(|| bp.prepend_block(block_ref, diff))?; + let validate = self.validate_state_root(bp, block_number)?; + Ok(PhaseTimings { compute, prepend, validate, commit: Duration::ZERO }) + } + + fn log_progress(&self, start: Instant, done: u64, total: u64, phase_totals: &PhaseTimings) { + let elapsed_secs = start.elapsed().as_secs_f64(); + let blocks_per_sec = + if elapsed_secs.is_normal() { done as f64 / elapsed_secs } else { 0.0 }; + let eta_secs = if blocks_per_sec.is_normal() && blocks_per_sec > 0.0 { + (total - done) as f64 / blocks_per_sec + } else { + 0.0 + }; + let progress_pct = (done as f64 / total as f64) * 100.0; + let avg = phase_totals.averages(done); + info!( + target: "trie::backfill::job", + done, + total, + avg_compute = ?avg.compute, + avg_prepend = ?avg.prepend, + avg_validate = ?avg.validate, + avg_commit = ?avg.commit, + "progress: {progress_pct:.2}% ({blocks_per_sec:.1} blk/s, ETA {eta_secs:.0}s)" + ); + } + + /// Resolve the `(block, parent)` hashes for `block_number` from reth. + fn resolve_block_ref( + &self, + block_number: BlockNumber, + ) -> Result { + let block_hash = self + .provider + .block_hash(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + let parent_hash = self + .provider + .block_hash(block_number - 1)? + .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))?; + Ok(BlockWithParent { block: NumHash::new(block_number, block_hash), parent: parent_hash }) + } + + /// Compute the per-block backfill diff (trie node + leaf before-values) and time the call. + /// + /// The batched [`Self::run`] passes the open RW backfill provider so cursors see writes + /// made earlier in the same MDBX transaction (the prepended blocks of this batch). + /// MDBX same-tx visibility means each `compute_diff_via(&bp, N)` sees the in-flight + /// `prepend_block` writes for blocks > N. + fn compute_diff_via( + &self, + proofs_ro: &RO, + block_number: BlockNumber, + ) -> Result<(BlockStateDiff, Duration), BackfillError> + where + RO: OpProofsProviderRO, + { + timed(|| { + // History-aware cursors at `max_block_number = block_number`. + let trie_factory = OpProofsTrieCursorFactory::new(proofs_ro, block_number); + let hashed_factory = OpProofsHashedAccountCursorFactory::new(proofs_ro, block_number); + let (trie_updates, post_state) = compute_block_backfill_diff( + &self.provider, + trie_factory, + hashed_factory, + block_number, + )?; + Ok(BlockStateDiff { sorted_trie_updates: trie_updates, sorted_post_state: post_state }) + }) + } + + /// Validate the just-prepended diff by computing a full state root at + /// `block_number - 1` against the open backfill provider (which sees its + /// own uncommitted writes) and comparing to the reth header. + fn validate_state_root( + &self, + bp: &BP, + block_number: BlockNumber, + ) -> Result + where + BP: OpProofsProviderRO, + { + let (_, elapsed) = timed(|| -> Result<(), BackfillError> { + let expected_root = self + .provider + .header_by_number(block_number - 1)? + .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))? + .state_root(); + let computed_root = + StateRoot::overlay_root(bp, block_number - 1, HashedPostState::default())?; + if computed_root != expected_root { + return Err(BackfillError::StateRootMismatch { + block_number, + computed: computed_root, + expected: expected_root, + }); + } + Ok(()) + })?; + Ok(elapsed) + } +} + +impl BackfillJob +where + P: DBProvider + + StageCheckpointReader + + ChangeSetReader + + StorageChangeSetReader + + BlockNumReader + + BlockHashReader + + HeaderProvider + + StorageSettingsCache + + Send + + Sync, + S: OpProofsBackfillStore + Clone + Send, +{ + /// Backfill using a `Ready` snapshot to accelerate per-block reads. + /// + /// Mirrors [`Self::run`] but reads trie state at each iteration's + /// `block_number` from the snapshot tables instead of the V2 merge-walk, + /// and advances the snapshot anchor atomically with each `prepend_block` + /// (so the snapshot stays in sync with the moving `earliest`). + /// + /// **Snapshot preconditions** (handled internally by `ensure_snapshot_ready`): + /// - If no snapshot exists, runs [`SnapshotInitJob`] at the current `earliest` and then + /// proceeds. + /// - If a `Ready` snapshot exists at the current `earliest`, proceeds. + /// - Otherwise (snapshot at a different anchor, or partial build in progress at a different + /// anchor), errors out and asks the caller to drop or finish the snapshot. + pub fn run_with_snapshot(&self, target_earliest_block: u64) -> Result<(), BackfillError> { + let current_earliest = self.storage.provider_ro()?.get_earliest_block()?; + if target_earliest_block >= current_earliest.number { + return Ok(()); + } + self.ensure_snapshot_ready(current_earliest)?; + self.drive_batched_loop( + current_earliest, + target_earliest_block, + "snapshot-accelerated", + |bp, n| self.backfill_block_with_snapshot(bp, n), + ) + } + + /// Ensure a `Ready` snapshot exists at `current_earliest`. + /// + /// - `Completed` at matching anchor → ok, return. + /// - `Completed` at a different anchor → [`BackfillError::SnapshotAnchorMismatch`]. + /// - `NotStarted` / `InProgress` → delegate to [`SnapshotInitJob`] (which handles fresh build + /// and crash-resume; errors on drift). + fn ensure_snapshot_ready(&self, current_earliest: NumHash) -> Result<(), BackfillError> { + let target = BlockNumHash::new(current_earliest.number, current_earliest.hash); + let init_anchor = + self.storage.snapshot_initialization_provider()?.snapshot_init_anchor()?; + + if let (SnapshotInitStatus::Completed, Some(existing)) = + (init_anchor.status, init_anchor.block) + { + if existing == target { + return Ok(()); + } + return Err(BackfillError::SnapshotAnchorMismatch { + expected: target, + found: existing, + }); + } + + info!( + target: "trie::backfill::job", + anchor = ?target, + status = ?init_anchor.status, + "Bootstrapping snapshot before backfill" + ); + + SnapshotInitJob::new(&self.provider, self.storage.clone()).run(current_earliest.number)?; + Ok(()) + } + + /// Per-block work for the snapshot-accelerated path. Reads via the open RW provider's + /// snapshot cursors (which see in-flight `update_snapshot` writes via MDBX same-tx + /// visibility), then advances snapshot anchor + proofs window in the same tx, then + /// validates against reth's header at `E-1`. + fn backfill_block_with_snapshot( + &self, + bp: &S::BackfillProvider<'_>, + block_number: BlockNumber, + ) -> Result { + let block_ref = self.resolve_block_ref(block_number)?; + let (diff, compute) = self.compute_diff_with_snapshot_via(bp, block_number)?; + + // After this iteration the proofs window's earliest moves from E to E-1, so the + // snapshot anchor advances to the parent block. + let new_anchor = BlockNumHash::new(block_number - 1, block_ref.parent); + + // Advance snapshot anchor + proofs window in the same tx. + let (_, prepend) = timed(|| -> Result<(), BackfillError> { + bp.update_snapshot(new_anchor, &diff)?; + bp.prepend_block(block_ref, diff)?; + Ok(()) + })?; + + let validate = self.validate_state_root_with_snapshot(bp, block_number)?; + Ok(PhaseTimings { compute, prepend, validate, commit: Duration::ZERO }) + } + + /// Compute the per-block backfill diff using snapshot trie + leaf cursors. + /// + /// The batched [`Self::run_with_snapshot`] passes the open RW backfill provider + /// (which implements [`OpProofsSnapshotProviderRO`] via the trait hierarchy), so cursors + /// see the in-flight `update_snapshot` writes from earlier blocks in the same MDBX + /// transaction. + fn compute_diff_with_snapshot_via( + &self, + sp: &SP, + block_number: BlockNumber, + ) -> Result<(BlockStateDiff, Duration), BackfillError> + where + SP: OpProofsSnapshotProviderRO, + { + // `block_number` is unused on the snapshot path: the snapshot reflects state at its + // anchor, which the caller guaranteed equals `block_number` via `ensure_snapshot_ready` + // (or via the prior iteration's `update_snapshot` advancing the anchor). + let _ = block_number; + timed(|| { + let trie_factory = SnapshotTrieCursorFactory::new(sp); + let hashed_factory = SnapshotHashedCursorFactory::new(sp); + let (sorted_trie_updates, sorted_post_state) = compute_block_backfill_diff( + &self.provider, + trie_factory, + hashed_factory, + block_number, + )?; + Ok(BlockStateDiff { sorted_trie_updates, sorted_post_state }) + }) + } + + /// Validate the just-prepended state at `block_number - 1` using snapshot + /// cursors (the snapshot has been updated to anchor at `E-1` within the + /// same tx, so its reads reflect the new state). + fn validate_state_root_with_snapshot( + &self, + bp: &BP, + block_number: BlockNumber, + ) -> Result + where + BP: OpProofsSnapshotProviderRO, + { + let (_, elapsed) = timed(|| -> Result<(), BackfillError> { + let expected_root = self + .provider + .header_by_number(block_number - 1)? + .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))? + .state_root(); + + let state_sorted = HashedPostState::default().into_sorted(); + let computed_root = StateRoot::new( + SnapshotTrieCursorFactory::new(bp), + HashedPostStateCursorFactory::new( + SnapshotHashedCursorFactory::new(bp), + &state_sorted, + ), + ) + .root()?; + + if computed_root != expected_root { + return Err(BackfillError::StateRootMismatch { + block_number, + computed: computed_root, + expected: expected_root, + }); + } + Ok(()) + })?; + Ok(elapsed) + } +} diff --git a/vendor/reth-optimism-trie/src/backfill/mod.rs b/vendor/reth-optimism-trie/src/backfill/mod.rs new file mode 100644 index 0000000..e459a90 --- /dev/null +++ b/vendor/reth-optimism-trie/src/backfill/mod.rs @@ -0,0 +1,25 @@ +//! Backfill: extend the proofs window from `[earliest, latest]` to +//! `[target_earliest, latest]` where `target_earliest < earliest`. See +//! [`BackfillJob`] for the per-step implementation. +//! +//! `earliest` is a **base-state boundary**, not "oldest block with its own +//! changeset rows". To move it from `E` to `E-1` the job materializes block +//! `E`'s historical records (changesets + history-bitmap entries) and then +//! flips the marker — mirroring prune in reverse. +//! +//! ## Invariants per step +//! +//! - The proofs current-state tables are untouched; only history is written. +//! - `earliest` decreases by exactly one per successful step. +//! - Each step commits atomically, so a crash mid-backfill resumes cleanly from the current +//! `earliest`. + +mod changesets; +mod error; +mod job; + +#[cfg(test)] +mod tests; + +pub use error::BackfillError; +pub use job::{BackfillJob, DEFAULT_BACKFILL_BATCH_SIZE}; diff --git a/vendor/reth-optimism-trie/src/backfill/tests.rs b/vendor/reth-optimism-trie/src/backfill/tests.rs new file mode 100644 index 0000000..a0df442 --- /dev/null +++ b/vendor/reth-optimism-trie/src/backfill/tests.rs @@ -0,0 +1,821 @@ +//! Integration tests for [`BackfillJob`]. +//! +//! Chain-construction helpers live in [`crate::test_utils`] and are shared +//! with the snapshot tests. + +use super::{BackfillError, BackfillJob}; +use crate::{ + BlockStateDiff, OpProofsBackfillStore, OpProofsSnapshotInitProvider, + OpProofsSnapshotProviderRO, OpProofsStorageError, OpProofsStore, RethTrieStorageLayout, + SnapshotInitJob, SnapshotInitStatus, + api::{OpProofsProviderRO, OpProofsProviderRw}, + initialize::InitializationJob, + proof::DatabaseStateRoot, + test_utils::{ + build_chain_and_initialize_storage, build_chain_with_storage_writes_and_initialize_storage, + build_transfer_block, chain_spec_with_address, commit_block_to_database, create_storage, + deterministic_keypair, execute_block, public_key_to_address, + }, +}; +use alloy_consensus::BlockHeader; +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use alloy_primitives::{Address, B256}; +use reth_db::Database; +use reth_db_common::init::init_genesis; +use reth_evm::{ConfigureEvm, execute::Executor}; +use reth_evm_ethereum::EthEvmConfig; +use reth_provider::{ + DatabaseProviderFactory, HashedPostStateProvider, LatestStateProviderRef, StateRootProvider, + StorageSettingsCache, test_utils::create_test_provider_factory_with_chain_spec, +}; +use reth_revm::database::StateProviderDatabase; +use reth_trie::{HashedPostState, StateRoot}; +use serial_test::serial; + +// ============================ Tests ============================ + +#[test] +fn run_is_noop_when_target_at_or_above_earliest() { + // Build a chain of 3 blocks; storage initialized at block 3 (earliest = 3). + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(3); + + // target == earliest: no-op. + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(latest_num).unwrap(); + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); + } + + // target > earliest: also no-op. + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(latest_num + 100).unwrap(); + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); + } +} + +#[test] +fn run_errors_when_storage_uninitialized() { + let key_pair = deterministic_keypair(); + let chain_spec = chain_spec_with_address(public_key_to_address(key_pair.public_key())); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); + init_genesis(&provider_factory).unwrap(); + + // Storage created but never initialized — no earliest marker. + let storage = create_storage(); + let provider = provider_factory.database_provider_ro().unwrap(); + let err = BackfillJob::new(provider, storage).run(0).unwrap_err(); + assert!( + matches!(err, BackfillError::Storage(OpProofsStorageError::NoBlocksFound)), + "expected NoBlocksFound, got {err:?}" + ); +} + +#[test] +fn run_extends_window_backward_multi_block() { + // 5-block chain — exercises descending iteration across multiple + // `BackfillContext::step` calls. + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(5); + + { + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); + } + + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(0).unwrap(); + } + + let provider = provider_factory.database_provider_ro().unwrap(); + let genesis_hash = reth_provider::BlockHashReader::block_hash(&provider, 0).unwrap().unwrap(); + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); +} + +#[test] +fn run_extends_window_backward() { + // Smallest possible case: 1-block chain, single backfill step from 1 → 0. + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(1); + + // Sanity: earliest starts at the latest block. + { + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); + } + + // Backfill all the way down to block 0 (genesis). + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(0).unwrap(); + } + + // Earliest should now point at block 0 (the genesis hash). + let provider = provider_factory.database_provider_ro().unwrap(); + let genesis_hash = reth_provider::BlockHashReader::block_hash(&provider, 0).unwrap().unwrap(); + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); +} + +#[test] +fn run_extends_window_backward_with_storage_writes() { + // Every block calls `STORAGE_CONTRACT`, writing `block.number` to slot 0. + // This exercises the backfill code paths that are silent in plain-transfer + // tests: + // - `V2HashedStorageChangeSets` / `V2HashedStoragesHistory` writes during `prepend_block` + // (the slot value changes every block). + // - Storage-side reconstruction via `V2StorageCursor` at each historical block during the + // in-job `StateRoot::overlay_root` validation. + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + + { + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); + } + + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(0).unwrap(); + } + + let provider = provider_factory.database_provider_ro().unwrap(); + let genesis_hash = reth_provider::BlockHashReader::block_hash(&provider, 0).unwrap().unwrap(); + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); +} + +#[test] +fn backfill_then_forward_write_preserves_state_roots() { + // End-to-end check that backfill and forward writes can share a proofs DB + // without corrupting historical reads: + // + // 1. Build a 5-block reth chain. Init proofs at block 5 → earliest=latest=5. + // 2. Backfill earliest from 5 down to 2. + // 3. Build + forward-write blocks 6 and 7 via `store_trie_updates`. + // 4. Assert state roots at every block in [2, 7] match reth's headers. + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient = Address::repeat_byte(0x42); + let mut last_hash = chain_spec.genesis_hash(); + + // 1. Build blocks 1..=5 in reth. + for n in 1..=5u64 { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + } + + // 2. Initialize proofs storage at block 5. + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout).run(5, last_hash).unwrap(); + } + + // 3. Backfill earliest from 5 down to 2. + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(2).unwrap(); + } + { + let window = storage.provider_ro().unwrap().get_proof_window().unwrap(); + assert_eq!(window.earliest.number, 2); + assert_eq!(window.latest.number, 5); + } + + // 4. Build + forward-write blocks 6 and 7. + for n in 6..=7u64 { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + + // Execute the block + compute (state_root, trie_updates) against reth's + // current state. Mirrors `execute_block` but also returns the trie + // updates + hashed post-state needed to build a `BlockStateDiff`. + let (exec, hashed_state, trie_updates) = { + let provider = provider_factory.provider().unwrap(); + let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); + let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); + let block_executor = evm_config.batch_executor(db); + let exec = block_executor.execute(&block).unwrap(); + let hashed_state = + LatestStateProviderRef::new(&provider).hashed_post_state(&exec.state); + let (state_root, trie_updates) = LatestStateProviderRef::new(&provider) + .state_root_with_updates(hashed_state.clone()) + .unwrap(); + block.set_state_root(state_root); + (exec, hashed_state, trie_updates) + }; + + // Advance reth's chain to block n. + commit_block_to_database(&block, &exec, &provider_factory); + + // Forward-write block n into the proofs storage. + let rw = storage.provider_rw().unwrap(); + rw.store_trie_updates( + BlockWithParent { block: NumHash::new(n, block.hash()), parent: last_hash }, + BlockStateDiff { + sorted_trie_updates: trie_updates.into_sorted(), + sorted_post_state: hashed_state.into_sorted(), + }, + ) + .unwrap(); + OpProofsProviderRw::commit(rw).unwrap(); + + last_hash = block.hash(); + } + + // Window should now span [2, 7]. + { + let window = storage.provider_ro().unwrap().get_proof_window().unwrap(); + assert_eq!(window.earliest.number, 2); + assert_eq!(window.latest.number, 7); + } + + // 5. Validate state roots at every block in [2, 7] against reth's headers. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + for n in 2..=7u64 { + let expected = reth_provider::HeaderProvider::header_by_number(&reth_provider, n) + .unwrap() + .unwrap() + .state_root(); + let computed = + StateRoot::overlay_root(storage.provider_ro().unwrap(), n, HashedPostState::default()) + .unwrap(); + assert_eq!(computed, expected, "state root mismatch at block {n}"); + } +} + +/// Batched (K > 1) and per-block (K = 1) backfill must be observationally equivalent: both +/// runs land the same proof window and reconstruct the same state root at every block in it. +#[test] +fn run_batched_matches_unbatched_state_roots() { + use crate::test_utils::build_chain_with_storage_writes_and_initialize_storage; + + // Storage-writing chain exercises both account and storage history paths. + const N: u64 = 20; + const TARGET: u64 = 3; + + // Run 1: K=1 (per-block commits). + let (pf_a, storage_a, latest_a, _hash_a) = + build_chain_with_storage_writes_and_initialize_storage(N); + { + let provider = pf_a.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage_a.clone()).with_batch_size(1).run(TARGET).unwrap(); + } + + // Run 2: K=10 (batched commits). + let (pf_b, storage_b, latest_b, _hash_b) = + build_chain_with_storage_writes_and_initialize_storage(N); + { + let provider = pf_b.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage_b.clone()).with_batch_size(10).run(TARGET).unwrap(); + } + + assert_eq!(latest_a, latest_b, "chain construction is deterministic"); + assert_eq!(latest_a, N); + + // Both runs should leave `earliest` at the same place. + { + let win_a = storage_a.provider_ro().unwrap().get_proof_window().unwrap(); + let win_b = storage_b.provider_ro().unwrap().get_proof_window().unwrap(); + assert_eq!(win_a.earliest, win_b.earliest); + assert_eq!(win_a.latest, win_b.latest); + assert_eq!(win_a.earliest.number, TARGET); + } + + // Reconstruct state root at every block in [TARGET, N] from BOTH storages and confirm they + // agree with each other and with reth's header. If batching corrupted any per-block history, + // the overlay-root computation here would diverge. + let reth_provider_a = pf_a.database_provider_ro().unwrap(); + for n in TARGET..=N { + let expected = reth_provider::HeaderProvider::header_by_number(&reth_provider_a, n) + .unwrap() + .unwrap() + .state_root(); + let computed_a = StateRoot::overlay_root( + storage_a.provider_ro().unwrap(), + n, + HashedPostState::default(), + ) + .unwrap(); + let computed_b = StateRoot::overlay_root( + storage_b.provider_ro().unwrap(), + n, + HashedPostState::default(), + ) + .unwrap(); + assert_eq!(computed_a, expected, "K=1 path: state root mismatch at block {n}",); + assert_eq!(computed_b, expected, "K=10 path: state root mismatch at block {n}",); + assert_eq!(computed_a, computed_b, "K=1 vs K=10 disagree at block {n}"); + } +} + +/// Job-loop atomicity: a validation failure mid-batch drops the open RW tx, rolling back +/// the in-flight writes for every prior block in the same batch. +#[test] +fn run_rolls_back_in_flight_batch_writes_on_validation_failure() { + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient = Address::repeat_byte(0x42); + const NUM_BLOCKS: u64 = 5; + // Corrupt `header[2].state_root` — `validate_state_root` for block 3 will check it, + // *after* blocks 5 and 4 have prepended successfully within the same open tx. + const CORRUPTED_BLOCK: u64 = 2; + const FAILING_VALIDATE_BLOCK: u64 = CORRUPTED_BLOCK + 1; + const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); + + let mut last_hash = chain_spec.genesis_hash(); + for n in 1..=NUM_BLOCKS { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + if n == CORRUPTED_BLOCK { + block.set_state_root(BOGUS_ROOT); + } + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + } + + // Init proofs at the chain tip. + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(NUM_BLOCKS, last_hash) + .unwrap(); + } + let earliest_before = storage.provider_ro().unwrap().get_earliest_block().unwrap(); + assert_eq!(earliest_before, NumHash::new(NUM_BLOCKS, last_hash)); + + // K=10 means the whole 5-block range fits in one batch — no commit before the failure. + let provider = provider_factory.database_provider_ro().unwrap(); + let err = BackfillJob::new(provider, storage.clone()).with_batch_size(10).run(0).unwrap_err(); + + match err { + BackfillError::StateRootMismatch { block_number, expected, .. } => { + assert_eq!( + block_number, FAILING_VALIDATE_BLOCK, + "validation must fire at block {FAILING_VALIDATE_BLOCK} (parent header is bogus)", + ); + assert_eq!(expected, BOGUS_ROOT, "expected root comes from the tampered header"); + } + other => panic!("expected StateRootMismatch, got {other:?}"), + } + + // Atomicity check: blocks 5 and 4 had successfully prepended (and validated) within the open + // tx before iteration 3 failed. The tx dropped on `Err`, so none of those writes — nor block + // 3's prepend — persisted. `earliest` must still be at the pre-backfill value. + let earliest_after = storage.provider_ro().unwrap().get_earliest_block().unwrap(); + assert_eq!( + earliest_after, earliest_before, + "in-flight batch writes (blocks 5, 4, 3) must roll back when the batch fails", + ); +} + +// ============================ Snapshot-accelerated tests ============================ + +#[test] +#[serial] +fn run_with_snapshot_is_noop_when_target_at_or_above_earliest() { + // Build 3-block chain; storage init at block 3 (earliest = 3). + // `run_with_snapshot` must short-circuit before touching the snapshot, + // so no snapshot is bootstrapped. + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(3); + + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run_with_snapshot(latest_num).unwrap(); + } + + // Earliest unchanged. + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); + + // Snapshot was never bootstrapped — init anchor still NotStarted. + let init_anchor = + storage.snapshot_initialization_provider().unwrap().snapshot_init_anchor().unwrap(); + assert_eq!(init_anchor.status, SnapshotInitStatus::NotStarted); + assert_eq!(init_anchor.block, None); +} + +#[test] +#[serial] +fn run_with_snapshot_bootstraps_snapshot_when_missing() { + // 5-block chain; no snapshot exists. `run_with_snapshot` must bootstrap + // the snapshot at the current `earliest` and then backfill down to 0. + // After backfill the snapshot anchor must track the new `earliest`. + let (provider_factory, storage, _, _) = build_chain_and_initialize_storage(5); + + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run_with_snapshot(0).unwrap(); + } + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let genesis_hash = + reth_provider::BlockHashReader::block_hash(&reth_provider, 0).unwrap().unwrap(); + + // Earliest reached genesis. + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); + + let init_anchor = + storage.snapshot_initialization_provider().unwrap().snapshot_init_anchor().unwrap(); + assert_eq!(init_anchor.status, SnapshotInitStatus::Completed); + assert_eq!(init_anchor.block, Some(BlockNumHash::new(0, genesis_hash))); + + let sp = storage.snapshot_provider_ro().unwrap(); + assert_eq!(sp.snapshot_anchor().unwrap(), BlockNumHash::new(0, genesis_hash)); +} + +#[test] +#[serial] +fn run_with_snapshot_uses_existing_ready_snapshot() { + // Pre-initialize the snapshot at `earliest`, then run snapshot-accelerated + // backfill. The job must reuse the existing snapshot (no re-init) and + // still drive earliest to the target. + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(4); + + // Pre-init snapshot at the current earliest. + { + let reth_provider = provider_factory.database_provider_ro().unwrap(); + SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).unwrap(); + } + // Sanity: snapshot is Completed at (latest_num, latest_hash). + { + let init_anchor = + storage.snapshot_initialization_provider().unwrap().snapshot_init_anchor().unwrap(); + assert_eq!(init_anchor.status, SnapshotInitStatus::Completed); + assert_eq!(init_anchor.block, Some(BlockNumHash::new(latest_num, latest_hash))); + } + + // Snapshot-accelerated backfill all the way to genesis. + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run_with_snapshot(0).unwrap(); + } + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let genesis_hash = + reth_provider::BlockHashReader::block_hash(&reth_provider, 0).unwrap().unwrap(); + + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); + + let sp = storage.snapshot_provider_ro().unwrap(); + assert_eq!(sp.snapshot_anchor().unwrap(), BlockNumHash::new(0, genesis_hash)); +} + +#[test] +#[serial] +fn run_with_snapshot_errors_on_anchor_mismatch() { + // Plant a `Completed` snapshot at the initial `earliest`, then advance + // the proofs window via plain (non-snapshot) backfill so `earliest` + // diverges from the snapshot anchor. A subsequent `run_with_snapshot` + // call must refuse rather than silently corrupting the snapshot. + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(5); + + // Snapshot at (5, hash5). + { + let reth_provider = provider_factory.database_provider_ro().unwrap(); + SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).unwrap(); + } + + // Plain backfill from 5 down to 3 — leaves the snapshot anchor at (5, _) + // while earliest moves to (3, hash3). + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run(3).unwrap(); + } + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let hash3 = reth_provider::BlockHashReader::block_hash(&reth_provider, 3).unwrap().unwrap(); + + // Snapshot-accelerated backfill must detect the mismatch. + let err = BackfillJob::new(reth_provider, storage).run_with_snapshot(0).unwrap_err(); + match err { + BackfillError::SnapshotAnchorMismatch { expected, found } => { + assert_eq!(expected, BlockNumHash::new(3, hash3)); + assert_eq!(found, BlockNumHash::new(latest_num, latest_hash)); + } + other => panic!("expected SnapshotAnchorMismatch, got {other:?}"), + } +} + +#[test] +#[serial] +fn run_with_snapshot_extends_window_backward_with_storage_writes() { + // Every block touches a storage slot, so each iteration drives the + // storage-trie codepaths inside the snapshot writer (`update_snapshot`) + // and the snapshot storage cursors used by `validate_state_root_with_snapshot`. + let (provider_factory, storage, _latest_num, _latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + + { + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage.clone()).run_with_snapshot(0).unwrap(); + } + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let genesis_hash = + reth_provider::BlockHashReader::block_hash(&reth_provider, 0).unwrap().unwrap(); + + let ro = storage.provider_ro().unwrap(); + assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); + + let sp = storage.snapshot_provider_ro().unwrap(); + assert_eq!(sp.snapshot_anchor().unwrap(), BlockNumHash::new(0, genesis_hash)); +} + +/// Snapshot-accelerated batched backfill (K > 1) must produce the same proofs DB state and +/// snapshot anchor as the per-block path (K = 1). Mirrors +/// [`run_batched_matches_unbatched_state_roots`] for the snapshot path. +#[test] +#[serial] +fn run_with_snapshot_batched_matches_unbatched_state_roots() { + const N: u64 = 20; + const TARGET: u64 = 3; + + // Run A: K=1 (per-block commits on the snapshot path). + let (pf_a, storage_a, _, _) = build_chain_with_storage_writes_and_initialize_storage(N); + { + let provider = pf_a.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage_a.clone()) + .with_batch_size(1) + .run_with_snapshot(TARGET) + .unwrap(); + } + + // Run B: K=10 (batched commits, same path). + let (pf_b, storage_b, _, _) = build_chain_with_storage_writes_and_initialize_storage(N); + { + let provider = pf_b.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage_b.clone()) + .with_batch_size(10) + .run_with_snapshot(TARGET) + .unwrap(); + } + + // Earliest + snapshot anchor must match across both runs. + let win_a = storage_a.provider_ro().unwrap().get_proof_window().unwrap(); + let win_b = storage_b.provider_ro().unwrap().get_proof_window().unwrap(); + assert_eq!(win_a.earliest, win_b.earliest); + assert_eq!(win_a.latest, win_b.latest); + assert_eq!(win_a.earliest.number, TARGET); + + let anchor_a = storage_a.snapshot_provider_ro().unwrap().snapshot_anchor().unwrap(); + let anchor_b = storage_b.snapshot_provider_ro().unwrap().snapshot_anchor().unwrap(); + assert_eq!(anchor_a, anchor_b, "snapshot anchors must match across batch sizes"); + + // State roots from BOTH storages must agree with reth at every backfilled block. + let reth_provider = pf_a.database_provider_ro().unwrap(); + for n in TARGET..=N { + let expected = reth_provider::HeaderProvider::header_by_number(&reth_provider, n) + .unwrap() + .unwrap() + .state_root(); + let root_a = StateRoot::overlay_root( + storage_a.provider_ro().unwrap(), + n, + HashedPostState::default(), + ) + .unwrap(); + let root_b = StateRoot::overlay_root( + storage_b.provider_ro().unwrap(), + n, + HashedPostState::default(), + ) + .unwrap(); + assert_eq!(root_a, expected, "snapshot K=1: state root mismatch at block {n}",); + assert_eq!(root_b, expected, "snapshot K=10: state root mismatch at block {n}",); + } +} + +/// Negative test for the validation safety net in [`BackfillJob`]. Every +/// "happy path" test feeds a self-consistent chain, so the +/// [`BackfillError::StateRootMismatch`] arm in `validate_state_root` is never +/// taken — a bug there (wrong overlay block, inverted compare, etc.) would let +/// corrupt history through silently. Here we commit one block with a deliberately +/// wrong `state_root` field and assert the job aborts at the right block. +#[test] +fn run_aborts_with_state_root_mismatch_when_header_corrupted() { + // Custom chain build + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient = Address::repeat_byte(0x42); + const NUM_BLOCKS: u64 = 3; + const CORRUPTED_BLOCK: u64 = NUM_BLOCKS - 1; + const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); + + let mut last_hash = chain_spec.genesis_hash(); + for n in 1..=NUM_BLOCKS { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + if n == CORRUPTED_BLOCK { + block.set_state_root(BOGUS_ROOT); + } + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + } + + // Initialization captures *real* state at the latest block, so backfill's + // reconstruction at any prior block will produce the real state root — + // which disagrees with the bogus header at CORRUPTED_BLOCK. + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(NUM_BLOCKS, last_hash) + .unwrap(); + } + + let provider = provider_factory.database_provider_ro().unwrap(); + let err = BackfillJob::new(provider, storage).run(0).unwrap_err(); + match err { + BackfillError::StateRootMismatch { block_number, expected, .. } => { + // Backfill descends from `latest`; the first prepend is NUM_BLOCKS, + // which validates at CORRUPTED_BLOCK. + assert_eq!(block_number, NUM_BLOCKS, "validation must fire on the first prepend"); + assert_eq!(expected, BOGUS_ROOT, "expected root must come from the tampered header"); + } + other => panic!("expected StateRootMismatch, got {other:?}"), + } +} + +/// Snapshot-accelerated mirror of +/// [`run_aborts_with_state_root_mismatch_when_header_corrupted`]: the +/// validation step in `validate_state_root_with_snapshot` (the snapshot path's +/// safety net) must reject a mismatch between the snapshot's computed root and +/// reth's header at `E-1`. +#[test] +fn run_with_snapshot_aborts_with_state_root_mismatch_when_header_corrupted() { + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient = Address::repeat_byte(0x42); + const NUM_BLOCKS: u64 = 3; + + const CORRUPTED_BLOCK: u64 = NUM_BLOCKS - 1; + const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); + + let mut last_hash = chain_spec.genesis_hash(); + for n in 1..=NUM_BLOCKS { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + if n == CORRUPTED_BLOCK { + block.set_state_root(BOGUS_ROOT); + } + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + } + + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(NUM_BLOCKS, last_hash) + .unwrap(); + } + + let provider = provider_factory.database_provider_ro().unwrap(); + let err = BackfillJob::new(provider, storage).run_with_snapshot(0).unwrap_err(); + match err { + BackfillError::StateRootMismatch { block_number, expected, .. } => { + assert_eq!( + block_number, NUM_BLOCKS, + "validation must fire on the first snapshot-accelerated prepend", + ); + assert_eq!(expected, BOGUS_ROOT, "expected root must come from the tampered header"); + } + other => panic!("expected StateRootMismatch, got {other:?}"), + } +} + +/// Negative test for the changeset-pruned detection in +/// [`compute_block_backfill_diff`](super::changesets::compute_block_backfill_diff). +/// +/// When reth prunes a block it typically deletes the per-block account/storage changesets +/// together with the body. Without detection, `from_reverts_auto` returns an empty revert, +/// the reconstructed trie@N-1 equals trie@N, and validation surfaces the misleading +/// [`BackfillError::StateRootMismatch`]. The fix detects the case directly and returns +/// [`BackfillError::BlockBodyPruned`] instead. +/// +/// We exercise this by: +/// 1. Building a 3-block transfer chain with `StorageSettings::v1()` (so changesets live in MDBX +/// and are surgically deletable). +/// 2. Deleting every `AccountChangeSets` entry at the targeted block. +/// 3. Running backfill and asserting `BlockBodyPruned(n)` rather than `StateRootMismatch`. +#[test] +fn run_aborts_with_block_body_pruned_when_changesets_deleted() { + use reth_db::{ + cursor::{DbCursorRO, DbDupCursorRW}, + tables, + transaction::{DbTx, DbTxMut}, + }; + use reth_db_api::models::StorageSettings; + use reth_db_common::init::init_genesis_with_settings; + + // Custom chain build: force v1 so changesets land in MDBX (deletable via cursor). + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis_with_settings(&provider_factory, StorageSettings::v1()).unwrap(); + + let recipient = Address::repeat_byte(0x42); + const NUM_BLOCKS: u64 = 3; + const PRUNED_BLOCK: u64 = 2; + + let mut last_hash = chain_spec.genesis_hash(); + for n in 1..=NUM_BLOCKS { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + } + + // Simulate reth pruning: delete every AccountChangeSets entry at PRUNED_BLOCK. + // Transfer-only txs produce account changesets but no storage changesets, so the + // account table is enough to break the per-block revert at that height. + { + let tx = provider_factory.db_ref().tx_mut().unwrap(); + let mut cursor = tx.cursor_dup_write::().unwrap(); + let found = cursor.seek_exact(PRUNED_BLOCK).unwrap(); + assert!(found.is_some(), "block {PRUNED_BLOCK} must have changeset entries pre-deletion"); + cursor.delete_current_duplicates().unwrap(); + assert!(cursor.seek_exact(PRUNED_BLOCK).unwrap().is_none(), "deletion left residue"); + drop(cursor); + tx.commit().unwrap(); + } + + // Initialize proofs storage at the chain tip. + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(NUM_BLOCKS, last_hash) + .unwrap(); + } + + // Backfill descends from NUM_BLOCKS down. Block NUM_BLOCKS prepends successfully; the + // detection fires when the job moves on to PRUNED_BLOCK. + let provider = provider_factory.database_provider_ro().unwrap(); + let err = BackfillJob::new(provider, storage).run(0).unwrap_err(); + match err { + BackfillError::BlockBodyPruned(block_number) => { + assert_eq!( + block_number, PRUNED_BLOCK, + "detection must fire at the block whose changesets are missing", + ); + } + other => panic!("expected BlockBodyPruned, got {other:?}"), + } +} diff --git a/vendor/reth-optimism-trie/src/cursor.rs b/vendor/reth-optimism-trie/src/cursor.rs new file mode 100644 index 0000000..732e806 --- /dev/null +++ b/vendor/reth-optimism-trie/src/cursor.rs @@ -0,0 +1,129 @@ +//! Implementation of [`HashedCursor`] and [`TrieCursor`] for +//! [`OpProofsStorage`](crate::OpProofsStorage). + +use alloy_primitives::{B256, U256}; +use derive_more::Constructor; +use reth_db::DatabaseError; +use reth_primitives_traits::Account; +use reth_trie::{ + hashed_cursor::{HashedCursor, HashedStorageCursor}, + trie_cursor::{TrieCursor, TrieStorageCursor}, +}; +use reth_trie_common::{BranchNodeCompact, Nibbles}; + +/// Manages reading storage or account trie nodes from [`TrieCursor`]. +#[derive(Debug, Clone, Constructor)] +pub struct OpProofsTrieCursor(pub C); + +impl TrieCursor for OpProofsTrieCursor +where + C: TrieCursor, +{ + #[inline] + fn seek_exact( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + self.0.seek_exact(key) + } + + #[inline] + fn seek( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + self.0.seek(key) + } + + #[inline] + fn next(&mut self) -> Result, DatabaseError> { + self.0.next() + } + + #[inline] + fn current(&mut self) -> Result, DatabaseError> { + self.0.current() + } + + #[inline] + fn reset(&mut self) { + self.0.reset() + } +} + +impl TrieStorageCursor for OpProofsTrieCursor +where + C: TrieStorageCursor, +{ + #[inline] + fn set_hashed_address(&mut self, hashed_address: B256) { + self.0.set_hashed_address(hashed_address) + } +} + +/// Manages reading hashed account nodes from external storage. +#[derive(Debug, Clone, Constructor)] +pub struct OpProofsHashedAccountCursor(pub C); + +impl HashedCursor for OpProofsHashedAccountCursor +where + C: HashedCursor + Send, +{ + type Value = Account; + + #[inline] + fn seek(&mut self, key: B256) -> Result, DatabaseError> { + self.0.seek(key) + } + + #[inline] + fn next(&mut self) -> Result, DatabaseError> { + self.0.next() + } + + #[inline] + fn reset(&mut self) { + self.0.reset() + } +} + +/// Manages reading hashed storage nodes from external storage. +#[derive(Debug, Clone, Constructor)] +pub struct OpProofsHashedStorageCursor(pub C); + +impl HashedCursor for OpProofsHashedStorageCursor +where + C: HashedCursor + Send, +{ + type Value = U256; + + #[inline] + fn seek(&mut self, key: B256) -> Result, DatabaseError> { + self.0.seek(key) + } + + #[inline] + fn next(&mut self) -> Result, DatabaseError> { + self.0.next() + } + + #[inline] + fn reset(&mut self) { + self.0.reset() + } +} + +impl HashedStorageCursor for OpProofsHashedStorageCursor +where + C: HashedStorageCursor + Send, +{ + #[inline] + fn is_storage_empty(&mut self) -> Result { + self.0.is_storage_empty() + } + + #[inline] + fn set_hashed_address(&mut self, hashed_address: B256) { + self.0.set_hashed_address(hashed_address) + } +} diff --git a/vendor/reth-optimism-trie/src/cursor_factory.rs b/vendor/reth-optimism-trie/src/cursor_factory.rs new file mode 100644 index 0000000..cb21a26 --- /dev/null +++ b/vendor/reth-optimism-trie/src/cursor_factory.rs @@ -0,0 +1,196 @@ +//! Implements [`TrieCursorFactory`] and [`HashedCursorFactory`] for [`crate::OpProofsStore`] types. + +use crate::{ + api::{OpProofsProviderRO, OpProofsSnapshotProviderRO}, + cursor::{OpProofsHashedAccountCursor, OpProofsHashedStorageCursor, OpProofsTrieCursor}, +}; +use alloy_primitives::B256; +use reth_db::DatabaseError; +use reth_trie::{hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory}; + +/// Factory for creating trie cursors for [`OpProofsProviderRO`]. +#[derive(Debug, Clone)] +pub struct OpProofsTrieCursorFactory

{ + provider: P, + block_number: u64, +} + +impl OpProofsTrieCursorFactory

{ + /// Initializes new `OpProofsTrieCursorFactory` + pub const fn new(provider: P, block_number: u64) -> Self { + Self { provider, block_number } + } +} + +impl

TrieCursorFactory for OpProofsTrieCursorFactory

+where + P: OpProofsProviderRO, +{ + type AccountTrieCursor<'a> + = OpProofsTrieCursor> + where + Self: 'a; + type StorageTrieCursor<'a> + = OpProofsTrieCursor> + where + Self: 'a; + + fn account_trie_cursor(&self) -> Result, DatabaseError> { + Ok(OpProofsTrieCursor::new( + self.provider + .account_trie_cursor(self.block_number) + .map_err(Into::::into)?, + )) + } + + fn storage_trie_cursor( + &self, + hashed_address: B256, + ) -> Result, DatabaseError> { + Ok(OpProofsTrieCursor::new( + self.provider + .storage_trie_cursor(hashed_address, self.block_number) + .map_err(Into::::into)?, + )) + } +} + +/// Factory for creating trie cursors backed by a snapshot reader. +/// +/// Unlike [`OpProofsTrieCursorFactory`] (which reads history-aware cursors at +/// a given block number), this factory reads directly from the snapshot +/// tables. It carries no block-number context: the snapshot already reflects +/// trie state at a fixed anchor block. The caller is responsible for first +/// resolving that anchor via +/// [`crate::api::OpProofsSnapshotProviderRO::snapshot_anchor`] and ensuring +/// the block being queried matches it. +#[derive(Debug, Clone)] +pub struct SnapshotTrieCursorFactory

{ + reader: P, +} + +impl SnapshotTrieCursorFactory

{ + /// Create a new snapshot-backed trie cursor factory. + pub const fn new(reader: P) -> Self { + Self { reader } + } +} + +impl

TrieCursorFactory for SnapshotTrieCursorFactory

+where + P: OpProofsSnapshotProviderRO, +{ + type AccountTrieCursor<'a> + = P::SnapshotAccountTrieCursor<'a> + where + Self: 'a; + type StorageTrieCursor<'a> + = P::SnapshotStorageTrieCursor<'a> + where + Self: 'a; + + fn account_trie_cursor(&self) -> Result, DatabaseError> { + self.reader.snapshot_account_trie_cursor().map_err(Into::::into) + } + + fn storage_trie_cursor( + &self, + hashed_address: B256, + ) -> Result, DatabaseError> { + self.reader + .snapshot_storage_trie_cursor(hashed_address) + .map_err(Into::::into) + } +} + +/// Factory for creating hashed account cursors for [`OpProofsProviderRO`]. +#[derive(Debug, Clone)] +pub struct OpProofsHashedAccountCursorFactory

{ + provider: P, + block_number: u64, +} + +impl OpProofsHashedAccountCursorFactory

{ + /// Creates a new `OpProofsHashedAccountCursorFactory` instance. + pub const fn new(provider: P, block_number: u64) -> Self { + Self { provider, block_number } + } +} + +impl

HashedCursorFactory for OpProofsHashedAccountCursorFactory

+where + P: OpProofsProviderRO, +{ + type AccountCursor<'a> + = OpProofsHashedAccountCursor> + where + Self: 'a; + type StorageCursor<'a> + = OpProofsHashedStorageCursor> + where + Self: 'a; + + fn hashed_account_cursor(&self) -> Result, DatabaseError> { + Ok(OpProofsHashedAccountCursor::new( + self.provider + .account_hashed_cursor(self.block_number) + .map_err(Into::::into)?, + )) + } + + fn hashed_storage_cursor( + &self, + hashed_address: B256, + ) -> Result, DatabaseError> { + Ok(OpProofsHashedStorageCursor::new( + self.provider + .storage_hashed_cursor(hashed_address, self.block_number) + .map_err(Into::::into)?, + )) + } +} + +/// Factory for creating hashed leaf cursors backed by the snapshot tables. +/// +/// Mirrors [`SnapshotTrieCursorFactory`]'s role for hashed leaves: reads +/// directly from [`crate::db::V2HashedAccountsSnapshot`] and +/// [`crate::db::V2HashedStoragesSnapshot`] without history merges. Valid only +/// when the snapshot is `Ready` at the anchor the caller is reading. +#[derive(Debug, Clone)] +pub struct SnapshotHashedCursorFactory

{ + reader: P, +} + +impl SnapshotHashedCursorFactory

{ + /// Create a new snapshot-backed hashed cursor factory. + pub const fn new(reader: P) -> Self { + Self { reader } + } +} + +impl

HashedCursorFactory for SnapshotHashedCursorFactory

+where + P: OpProofsSnapshotProviderRO, +{ + type AccountCursor<'a> + = P::SnapshotHashedAccountCursor<'a> + where + Self: 'a; + type StorageCursor<'a> + = P::SnapshotHashedStorageCursor<'a> + where + Self: 'a; + + fn hashed_account_cursor(&self) -> Result, DatabaseError> { + self.reader.snapshot_hashed_account_cursor().map_err(Into::::into) + } + + fn hashed_storage_cursor( + &self, + hashed_address: B256, + ) -> Result, DatabaseError> { + self.reader + .snapshot_hashed_storage_cursor(hashed_address) + .map_err(Into::::into) + } +} diff --git a/vendor/reth-optimism-trie/src/db/cursor.rs b/vendor/reth-optimism-trie/src/db/cursor.rs new file mode 100644 index 0000000..1477cc9 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/cursor.rs @@ -0,0 +1,1465 @@ +use std::marker::PhantomData; + +use crate::{ + OpProofsStorageResult, + db::{ + AccountTrieHistory, HashedAccountHistory, HashedStorageHistory, HashedStorageKey, + MaybeDeleted, StorageTrieHistory, StorageTrieKey, VersionedValue, + }, +}; +use alloy_primitives::{B256, U256}; +use reth_db::{ + DatabaseError, + cursor::{DbCursorRO, DbDupCursorRO}, + table::{DupSort, Table}, +}; +use reth_primitives_traits::Account; +use reth_trie::{ + hashed_cursor::{HashedCursor, HashedStorageCursor}, + trie_cursor::{TrieCursor, TrieStorageCursor}, +}; +use reth_trie_common::{BranchNodeCompact, Nibbles, StoredNibbles}; + +/// Iterates versioned dup-sorted rows and returns the latest value (<= `max_block_number`), +/// skipping tombstones. +#[derive(Debug, Clone)] +pub struct BlockNumberVersionedCursor { + _table: PhantomData, + cursor: Cursor, + max_block_number: u64, +} + +impl BlockNumberVersionedCursor +where + T: Table> + DupSort, + Cursor: DbCursorRO + DbDupCursorRO, +{ + /// Initializes new [`BlockNumberVersionedCursor`]. + pub const fn new(cursor: Cursor, max_block_number: u64) -> Self { + Self { _table: PhantomData, cursor, max_block_number } + } + + /// Check if the cursor is currently positioned at a valid row. + fn is_positioned(&mut self) -> OpProofsStorageResult { + Ok(self.cursor.current()?.is_some()) + } + + /// Resolve the latest version for `key` with `block_number` <= `max_block_number`. + /// Strategy: + /// - `seek_by_key_subkey(key, max)` gives first dup >= max. + /// - if exactly == max → it's our latest + /// - if > max → `prev_dup()` is latest < max (or None) + /// - if no dup >= max: + /// - if key exists → `last_dup()` is latest < max + /// - else → None + fn latest_version_for_key( + &mut self, + key: T::Key, + ) -> OpProofsStorageResult> { + // First dup with subkey >= max_block_number + let seek_res = self.cursor.seek_by_key_subkey(key.clone(), self.max_block_number)?; + + if let Some(vv) = seek_res { + if vv.block_number > self.max_block_number { + // step back to the last dup < max + return Ok(self.cursor.prev_dup()?); + } + // already at the dup = max + return Ok(Some((key, vv))); + } + + // No dup >= max ⇒ either key absent or all dups < max. Check if key exists: + if self.cursor.seek_exact(key.clone())?.is_none() { + return Ok(None); + } + + // Key exists ⇒ take last dup (< max). + if let Some(vv) = self.cursor.last_dup()? { + return Ok(Some((key, vv))); + } + Ok(None) + } + + /// Returns a non-deleted latest version for exactly `key`, if any. + fn seek_exact(&mut self, key: T::Key) -> OpProofsStorageResult> { + if let Some((latest_key, latest_value)) = self.latest_version_for_key(key)? && + let MaybeDeleted(Some(v)) = latest_value.value + { + return Ok(Some((latest_key, v))); + } + Ok(None) + } + + /// Walk forward from `first_key` (inclusive) until we find a *live* latest-≤-max value. + /// `first_key` must already be a *real key* in the table. + fn next_live_from( + &mut self, + mut first_key: T::Key, + ) -> OpProofsStorageResult> { + loop { + // Compute latest version ≤ max for this key + if let Some((k, v)) = self.seek_exact(first_key.clone())? { + return Ok(Some((k, v))); + } + + // Move to next distinct key, or EOF + let Some((next_key, _)) = self.cursor.next_no_dup()? else { + return Ok(None); + }; + + first_key = next_key; + } + } + + /// Seek to the first non-deleted latest version at or after `start_key`. + /// Logic: + /// - Try exact key first (above). If alive, return it. + /// - Otherwise hop to next distinct key and repeat until we find a live version or hit EOF. + fn seek(&mut self, start_key: T::Key) -> OpProofsStorageResult> { + // Position MDBX at first key >= start_key + if let Some((first_key, _)) = self.cursor.seek(start_key)? { + return self.next_live_from(first_key); + } + Ok(None) + } + + /// Advance to the next distinct key from the current MDBX position + /// and return its non-deleted latest version, if any. + /// Next distinct key; if not positioned, start from `T::Key::default()`. + fn next(&mut self) -> OpProofsStorageResult> + where + T::Key: Default, + { + // If not positioned, start from the beginning (default key). + if self.cursor.current()?.is_none() { + let Some((first_key, _)) = self.cursor.seek(T::Key::default())? else { + return Ok(None); + }; + return self.next_live_from(first_key); + } + + // Otherwise advance to next distinct key and resume the walk. + let Some((next_key, _)) = self.cursor.next_no_dup()? else { + return Ok(None); + }; + self.next_live_from(next_key) + } +} + +/// MDBX implementation of [`TrieCursor`]. +#[derive(Debug)] +pub struct MdbxTrieCursor { + inner: BlockNumberVersionedCursor, + hashed_address: Option, +} + +impl< + V, + T: Table> + DupSort, + Cursor: DbCursorRO + DbDupCursorRO, +> MdbxTrieCursor +{ + /// Initializes new [`MdbxTrieCursor`]. + pub const fn new(cursor: Cursor, max_block_number: u64, hashed_address: Option) -> Self { + Self { inner: BlockNumberVersionedCursor::new(cursor, max_block_number), hashed_address } + } +} + +impl TrieCursor for MdbxTrieCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + fn seek_exact( + &mut self, + path: Nibbles, + ) -> Result, DatabaseError> { + Ok(self + .inner + .seek_exact(StoredNibbles(path)) + .map(|opt| opt.map(|(StoredNibbles(n), node)| (n, node)))?) + } + + fn seek( + &mut self, + path: Nibbles, + ) -> Result, DatabaseError> { + Ok(self + .inner + .seek(StoredNibbles(path)) + .map(|opt| opt.map(|(StoredNibbles(n), node)| (n, node)))?) + } + + fn next(&mut self) -> Result, DatabaseError> { + Ok(self.inner.next().map(|opt| opt.map(|(StoredNibbles(n), node)| (n, node)))?) + } + + fn current(&mut self) -> Result, DatabaseError> { + self.inner.cursor.current().map(|opt| opt.map(|(StoredNibbles(n), _)| n)) + } + + fn reset(&mut self) { + // Database cursors are stateless, no reset needed + } +} + +impl TrieCursor for MdbxTrieCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + fn seek_exact( + &mut self, + path: Nibbles, + ) -> Result, DatabaseError> { + if let Some(address) = self.hashed_address { + let key = StorageTrieKey::new(address, StoredNibbles(path)); + return Ok(self.inner.seek_exact(key).map(|opt| { + opt.and_then(|(k, node)| (k.hashed_address == address).then_some((k.path.0, node))) + })?); + } + Ok(None) + } + + fn seek( + &mut self, + path: Nibbles, + ) -> Result, DatabaseError> { + if let Some(address) = self.hashed_address { + let key = StorageTrieKey::new(address, StoredNibbles(path)); + return Ok(self.inner.seek(key).map(|opt| { + opt.and_then(|(k, node)| (k.hashed_address == address).then_some((k.path.0, node))) + })?); + } + Ok(None) + } + + fn next(&mut self) -> Result, DatabaseError> { + if let Some(address) = self.hashed_address { + // If the cursor is not positioned, we need to seek to the first key for our bound + // address to ensure we start iterating from the correct position in the + // table. This is necessary because BlockNumberVersionedCursor::next() would + // otherwise start from T::Key::default() (the beginning of the entire + // table), which would cause us to miss entries for non-first addresses. + if !self.inner.is_positioned()? { + return self.seek(Nibbles::default()); + } + + return Ok(self.inner.next().map(|opt| { + opt.and_then(|(k, node)| (k.hashed_address == address).then_some((k.path.0, node))) + })?); + } + Ok(None) + } + + fn current(&mut self) -> Result, DatabaseError> { + if let Some(address) = self.hashed_address { + return self.inner.cursor.current().map(|opt| { + opt.and_then(|(k, _)| (k.hashed_address == address).then_some(k.path.0)) + }); + } + Ok(None) + } + + fn reset(&mut self) { + // Database cursors are stateless, no reset needed + } +} + +impl TrieStorageCursor for MdbxTrieCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + fn set_hashed_address(&mut self, hashed_address: B256) { + self.hashed_address = Some(hashed_address); + } +} + +/// MDBX implementation of [`HashedCursor`] for storage state. +#[derive(Debug)] +pub struct MdbxStorageCursor { + inner: BlockNumberVersionedCursor, + hashed_address: B256, +} + +impl MdbxStorageCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + /// Initializes new [`MdbxStorageCursor`] + pub const fn new(cursor: Cursor, block_number: u64, hashed_address: B256) -> Self { + Self { inner: BlockNumberVersionedCursor::new(cursor, block_number), hashed_address } + } +} + +impl HashedCursor for MdbxStorageCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + type Value = U256; + + fn seek(&mut self, key: B256) -> Result, DatabaseError> { + let storage_key = HashedStorageKey::new(self.hashed_address, key); + + // hashed storage values can be zero, which means the storage slot is deleted, so we should + // skip those + let result = self.inner.seek(storage_key).map(|opt| { + opt.and_then(|(k, v)| { + // Only return entries that belong to the bound address + (k.hashed_address == self.hashed_address).then_some((k.hashed_storage_key, v.0)) + }) + })?; + + if let Some((_, v)) = result && + v.is_zero() + { + return self.next(); + } + + Ok(result) + } + + fn next(&mut self) -> Result, DatabaseError> { + // If the cursor is not positioned, we need to seek to the first key for our bound address + // to ensure we start iterating from the correct position in the table. + // This is necessary because BlockNumberVersionedCursor::next() would otherwise start + // from T::Key::default() (the beginning of the entire table), which would cause us + // to miss entries for non-first addresses. + if !self.inner.is_positioned()? { + return self.seek(B256::ZERO); + } + + loop { + let result = self.inner.next().map(|opt| { + opt.and_then(|(k, v)| { + // Only return entries that belong to the bound address + (k.hashed_address == self.hashed_address).then_some((k.hashed_storage_key, v.0)) + }) + })?; + + // hashed storage values can be zero, which means the storage slot is deleted, so we + // should skip those + if let Some((_, v)) = result && + v.is_zero() + { + continue; + } + + return Ok(result); + } + } + + fn reset(&mut self) { + // Database cursors are stateless, no reset needed + } +} + +impl HashedStorageCursor for MdbxStorageCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + fn is_storage_empty(&mut self) -> Result { + Ok(self.seek(B256::ZERO)?.is_none()) + } + + fn set_hashed_address(&mut self, hashed_address: B256) { + self.hashed_address = hashed_address + } +} + +/// MDBX implementation of [`HashedCursor`] for account state. +#[derive(Debug)] +pub struct MdbxAccountCursor { + inner: BlockNumberVersionedCursor, +} + +impl MdbxAccountCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + /// Initializes new `MdbxAccountCursor` + pub const fn new(cursor: Cursor, block_number: u64) -> Self { + Self { inner: BlockNumberVersionedCursor::new(cursor, block_number) } + } +} + +impl HashedCursor for MdbxAccountCursor +where + Cursor: DbCursorRO + DbDupCursorRO + Send, +{ + type Value = Account; + + fn seek(&mut self, key: B256) -> Result, DatabaseError> { + Ok(self.inner.seek(key)?) + } + + fn next(&mut self) -> Result, DatabaseError> { + Ok(self.inner.next()?) + } + + fn reset(&mut self) { + // Database cursors are stateless, no reset needed + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::{StorageValue, models}; + use reth_db::{ + Database, DatabaseEnv, + mdbx::{DatabaseArguments, init_db_for}, + }; + use reth_db_api::{ + cursor::DbDupCursorRW, + transaction::{DbTx, DbTxMut}, + }; + + type Dup<'tx, T> = <::TX as DbTx>::DupCursor; + use reth_trie::{BranchNodeCompact, Nibbles, StoredNibbles}; + use tempfile::TempDir; + + fn setup_db() -> DatabaseEnv { + let tmp = TempDir::new().expect("create tmpdir"); + init_db_for::<_, models::Tables>(tmp, DatabaseArguments::default()).expect("init db") + } + + fn stored(path: Nibbles) -> StoredNibbles { + StoredNibbles(path) + } + + fn node() -> BranchNodeCompact { + BranchNodeCompact::default() + } + + fn append_account_trie( + wtx: &::TXMut, + key: StoredNibbles, + block: u64, + val: Option, + ) { + let mut c = wtx.cursor_dup_write::().expect("dup write cursor"); + let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) }; + c.append_dup(key, vv).expect("append dup"); + } + + fn append_storage_trie( + wtx: &::TXMut, + address: B256, + path: Nibbles, + block: u64, + val: Option, + ) { + let mut c = wtx.cursor_dup_write::().expect("dup write cursor"); + let key = StorageTrieKey::new(address, StoredNibbles(path)); + let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) }; + c.append_dup(key, vv).expect("append dup"); + } + + fn append_hashed_storage( + wtx: &::TXMut, + addr: B256, + slot: B256, + block: u64, + val: Option, + ) { + let mut c = wtx.cursor_dup_write::().expect("dup write"); + let key = HashedStorageKey::new(addr, slot); + let vv = VersionedValue { block_number: block, value: MaybeDeleted(val.map(StorageValue)) }; + c.append_dup(key, vv).expect("append dup"); + } + + fn append_hashed_account( + wtx: &::TXMut, + key: B256, + block: u64, + val: Option, + ) { + let mut c = wtx.cursor_dup_write::().expect("dup write"); + let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) }; + c.append_dup(key, vv).expect("append dup"); + } + + // Open a dup-RO cursor and wrap it in a BlockNumberVersionedCursor with a given bound. + fn version_cursor( + tx: &::TX, + max_block: u64, + ) -> BlockNumberVersionedCursor> { + let cur = tx.cursor_dup_read::().expect("dup ro cursor"); + BlockNumberVersionedCursor::new(cur, max_block) + } + + fn account_trie_cursor( + tx: &'_ ::TX, + max_block: u64, + ) -> MdbxTrieCursor> { + let c = tx.cursor_dup_read::().expect("dup ro cursor"); + // For account trie the address is not used; pass None. + MdbxTrieCursor::new(c, max_block, None) + } + + // Helper: build a Storage trie cursor bound to an address + fn storage_trie_cursor( + tx: &'_ ::TX, + max_block: u64, + address: B256, + ) -> MdbxTrieCursor> { + let c = tx.cursor_dup_read::().expect("dup ro cursor"); + MdbxTrieCursor::new(c, max_block, Some(address)) + } + + fn storage_cursor( + tx: &'_ ::TX, + max_block: u64, + address: B256, + ) -> MdbxStorageCursor> { + let c = tx.cursor_dup_read::().expect("dup ro cursor"); + MdbxStorageCursor::new(c, max_block, address) + } + + fn account_cursor( + tx: &'_ ::TX, + max_block: u64, + ) -> MdbxAccountCursor> { + let c = tx.cursor_dup_read::().expect("dup ro cursor"); + MdbxAccountCursor::new(c, max_block) + } + + // Assert helper: ensure the chosen VersionedValue has the expected block and deletion flag. + fn assert_block( + got: Option<(StoredNibbles, VersionedValue)>, + expected_block: u64, + expect_deleted: bool, + ) { + let (_, vv) = got.expect("expected Some(..)"); + assert_eq!(vv.block_number, expected_block, "wrong block chosen"); + let is_deleted = matches!(vv.value, MaybeDeleted(None)); + assert_eq!(is_deleted, expect_deleted, "tombstone mismatch"); + } + + /// No entry for key → None. + #[test] + fn latest_version_for_key_none_when_key_absent() { + let db = setup_db(); + let tx = db.tx().expect("ro tx"); + let mut cursor = version_cursor(&tx, 100); + + let out = cursor + .latest_version_for_key(stored(Nibbles::default())) + .expect("should not return error"); + assert!(out.is_none(), "absent key must return None"); + } + + /// Exact match at max (live) → pick it. + #[test] + fn latest_version_for_key_picks_value_at_max_if_present() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 10, Some(node())); + append_account_trie(&wtx, k.clone(), 50, Some(node())); // == max + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 50); + + let out = core.latest_version_for_key(k).expect("ok"); + assert_block(out, 50, false); + } + + /// When `seek_by_key_subkey` points to the subkey > max - fallback to the prev. + #[test] + fn latest_version_for_key_picks_latest_below_max_when_next_is_above() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 10, Some(node())); + append_account_trie(&wtx, k.clone(), 30, Some(node())); // expected + append_account_trie(&wtx, k.clone(), 70, Some(node())); // > max + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 50); + + let out = core.latest_version_for_key(k).expect("ok"); + assert_block(out, 30, false); + } + + /// No ≥ max but key exists → use last < max. + #[test] + fn latest_version_for_key_picks_last_below_max_when_none_at_or_above() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 10, Some(node())); + append_account_trie(&wtx, k.clone(), 40, Some(node())); // expected (max=100) + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 100); + + let out = core.latest_version_for_key(k).expect("ok"); + assert_block(out, 40, false); + } + + /// All entries are > max → None. + #[test] + fn latest_version_for_key_none_when_everything_is_above_max() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1.clone(), 60, Some(node())); + append_account_trie(&wtx, k1.clone(), 70, Some(node())); + append_account_trie(&wtx, k2, 40, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 50); + + let out = core.latest_version_for_key(k1).expect("ok"); + assert!(out.is_none(), "no dup ≤ max ⇒ None"); + } + + /// Single dup < max → pick it. + #[test] + fn latest_version_for_key_picks_single_below_max() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 25, Some(node())); // < max + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 50); + + let out = core.latest_version_for_key(k).expect("ok"); + assert_block(out, 25, false); + } + + /// Single dup == max → pick it. + #[test] + fn latest_version_for_key_picks_single_at_max() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 50, Some(node())); // == max + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 50); + + let out = core.latest_version_for_key(k).expect("ok"); + assert_block(out, 50, false); + } + + /// Latest ≤ max is a tombstone → return it (this API doesn't filter). + #[test] + fn latest_version_for_key_returns_tombstone_if_latest_is_deleted() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 10, Some(node())); + append_account_trie(&wtx, k.clone(), 90, None); // latest ≤ max, but deleted + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 100); + + let out = core.latest_version_for_key(k).expect("ok"); + assert_block(out, 90, true); + } + + /// Should skip tombstones and return None when the latest ≤ max is deleted. + #[test] + fn seek_exact_skips_tombstone_returns_none() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 10, Some(node())); + append_account_trie(&wtx, k.clone(), 90, None); // latest ≤ max is tombstoned + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut core = version_cursor(&tx, 100); + + let out = core.seek_exact(k).expect("ok"); + assert!(out.is_none(), "seek_exact must filter out deleted latest value"); + } + + /// Empty table → None. + #[test] + fn seek_empty_returns_none() { + let db = setup_db(); + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 100); + + let out = cur.seek(stored(Nibbles::from_nibbles([0x0A]))).expect("ok"); + assert!(out.is_none()); + } + + /// Start at an existing key whose latest ≤ max is live → returns that key. + #[test] + fn seek_at_live_key_returns_it() { + let db = setup_db(); + let k = stored(Nibbles::from_nibbles([0x0A])); + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k.clone(), 10, Some(node())); + append_account_trie(&wtx, k.clone(), 20, Some(node())); // latest ≤ max + wtx.commit().expect("commit"); + } + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 50); + + let out = cur.seek(k.clone()).expect("ok").expect("some"); + assert_eq!(out.0, k); + } + + /// Start at an existing key whose latest ≤ max is tombstoned → skip to next key with live + /// value. + #[test] + fn seek_skips_tombstoned_key_to_next_live_key() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + // Key 0x10 latest ≤ max is deleted + append_account_trie(&wtx, k1.clone(), 10, Some(node())); + append_account_trie(&wtx, k1.clone(), 20, None); // tombstone at latest ≤ max + // Next key has live + append_account_trie(&wtx, k2.clone(), 5, Some(node())); + wtx.commit().expect("commit"); + } + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 50); + + let out = cur.seek(k1).expect("ok").expect("some"); + assert_eq!(out.0, k2); + } + + /// Start between keys → returns the next key’s live latest ≤ max. + #[test] + fn seek_between_keys_returns_next_key() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0C])); + let k3 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1, 10, Some(node())); + append_account_trie(&wtx, k2.clone(), 10, Some(node())); + wtx.commit().expect("commit"); + } + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 100); + + // Start at 0x15 (between 0x10 and 0x20) + + let out = cur.seek(k3).expect("ok").expect("some"); + assert_eq!(out.0, k2); + } + + /// Start after the last key → None. + #[test] + fn seek_after_last_returns_none() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + let k3 = stored(Nibbles::from_nibbles([0x0C])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1, 10, Some(node())); + append_account_trie(&wtx, k2, 10, Some(node())); + wtx.commit().expect("commit"); + } + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 100); + + let out = cur.seek(k3).expect("ok"); + assert!(out.is_none()); + } + + /// If the first key at-or-after has only versions > max, it is effectively not visible → skip + /// to next. + #[test] + fn seek_skips_keys_with_only_versions_above_max() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1.clone(), 60, Some(node())); + append_account_trie(&wtx, k2.clone(), 40, Some(node())); + wtx.commit().expect("commit"); + } + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 50); + + let out = cur.seek(k1).expect("ok").expect("some"); + assert_eq!(out.0, k2); + } + + /// Start at a key with mixed versions; latest ≤ max is tombstone → skip to next key with live. + #[test] + fn seek_mixed_versions_tombstone_latest_skips_to_next_key() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1.clone(), 10, Some(node())); + append_account_trie(&wtx, k1.clone(), 30, None); + append_account_trie(&wtx, k2.clone(), 5, Some(node())); + wtx.commit().expect("commit"); + } + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 30); + + let out = cur.seek(k1).expect("ok").expect("some"); + assert_eq!(out.0, k2); + } + + /// When not positioned should start from default key and return the first live key. + #[test] + fn next_unpositioned_starts_from_default_returns_first_live() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1.clone(), 10, Some(node())); // first live + append_account_trie(&wtx, k2, 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + // Unpositioned cursor + let mut cur = version_cursor(&tx, 100); + + let out = cur.next().expect("ok").expect("some"); + assert_eq!(out.0, k1); + } + + /// After positioning on a live key via `seek()`, `next()` should advance to the next live key. + #[test] + fn next_advances_from_current_live_to_next_live() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1.clone(), 10, Some(node())); // live + append_account_trie(&wtx, k2.clone(), 10, Some(node())); // next live + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 100); + + // Position at k1 + let _ = cur.seek(k1).expect("ok").expect("some"); + // Next should yield k2 + let out = cur.next().expect("ok").expect("some"); + assert_eq!(out.0, k2); + } + + /// If the next key's latest ≤ max is tombstone, `next()` should skip to the next live key. + #[test] + fn next_skips_tombstoned_key_to_next_live() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); // will be tombstoned at latest ≤ max + let k3 = stored(Nibbles::from_nibbles([0x0C])); // next live + + { + let wtx = db.tx_mut().expect("rw tx"); + // k1 live + append_account_trie(&wtx, k1.clone(), 10, Some(node())); + // k2: latest ≤ max is tombstone + append_account_trie(&wtx, k2.clone(), 10, Some(node())); + append_account_trie(&wtx, k2, 20, None); + // k3 live + append_account_trie(&wtx, k3.clone(), 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 50); + + // Position at k1 + let _ = cur.seek(k1).expect("ok").expect("some"); + // next should skip k2 (tombstoned latest) and return k3 + let out = cur.next().expect("ok").expect("some"); + assert_eq!(out.0, k3); + } + + /// If positioned on the last live key, `next()` should return None (EOF). + #[test] + fn next_returns_none_at_eof() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); + let k2 = stored(Nibbles::from_nibbles([0x0B])); // last key + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, k1, 10, Some(node())); + append_account_trie(&wtx, k2.clone(), 10, Some(node())); // last live + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 100); + + // Position at the last key k2 + let _ = cur.seek(k2).expect("ok").expect("some"); + // `next()` should hit EOF + let out = cur.next().expect("ok"); + assert!(out.is_none()); + } + + /// If the first key has only versions > max, `next()` should skip it and return the next live + /// key. + #[test] + fn next_skips_keys_with_only_versions_above_max() { + let db = setup_db(); + let k1 = stored(Nibbles::from_nibbles([0x0A])); // only > max + let k2 = stored(Nibbles::from_nibbles([0x0B])); // ≤ max live + + { + let wtx = db.tx_mut().expect("rw tx"); + // k1 only above max (max=50) + append_account_trie(&wtx, k1, 60, Some(node())); + // k2 within max + append_account_trie(&wtx, k2.clone(), 40, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + // Unpositioned; `next()` will start from default and walk + let mut cur = version_cursor(&tx, 50); + + let out = cur.next().expect("ok").expect("some"); + assert_eq!(out.0, k2); + } + + /// Empty table: `next()` should return None. + #[test] + fn next_on_empty_returns_none() { + let db = setup_db(); + let tx = db.tx().expect("ro tx"); + let mut cur = version_cursor(&tx, 100); + + let out = cur.next().expect("ok"); + assert!(out.is_none()); + } + + // ----------------- Account trie cursor thin-wrapper checks ----------------- + + #[test] + fn account_seek_exact_live_maps_key_and_value() { + let db = setup_db(); + let k = Nibbles::from_nibbles([0x0A]); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, StoredNibbles(k), 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Build wrapper + let mut cur = account_trie_cursor(&tx, 100); + + // Wrapper should return (Nibbles, BranchNodeCompact) + let out = TrieCursor::seek_exact(&mut cur, k).expect("ok").expect("some"); + assert_eq!(out.0, k); + } + + #[test] + fn account_seek_exact_filters_tombstone() { + let db = setup_db(); + let k = Nibbles::from_nibbles([0x0B]); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, StoredNibbles(k), 5, Some(node())); + append_account_trie(&wtx, StoredNibbles(k), 9, None); // latest ≤ max tombstone + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = account_trie_cursor(&tx, 10); + + let out = TrieCursor::seek_exact(&mut cur, k).expect("ok"); + assert!(out.is_none(), "account seek_exact must filter tombstone"); + } + + #[test] + fn account_seek_and_next_and_current_roundtrip() { + let db = setup_db(); + let k1 = Nibbles::from_nibbles([0x01]); + let k2 = Nibbles::from_nibbles([0x02]); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_account_trie(&wtx, StoredNibbles(k1), 10, Some(node())); + append_account_trie(&wtx, StoredNibbles(k2), 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = account_trie_cursor(&tx, 100); + + // seek at k1 + let out1 = TrieCursor::seek(&mut cur, k1).expect("ok").expect("some"); + assert_eq!(out1.0, k1); + + // current should be k1 + let cur_k = TrieCursor::current(&mut cur).expect("ok").expect("some"); + assert_eq!(cur_k, k1); + + // next should move to k2 + let out2 = TrieCursor::next(&mut cur).expect("ok").expect("some"); + assert_eq!(out2.0, k2); + } + + // ----------------- Storage trie cursor thin-wrapper checks ----------------- + + #[test] + fn storage_seek_exact_respects_address_filter() { + let db = setup_db(); + + let addr_a = B256::from([0xAA; 32]); + let addr_b = B256::from([0xBB; 32]); + + let path = Nibbles::from_nibbles([0x0D]); + + { + let wtx = db.tx_mut().expect("rw tx"); + // insert only under B + append_storage_trie(&wtx, addr_b, path, 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Cursor bound to A must not see B’s data + let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); + let out_a = TrieCursor::seek_exact(&mut cur_a, path).expect("ok"); + assert!(out_a.is_none(), "no data for addr A"); + + // Cursor bound to B should see it + let mut cur_b = storage_trie_cursor(&tx, 100, addr_b); + let out_b = TrieCursor::seek_exact(&mut cur_b, path).expect("ok").expect("some"); + assert_eq!(out_b.0, path); + } + + #[test] + fn storage_seek_returns_first_key_for_bound_address() { + let db = setup_db(); + + let addr_a = B256::from([0x11; 32]); + let addr_b = B256::from([0x22; 32]); + + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let p3 = Nibbles::from_nibbles([0x03]); + + { + let wtx = db.tx_mut().expect("rw tx"); + // For A: only p2 + append_storage_trie(&wtx, addr_a, p2, 10, Some(node())); + // For B: p1 + append_storage_trie(&wtx, addr_b, p1, 10, Some(node())); + wtx.commit().expect("commit"); + } + + // test seek behaviour + { + let tx = db.tx().expect("ro tx"); + let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); + + // seek at p1: for A there is no p1; the next key >= p1 under A is p2 + let out = TrieCursor::seek(&mut cur_a, p1).expect("ok").expect("some"); + assert_eq!(out.0, p2); + + // seek at p2: exact match + let out = TrieCursor::seek(&mut cur_a, p2).expect("ok").expect("some"); + assert_eq!(out.0, p2); + + // seek at p3: no p3 under A; no next key ≥ p3 under A → None + let out = TrieCursor::seek(&mut cur_a, p3).expect("ok"); + assert!(out.is_none(), "no key ≥ p3 under A"); + } + + // test next behaviour + { + let tx = db.tx().expect("ro tx"); + let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); + + let out = TrieCursor::next(&mut cur_a).expect("ok").expect("some"); + assert_eq!(out.0, p2); + + // next should yield None as there is no further key under A + let out = TrieCursor::next(&mut cur_a).expect("ok"); + assert!(out.is_none(), "no more keys under A"); + + // current should return None + let out = TrieCursor::current(&mut cur_a).expect("ok"); + assert!(out.is_none(), "no current key after EOF"); + } + + // test seek_exact behaviour + { + let tx = db.tx().expect("ro tx"); + let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); + + // seek_exact at p1: no exact match + let out = TrieCursor::seek_exact(&mut cur_a, p1).expect("ok"); + assert!(out.is_none(), "no exact p1 under A"); + + // seek_exact at p2: exact match + let out = TrieCursor::seek_exact(&mut cur_a, p2).expect("ok").expect("some"); + assert_eq!(out.0, p2); + + // seek_exact at p3: no exact match + let out = TrieCursor::seek_exact(&mut cur_a, p3).expect("ok"); + assert!(out.is_none(), "no exact p3 under A"); + } + } + + #[test] + fn storage_next_stops_at_address_boundary() { + let db = setup_db(); + + let addr_a = B256::from([0x33; 32]); + let addr_b = B256::from([0x44; 32]); + + let p1 = Nibbles::from_nibbles([0x05]); // under A + let p2 = Nibbles::from_nibbles([0x06]); // under B (next key overall) + + { + let wtx = db.tx_mut().expect("rw tx"); + append_storage_trie(&wtx, addr_a, p1, 10, Some(node())); + append_storage_trie(&wtx, addr_b, p2, 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); + + // position at p1 (A) + let _ = TrieCursor::seek_exact(&mut cur_a, p1).expect("ok").expect("some"); + + // next should reach boundary; impl filters different address and returns None + let out = TrieCursor::next(&mut cur_a).expect("ok"); + assert!(out.is_none(), "next() should stop when next key is a different address"); + } + + #[test] + fn storage_current_maps_key() { + let db = setup_db(); + + let addr = B256::from([0x55; 32]); + let p = Nibbles::from_nibbles([0x09]); + + { + let wtx = db.tx_mut().expect("rw tx"); + append_storage_trie(&wtx, addr, p, 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = storage_trie_cursor(&tx, 100, addr); + + let _ = TrieCursor::seek_exact(&mut cur, p).expect("ok").expect("some"); + + let now = TrieCursor::current(&mut cur).expect("ok").expect("some"); + assert_eq!(now, p); + } + + #[test] + fn hashed_storage_seek_maps_slot_and_value() { + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + let slot = B256::from([0x10; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_storage(&wtx, addr, slot, 10, Some(U256::from(7))); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = storage_cursor(&tx, 100, addr); + + let (got_slot, got_val) = cur.seek(slot).expect("ok").expect("some"); + assert_eq!(got_slot, slot); + assert_eq!(got_val, U256::from(7)); + } + + #[test] + fn hashed_storage_seek_filters_tombstone() { + let db = setup_db(); + let addr = B256::from([0xAB; 32]); + let slot = B256::from([0x11; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_storage(&wtx, addr, slot, 5, Some(U256::from(1))); + append_hashed_storage(&wtx, addr, slot, 9, None); // latest ≤ max is tombstone + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = storage_cursor(&tx, 10, addr); + + let out = cur.seek(slot).expect("ok"); + assert!(out.is_none(), "wrapper must filter tombstoned latest"); + } + + #[test] + fn hashed_storage_seek_and_next_roundtrip() { + let db = setup_db(); + let addr = B256::from([0xAC; 32]); + let s1 = B256::from([0x01; 32]); + let s2 = B256::from([0x02; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_storage(&wtx, addr, s1, 10, Some(U256::from(11))); + append_hashed_storage(&wtx, addr, s2, 10, Some(U256::from(22))); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = storage_cursor(&tx, 100, addr); + + let (k1, v1) = cur.seek(s1).expect("ok").expect("some"); + assert_eq!((k1, v1), (s1, U256::from(11))); + + let (k2, v2) = cur.next().expect("ok").expect("some"); + assert_eq!((k2, v2), (s2, U256::from(22))); + } + + #[test] + fn hashed_storage_address_boundary() { + let db = setup_db(); + let addr1 = B256::from([0xAC; 32]); + let addr2 = B256::from([0xAD; 32]); + let s1 = B256::from([0x01; 32]); + let s2 = B256::from([0x02; 32]); + let s3 = B256::from([0x03; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_storage(&wtx, addr1, s1, 10, Some(U256::from(11))); + append_hashed_storage(&wtx, addr1, s2, 10, Some(U256::from(22))); + wtx.commit().expect("commit"); + } + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_storage(&wtx, addr2, s1, 10, Some(U256::from(33))); + append_hashed_storage(&wtx, addr2, s2, 10, Some(U256::from(44))); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = storage_cursor(&tx, 100, addr1); + + let (k1, v1) = cur.next().expect("ok").expect("some"); + assert_eq!((k1, v1), (s1, U256::from(11))); + + let (k2, v2) = cur.next().expect("ok").expect("some"); + assert_eq!((k2, v2), (s2, U256::from(22))); + + let out = cur.next().expect("ok"); + assert!(out.is_none(), "should stop at address boundary"); + + let (k1, v1) = cur.seek(s1).expect("ok").expect("some"); + assert_eq!((k1, v1), (s1, U256::from(11))); + + let (k2, v2) = cur.seek(s2).expect("ok").expect("some"); + assert_eq!((k2, v2), (s2, U256::from(22))); + + let out = cur.seek(s3).expect("ok"); + assert!(out.is_none(), "should not see keys from other address"); + } + + #[test] + fn hashed_account_seek_maps_key_and_value() { + let db = setup_db(); + let key = B256::from([0x20; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_account(&wtx, key, 10, Some(Account::default())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = account_cursor(&tx, 100); + + let (got_key, _acc) = cur.seek(key).expect("ok").expect("some"); + assert_eq!(got_key, key); + } + + #[test] + fn hashed_account_seek_filters_tombstone() { + let db = setup_db(); + let key = B256::from([0x21; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_account(&wtx, key, 5, Some(Account::default())); + append_hashed_account(&wtx, key, 9, None); // latest ≤ max is tombstone + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = account_cursor(&tx, 10); + + let out = cur.seek(key).expect("ok"); + assert!(out.is_none(), "wrapper must filter tombstoned latest"); + } + + #[test] + fn hashed_account_seek_and_next_roundtrip() { + let db = setup_db(); + let k1 = B256::from([0x01; 32]); + let k2 = B256::from([0x02; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + append_hashed_account(&wtx, k1, 10, Some(Account::default())); + append_hashed_account(&wtx, k2, 10, Some(Account::default())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + let mut cur = account_cursor(&tx, 100); + + let (got1, _) = cur.seek(k1).expect("ok").expect("some"); + assert_eq!(got1, k1); + + let (got2, _) = cur.next().expect("ok").expect("some"); + assert_eq!(got2, k2); + } + + /// Regression test: `MdbxStorageCursor` `next()` should work without explicit `seek()` + /// when cursor is constructed for a non-first key. + /// + /// Bug: When a storage cursor is created for a specific address (e.g., 0x02), + /// calling `next()` without first calling `seek()` returns None instead of the first + /// slot for that address. This only manifests when the address is not the first + /// in the table. + #[test] + fn storage_cursor_next_without_seek_for_non_first_address() { + let db = setup_db(); + let addr1 = B256::from([0x01; 32]); // First address + let addr2 = B256::from([0x02; 32]); // Second address (non-first) + let slot1 = B256::from([0x11; 32]); + let slot2 = B256::from([0x12; 32]); + + { + let wtx = db.tx_mut().expect("rw"); + // Add storage for first address + append_hashed_storage(&wtx, addr1, slot1, 10, Some(U256::from(100))); + + // Add storage for second address + append_hashed_storage(&wtx, addr2, slot2, 10, Some(U256::from(200))); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + + // Test with addr1 (first address) - this typically works + let mut cur1 = storage_cursor(&tx, 100, addr1); + let result1 = cur1.next().expect("ok"); + assert!(result1.is_some(), "next() should return data for first address without seek()"); + if let Some((key, val)) = result1 { + assert_eq!(key, slot1); + assert_eq!(val, U256::from(100)); + } + + // Test with addr2 (non-first address) - this demonstrates the bug fix + let mut cur2 = storage_cursor(&tx, 100, addr2); + let result2_without_seek = cur2.next().expect("ok"); + + assert!( + result2_without_seek.is_some(), + "next() should return data for non-first address without seek()" + ); + if let Some((key, val)) = result2_without_seek { + assert_eq!(key, slot2); + assert_eq!(val, U256::from(200)); + } + + // Verify that seek() works correctly + let mut cur3 = storage_cursor(&tx, 100, addr2); + let result3_with_seek = cur3.seek(slot2).expect("ok"); + assert!(result3_with_seek.is_some(), "seek() should find the slot for addr2"); + if let Some((key, val)) = result3_with_seek { + assert_eq!(key, slot2); + assert_eq!(val, U256::from(200)); + } + } + + /// Regression test: `MdbxTrieCursor` `next()` should work without `seek()` + /// for non-first addresses. + #[test] + fn storage_trie_cursor_next_without_seek_for_non_first_address() { + let db = setup_db(); + let addr1 = B256::from([0x01; 32]); + let addr2 = B256::from([0x02; 32]); + let path1 = Nibbles::from_nibbles([0x0A]); + let path2 = Nibbles::from_nibbles([0x0B]); + + { + let wtx = db.tx_mut().expect("rw"); + append_storage_trie(&wtx, addr1, path1, 10, Some(node())); + append_storage_trie(&wtx, addr2, path2, 10, Some(node())); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro"); + + // Test addr1 (first) - works + let mut cur1 = storage_trie_cursor(&tx, 100, addr1); + let result1 = TrieCursor::next(&mut cur1).expect("ok"); + assert!(result1.is_some()); + assert_eq!(result1.unwrap().0, path1); + + // Test addr2 (non-first) - should also work now + let mut cur2 = storage_trie_cursor(&tx, 100, addr2); + let result2 = TrieCursor::next(&mut cur2).expect("ok"); + assert!(result2.is_some(), "next() should work for non-first address without seek()"); + assert_eq!(result2.unwrap().0, path2); + } +} diff --git a/vendor/reth-optimism-trie/src/db/mod.rs b/vendor/reth-optimism-trie/src/db/mod.rs new file mode 100644 index 0000000..5f611e9 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/mod.rs @@ -0,0 +1,24 @@ +//! MDBX implementation of [`OpProofsStore`](crate::OpProofsStore). +//! +//! This module provides a complete MDBX implementation of the +//! [`OpProofsStore`](crate::OpProofsStore) trait. It uses the [`reth_db`] +//! crate for database interactions and defines the necessary tables and models for storing trie +//! branches, accounts, and storage leaves. + +mod models; +pub use models::*; + +mod store; +pub use store::{MdbxProofsProvider, MdbxProofsStorage}; + +mod cursor; +pub use cursor::{ + BlockNumberVersionedCursor, MdbxAccountCursor, MdbxStorageCursor, MdbxTrieCursor, +}; + +mod store_v2; +pub use store_v2::{ + MdbxProofsProviderV2, MdbxProofsStorageV2, V2AccountCursor, V2AccountTrieCursor, + V2AccountTrieSnapshotCursor, V2HashedAccountSnapshotCursor, V2HashedStorageSnapshotCursor, + V2StorageCursor, V2StorageTrieCursor, V2StorageTrieSnapshotCursor, +}; diff --git a/vendor/reth-optimism-trie/src/db/models/block.rs b/vendor/reth-optimism-trie/src/db/models/block.rs new file mode 100644 index 0000000..d2aeff2 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/block.rs @@ -0,0 +1,79 @@ +use alloy_eips::BlockNumHash; +use alloy_primitives::B256; +use bytes::BufMut; +use derive_more::{From, Into}; +use reth_codecs::DecompressError; +use reth_db::{ + DatabaseError, + table::{Compress, Decompress}, +}; +use serde::{Deserialize, Serialize}; + +/// Wrapper for block number and block hash tuple to implement [`Compress`]/[`Decompress`]. +/// +/// Used for storing block metadata (number + hash). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, From, Into)] +pub struct BlockNumberHash(BlockNumHash); + +impl Compress for BlockNumberHash { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + // Encode block number (8 bytes, big-endian) + hash (32 bytes) = 40 bytes total + buf.put_u64(self.0.number); + buf.put_slice(self.0.hash.as_slice()); + } +} + +impl Decompress for BlockNumberHash { + fn decompress(value: &[u8]) -> Result { + if value.len() != 40 { + return Err(DecompressError::new(DatabaseError::Decode)); + } + + let number = u64::from_be_bytes( + value[..8].try_into().map_err(|_| DecompressError::new(DatabaseError::Decode))?, + ); + let hash = B256::from_slice(&value[8..40]); + + Ok(Self(BlockNumHash { number, hash })) + } +} + +impl BlockNumberHash { + /// Create new instance. + pub const fn new(number: u64, hash: B256) -> Self { + Self(BlockNumHash { number, hash }) + } + + /// Get the block number. + pub const fn number(&self) -> u64 { + self.0.number + } + + /// Get the block hash. + pub const fn hash(&self) -> &B256 { + &self.0.hash + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + + #[test] + fn test_block_number_hash_roundtrip() { + let test_cases = vec![ + BlockNumberHash::new(0, B256::ZERO), + BlockNumberHash::new(42, B256::repeat_byte(0xaa)), + BlockNumberHash::new(u64::MAX, B256::repeat_byte(0xff)), + ]; + + for original in test_cases { + let compressed = original.compress(); + let decompressed = BlockNumberHash::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/change_set.rs b/vendor/reth-optimism-trie/src/db/models/change_set.rs new file mode 100644 index 0000000..248d060 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/change_set.rs @@ -0,0 +1,128 @@ +use crate::db::{HashedStorageKey, StorageTrieKey}; +use alloy_primitives::B256; +use reth_db::{ + DatabaseError, + table::{self, Decode, Encode}, +}; +use reth_trie_common::StoredNibbles; +use serde::{Deserialize, Serialize}; + +/// The keys of the entries in the history tables. +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct ChangeSet { + /// Keys changed in [`AccountTrieHistory`](super::AccountTrieHistory) table. + pub account_trie_keys: Vec, + /// Keys changed in [`StorageTrieHistory`](super::StorageTrieHistory) table. + pub storage_trie_keys: Vec, + /// Keys changed in [`HashedAccountHistory`](super::HashedAccountHistory) table. + pub hashed_account_keys: Vec, + /// Keys changed in [`HashedStorageHistory`](super::HashedStorageHistory) table. + pub hashed_storage_keys: Vec, +} + +impl table::Encode for ChangeSet { + type Encoded = Vec; + + fn encode(self) -> Self::Encoded { + bincode::serde::encode_to_vec(&self, bincode::config::standard()) + .expect("ChangeSet serialization should not fail") + } +} + +impl table::Decode for ChangeSet { + fn decode(value: &[u8]) -> Result { + bincode::serde::decode_from_slice(value, bincode::config::standard()) + .map(|(v, _)| v) + .map_err(|_| DatabaseError::Decode) + } +} + +impl table::Compress for ChangeSet { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + let encoded = self.clone().encode(); + buf.put_slice(&encoded); + } +} + +impl table::Decompress for ChangeSet { + fn decompress(value: &[u8]) -> Result { + Self::decode(value).map_err(reth_codecs::DecompressError::new) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + use reth_db::table::{Compress, Decompress}; + + #[test] + fn test_encode_decode_empty_change_set() { + let change_set = ChangeSet { + account_trie_keys: vec![], + storage_trie_keys: vec![], + hashed_account_keys: vec![], + hashed_storage_keys: vec![], + }; + + let encoded = change_set.clone().encode(); + let decoded = ChangeSet::decode(&encoded).expect("Failed to decode"); + assert_eq!(change_set, decoded); + } + + #[test] + fn test_encode_decode_populated_change_set() { + let account_key = StoredNibbles::from(vec![1, 2, 3, 4]); + let storage_key = StorageTrieKey { + hashed_address: B256::repeat_byte(0x11), + path: StoredNibbles::from(vec![5, 6, 7, 8]), + }; + let hashed_storage_key = HashedStorageKey { + hashed_address: B256::repeat_byte(0x22), + hashed_storage_key: B256::repeat_byte(0x33), + }; + + let change_set = ChangeSet { + account_trie_keys: vec![account_key], + storage_trie_keys: vec![storage_key], + hashed_account_keys: vec![B256::repeat_byte(0x44)], + hashed_storage_keys: vec![hashed_storage_key], + }; + + let encoded = change_set.clone().encode(); + let decoded = ChangeSet::decode(&encoded).expect("Failed to decode"); + assert_eq!(change_set, decoded); + } + + #[test] + fn test_decode_invalid_data() { + let invalid_data = vec![0xFF; 32]; + let result = ChangeSet::decode(&invalid_data); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), DatabaseError::Decode)); + } + + #[test] + fn test_compress_decompress() { + let change_set = ChangeSet { + account_trie_keys: vec![StoredNibbles::from(vec![1, 2, 3])], + storage_trie_keys: vec![StorageTrieKey { + hashed_address: B256::ZERO, + path: StoredNibbles::from(vec![4, 5, 6]), + }], + hashed_account_keys: vec![B256::ZERO], + hashed_storage_keys: vec![HashedStorageKey { + hashed_address: B256::ZERO, + hashed_storage_key: B256::repeat_byte(0x42), + }], + }; + + let mut buf = Vec::new(); + change_set.compress_to_buf(&mut buf); + + let decompressed = ChangeSet::decompress(&buf).expect("Failed to decompress"); + assert_eq!(change_set, decompressed); + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/key.rs b/vendor/reth-optimism-trie/src/db/models/key.rs new file mode 100644 index 0000000..1a414f3 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/key.rs @@ -0,0 +1,476 @@ +use alloy_primitives::{B256, BlockNumber}; +use reth_db::{ + DatabaseError, + models::sharded_key::ShardedKey, + table::{Decode, Encode}, +}; +use reth_trie_common::{Nibbles, StoredNibbles}; +use serde::{Deserialize, Serialize}; + +/// Nibble-subkey layout shared by [`AccountTrieShardedKey`] and [`StorageTrieShardedKey`]: 64 +/// nibble bytes right-padded with `0x00`, followed by a 1-byte length suffix. Padding the path +/// to a fixed width and placing nibble bytes ahead of the length byte makes MDBX's byte-wise +/// sort agree with `Nibbles`' lex-by-nibble order. +const NIBBLE_SUBKEY_LEN: usize = 65; +/// Byte length of an encoded [`AccountTrieShardedKey`]: nibble subkey + 8 block number bytes. +const ACCOUNT_TRIE_SHARDED_KEY_LEN: usize = NIBBLE_SUBKEY_LEN + 8; +/// Byte length of an encoded [`StorageTrieShardedKey`]: hashed address + nibble subkey + block. +const STORAGE_TRIE_SHARDED_KEY_LEN: usize = 32 + NIBBLE_SUBKEY_LEN + 8; + +/// Encode a nibble path into the fixed-size `[u8; 65]` subkey layout: 64 path bytes right-padded +/// with `0x00`, followed by the actual nibble count in byte 64. +fn encode_nibble_subkey(nibbles: &StoredNibbles) -> [u8; NIBBLE_SUBKEY_LEN] { + debug_assert!(nibbles.0.len() <= 64, "nibble path exceeds 64"); + let mut buf = [0u8; NIBBLE_SUBKEY_LEN]; + for (i, nibble) in nibbles.0.iter().enumerate() { + buf[i] = nibble; + } + buf[64] = nibbles.0.len() as u8; + buf +} + +/// Inverse of [`encode_nibble_subkey`]: read the length byte at position 64 and reconstruct the +/// [`StoredNibbles`] from the first `len` path bytes. +fn decode_nibble_subkey(buf: &[u8; NIBBLE_SUBKEY_LEN]) -> StoredNibbles { + let len = buf[64] as usize; + StoredNibbles::from(Nibbles::from_nibbles_unchecked(&buf[..len])) +} + +/// Sharded key for hashed accounts history, keyed by `(hashed_address, block)`. +/// +/// Encoded as a fixed-size 40-byte buffer: +/// +/// ```text +/// [hashed_address: 32 bytes] ++ [block_number: 8 BE bytes] +/// ``` +/// +/// MDBX's byte-wise sort groups entries by address (full-width hash, so no padding needed), +/// then orders ascending by block number. +#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct HashedAccountShardedKey(pub ShardedKey); + +impl HashedAccountShardedKey { + /// Create a new sharded key for a hashed account. + pub const fn new(key: B256, highest_block_number: u64) -> Self { + Self(ShardedKey::new(key, highest_block_number)) + } +} + +impl Encode for HashedAccountShardedKey { + type Encoded = [u8; 40]; // 32 (B256) + 8 (BlockNumber) + + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; 40]; + buf[..32].copy_from_slice(self.0.key.as_slice()); + buf[32..].copy_from_slice(&self.0.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for HashedAccountShardedKey { + fn decode(value: &[u8]) -> Result { + if value.len() != 40 { + return Err(DatabaseError::Decode); + } + let key = B256::from_slice(&value[..32]); + let highest_block_number = + u64::from_be_bytes(value[32..].try_into().map_err(|_| DatabaseError::Decode)?); + Ok(Self(ShardedKey::new(key, highest_block_number))) + } +} + +/// Sharded key for hashed storage history, keyed by `(hashed_address, storage_key, block)`. +/// +/// Encoded as a 72-byte buffer: +/// +/// ```text +/// [hashed_address: 32 bytes] ++ [storage_key: 32 bytes] ++ [block_number: 8 BE bytes] +/// ``` +/// +/// MDBX cursor walks group entries by address, then by storage key, then ascending by block. +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct HashedStorageShardedKey { + /// The hashed address of the account owning the storage. + pub hashed_address: B256, + /// The sharded key combining the storage key and sharded block number. + pub sharded_key: ShardedKey, +} + +impl Encode for HashedStorageShardedKey { + type Encoded = Vec; + fn encode(self) -> Self::Encoded { + let mut buf = Vec::with_capacity(32 + 32 + 8); + buf.extend_from_slice(self.hashed_address.as_slice()); + // ShardedKey: Key (32 bytes) + BlockNumber (8 bytes BE) + buf.extend_from_slice(self.sharded_key.key.as_slice()); + buf.extend_from_slice(&self.sharded_key.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for HashedStorageShardedKey { + fn decode(value: &[u8]) -> Result { + // 32 (Addr) + 32 (Key) + 8 (Block) = 72 bytes + if value.len() < 72 { + return Err(DatabaseError::Decode); + } + let (addr, rest) = value.split_at(32); + let hashed_address = B256::from_slice(addr); + let key = B256::from_slice(&rest[..32]); + let highest_block_number = + u64::from_be_bytes(rest[32..40].try_into().map_err(|_| DatabaseError::Decode)?); + Ok(Self { hashed_address, sharded_key: ShardedKey::new(key, highest_block_number) }) + } +} + +/// Sharded key for account trie history. +/// +/// Encoded as a fixed-size 73-byte buffer. The nibble portion is right-padded with `0x00` and +/// followed by a length byte so MDBX's byte-wise sort agrees with `Nibbles`' lex-by-nibble +/// order: +/// +/// ```text +/// [nibbles: 64 bytes, right-padded 0x00] ++ [length: 1 byte] ++ [block_number: 8 BE bytes] +/// ``` +/// +/// See [`StorageTrieShardedKey`] for the same layout extended with a per-account address. +#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct AccountTrieShardedKey { + /// Trie path as nibbles. + pub key: StoredNibbles, + /// Highest block number in this shard (or `u64::MAX` for the sentinel). + pub highest_block_number: u64, +} + +impl AccountTrieShardedKey { + /// Create a new sharded key for an account trie path. + pub const fn new(key: StoredNibbles, highest_block_number: u64) -> Self { + Self { key, highest_block_number } + } +} + +impl Encode for AccountTrieShardedKey { + type Encoded = [u8; ACCOUNT_TRIE_SHARDED_KEY_LEN]; + + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; ACCOUNT_TRIE_SHARDED_KEY_LEN]; + buf[..NIBBLE_SUBKEY_LEN].copy_from_slice(&encode_nibble_subkey(&self.key)); + buf[NIBBLE_SUBKEY_LEN..].copy_from_slice(&self.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for AccountTrieShardedKey { + fn decode(value: &[u8]) -> Result { + let bytes: &[u8; ACCOUNT_TRIE_SHARDED_KEY_LEN] = + value.try_into().map_err(|_| DatabaseError::Decode)?; + let nibble_buf: &[u8; NIBBLE_SUBKEY_LEN] = + bytes[..NIBBLE_SUBKEY_LEN].try_into().map_err(|_| DatabaseError::Decode)?; + let key = decode_nibble_subkey(nibble_buf); + let highest_block_number = u64::from_be_bytes( + bytes[NIBBLE_SUBKEY_LEN..].try_into().map_err(|_| DatabaseError::Decode)?, + ); + Ok(Self { key, highest_block_number }) + } +} + +/// Sharded key for storage trie history, keyed by `(hashed_address, nibble_path, block)`. +/// +/// Encoded as a fixed-size 105-byte buffer; the 32-byte hashed address sits at position 0 so +/// MDBX cursor walks naturally group all entries for the same account, then sort by trie path +/// (lex-by-nibble) and block number — see [`AccountTrieShardedKey`] for the nibble-subkey +/// rationale. +/// +/// ```text +/// [hashed_address: 32 bytes] ++ [nibbles: 64 bytes, right-padded 0x00] +/// ++ [length: 1 byte] +/// ++ [block_number: 8 BE bytes] +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct StorageTrieShardedKey { + /// The hashed address of the account owning the storage trie. + pub hashed_address: B256, + /// The trie path (nibbles). + pub key: StoredNibbles, + /// Highest block number in this shard (or `u64::MAX` for the sentinel). + pub highest_block_number: u64, +} + +impl StorageTrieShardedKey { + /// Create a new storage trie sharded key. + pub const fn new(hashed_address: B256, key: StoredNibbles, highest_block_number: u64) -> Self { + Self { hashed_address, key, highest_block_number } + } +} + +impl Encode for StorageTrieShardedKey { + type Encoded = [u8; STORAGE_TRIE_SHARDED_KEY_LEN]; + + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; STORAGE_TRIE_SHARDED_KEY_LEN]; + buf[..32].copy_from_slice(self.hashed_address.as_slice()); + buf[32..32 + NIBBLE_SUBKEY_LEN].copy_from_slice(&encode_nibble_subkey(&self.key)); + buf[32 + NIBBLE_SUBKEY_LEN..].copy_from_slice(&self.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for StorageTrieShardedKey { + fn decode(value: &[u8]) -> Result { + let bytes: &[u8; STORAGE_TRIE_SHARDED_KEY_LEN] = + value.try_into().map_err(|_| DatabaseError::Decode)?; + let hashed_address = B256::from_slice(&bytes[..32]); + let nibble_buf: &[u8; NIBBLE_SUBKEY_LEN] = + bytes[32..32 + NIBBLE_SUBKEY_LEN].try_into().map_err(|_| DatabaseError::Decode)?; + let key = decode_nibble_subkey(nibble_buf); + let highest_block_number = u64::from_be_bytes( + bytes[32 + NIBBLE_SUBKEY_LEN..].try_into().map_err(|_| DatabaseError::Decode)?, + ); + Ok(Self { hashed_address, key, highest_block_number }) + } +} + +/// Key for the storage `ChangeSets` table, keyed by `(block, hashed_address)`. +/// +/// Encoded as a fixed-size 40-byte buffer: +/// +/// ```text +/// [block_number: 8 BE bytes] ++ [hashed_address: 32 bytes] +/// ``` +/// +/// Block goes first so MDBX cursor walks iterate change sets in block order, with +/// address-grouping within each block. Replaces upstream `BlockNumberAddress`, which keyed by +/// the unhashed account address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct BlockNumberHashedAddress(pub (BlockNumber, B256)); + +impl Encode for BlockNumberHashedAddress { + type Encoded = [u8; 40]; // 8 + 32 + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; 40]; + buf[..8].copy_from_slice(&self.0.0.to_be_bytes()); + buf[8..].copy_from_slice(self.0.1.as_slice()); + buf + } +} + +impl Decode for BlockNumberHashedAddress { + fn decode(value: &[u8]) -> Result { + if value.len() < 40 { + return Err(DatabaseError::Decode); + } + let block_num = u64::from_be_bytes(value[..8].try_into().unwrap()); + let hash = B256::from_slice(&value[8..40]); + Ok(Self((block_num, hash))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_db::table::{Decode, Encode}; + use reth_trie_common::Nibbles; + + #[test] + fn hashed_account_sharded_key_roundtrip() { + let original = HashedAccountShardedKey::new(B256::repeat_byte(0xaa), 42); + let decoded = HashedAccountShardedKey::decode(&original.clone().encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn hashed_storage_sharded_key_roundtrip() { + let original = HashedStorageShardedKey { + hashed_address: B256::repeat_byte(0xaa), + sharded_key: ShardedKey::new(B256::repeat_byte(0xbb), 100), + }; + let decoded = HashedStorageShardedKey::decode(&original.clone().encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn account_trie_sharded_key_roundtrip() { + let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0a, 0x0b, 0x0c])); + let original = AccountTrieShardedKey::new(nibbles, 500); + let decoded = AccountTrieShardedKey::decode(&original.clone().encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn account_trie_sharded_key_roundtrip_empty_nibbles() { + let original = + AccountTrieShardedKey::new(StoredNibbles::from(Nibbles::default()), u64::MAX); + let decoded = AccountTrieShardedKey::decode(&original.clone().encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn storage_trie_sharded_key_roundtrip() { + let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x02])); + let original = StorageTrieShardedKey::new(B256::repeat_byte(0xcc), nibbles, 999); + let decoded = StorageTrieShardedKey::decode(&original.clone().encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn storage_trie_sharded_key_roundtrip_empty_nibbles() { + let original = StorageTrieShardedKey::new( + B256::repeat_byte(0xdd), + StoredNibbles::from(Nibbles::default()), + 0, + ); + let decoded = StorageTrieShardedKey::decode(&original.clone().encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn block_number_hashed_address_roundtrip() { + let original = BlockNumberHashedAddress((42, B256::repeat_byte(0xdd))); + let decoded = BlockNumberHashedAddress::decode(&original.encode()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn account_trie_shorter_nibbles_sort_before_longer() { + let key_a = AccountTrieShardedKey::new( + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01])), + 256, + ); + let key_b = AccountTrieShardedKey::new( + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x00])), + 1, + ); + + assert!( + key_a.encode() < key_b.encode(), + "shorter nibble path must sort before longer in encoded form" + ); + } + + #[test] + fn account_trie_same_nibbles_ordered_by_block() { + let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0a, 0x0b])); + + let lo = AccountTrieShardedKey::new(nibbles.clone(), 10); + let hi = AccountTrieShardedKey::new(nibbles, 20); + + assert!( + lo.encode() < hi.encode(), + "same nibbles: lower block must sort before higher block" + ); + } + + #[test] + fn account_trie_nibbles_resembling_block_bytes_are_unambiguous() { + let key_a = AccountTrieShardedKey::new( + StoredNibbles::from(Nibbles::from_nibbles_unchecked([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + ])), + 1, + ); + let key_b = AccountTrieShardedKey::new(StoredNibbles::from(Nibbles::default()), 5); + + let enc_a = key_a.encode(); + let enc_b = key_b.encode(); + assert_ne!(enc_a, enc_b, "different logical keys must never produce identical encodings"); + // Empty nibbles (all-zero padded path) must sort before non-empty paths whose first + // non-zero nibble is greater than zero. + assert!(enc_b < enc_a, "empty nibbles must sort before non-empty nibbles"); + } + + /// Regression: under the old length-prefixed encoding, the length byte at position 0 + /// dominated MDBX's byte-wise sort, so `[0x05]` (len=1) would have sorted before + /// `[0x01, 0x05]` (len=2) — opposite of the logical nibble lex order. The fixed-size + /// layout places nibble bytes first, so the actual path content drives ordering. + #[test] + fn account_trie_sort_follows_nibble_lex_order_not_length() { + let key_short = AccountTrieShardedKey::new( + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x05])), + 0, + ); + let key_long = AccountTrieShardedKey::new( + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x05])), + 0, + ); + + assert!( + key_long.encode() < key_short.encode(), + "[0x01, 0x05] must sort before [0x05] because nibble 0x01 < 0x05", + ); + } + + /// Storage-trie variant of the same regression. + #[test] + fn storage_trie_sort_follows_nibble_lex_order_not_length() { + let addr = B256::repeat_byte(0x44); + let key_short = StorageTrieShardedKey::new( + addr, + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x05])), + 0, + ); + let key_long = StorageTrieShardedKey::new( + addr, + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x05])), + 0, + ); + + assert!( + key_long.encode() < key_short.encode(), + "[0x01, 0x05] must sort before [0x05] within the same address", + ); + } + + #[test] + fn storage_trie_shorter_nibbles_sort_before_longer() { + let addr = B256::repeat_byte(0x11); + + let key_a = StorageTrieShardedKey::new( + addr, + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0f])), + 256, + ); + let key_b = StorageTrieShardedKey::new( + addr, + StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0f, 0x00])), + 1, + ); + + assert!( + key_a.encode() < key_b.encode(), + "shorter nibble path must sort before longer in encoded form" + ); + } + + #[test] + fn storage_trie_same_nibbles_ordered_by_block() { + let addr = B256::repeat_byte(0x22); + let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0a])); + + let lo = StorageTrieShardedKey::new(addr, nibbles.clone(), 10); + let hi = StorageTrieShardedKey::new(addr, nibbles, 20); + + assert!( + lo.encode() < hi.encode(), + "same nibbles: lower block must sort before higher block" + ); + } + + #[test] + fn storage_trie_nibbles_resembling_block_bytes_are_unambiguous() { + let addr = B256::repeat_byte(0x33); + + let key_a = StorageTrieShardedKey::new( + addr, + StoredNibbles::from(Nibbles::from_nibbles_unchecked([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + ])), + 1, + ); + let key_b = StorageTrieShardedKey::new(addr, StoredNibbles::from(Nibbles::default()), 5); + + let enc_a = key_a.encode(); + let enc_b = key_b.encode(); + assert_ne!(enc_a, enc_b, "different logical keys must never produce identical encodings"); + assert!(enc_b < enc_a, "empty nibbles must sort before non-empty nibbles"); + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/kv.rs b/vendor/reth-optimism-trie/src/db/models/kv.rs new file mode 100644 index 0000000..e42bea5 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/kv.rs @@ -0,0 +1,68 @@ +//! KV conversion helpers for trie history tables. + +use crate::db::{ + AccountTrieHistory, HashedAccountHistory, HashedStorageHistory, HashedStorageKey, MaybeDeleted, + StorageTrieHistory, StorageTrieKey, StorageValue, VersionedValue, +}; +use alloy_primitives::B256; +use reth_db::table::{DupSort, Table}; +use reth_primitives_traits::Account; +use reth_trie_common::{BranchNodeCompact, Nibbles, StoredNibbles}; + +/// Helper to convert inputs into a table key or kv pair. +pub trait IntoKV { + /// Convert `self` into the table key. + fn into_key(self) -> Tab::Key; + + /// Convert `self` into kv for the given `block_number`. + fn into_kv(self, block_number: u64) -> (Tab::Key, Tab::Value); +} + +impl IntoKV for (Nibbles, Option) { + fn into_key(self) -> StoredNibbles { + StoredNibbles::from(self.0) + } + + fn into_kv(self, block_number: u64) -> (StoredNibbles, VersionedValue) { + let (path, node) = self; + (StoredNibbles::from(path), VersionedValue { block_number, value: MaybeDeleted(node) }) + } +} + +impl IntoKV for (B256, Nibbles, Option) { + fn into_key(self) -> StorageTrieKey { + let (hashed_address, path, _) = self; + StorageTrieKey::new(hashed_address, StoredNibbles::from(path)) + } + fn into_kv(self, block_number: u64) -> (StorageTrieKey, VersionedValue) { + let (hashed_address, path, node) = self; + ( + StorageTrieKey::new(hashed_address, StoredNibbles::from(path)), + VersionedValue { block_number, value: MaybeDeleted(node) }, + ) + } +} + +impl IntoKV for (B256, Option) { + fn into_key(self) -> B256 { + self.0 + } + fn into_kv(self, block_number: u64) -> (B256, VersionedValue) { + let (hashed_address, account) = self; + (hashed_address, VersionedValue { block_number, value: MaybeDeleted(account) }) + } +} + +impl IntoKV for (B256, B256, Option) { + fn into_key(self) -> HashedStorageKey { + let (hashed_address, hashed_storage_key, _) = self; + HashedStorageKey::new(hashed_address, hashed_storage_key) + } + fn into_kv(self, block_number: u64) -> (HashedStorageKey, VersionedValue) { + let (hashed_address, hashed_storage_key, value) = self; + ( + HashedStorageKey::new(hashed_address, hashed_storage_key), + VersionedValue { block_number, value: MaybeDeleted(value) }, + ) + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/mod.rs b/vendor/reth-optimism-trie/src/db/models/mod.rs new file mode 100644 index 0000000..9ab3e0a --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/mod.rs @@ -0,0 +1,279 @@ +//! MDBX implementation of [`OpProofsStore`](crate::OpProofsStore). +//! +//! This module provides a complete MDBX implementation of the +//! [`OpProofsStore`](crate::OpProofsStore) trait. It uses the [`reth_db`] crate for +//! database interactions and defines the necessary tables and models for storing trie branches, +//! accounts, and storage leaves. + +mod block; +pub use block::*; +mod version; +pub use version::*; +mod storage; +pub use storage::*; +mod change_set; +pub use change_set::*; +pub mod kv; +pub use kv::*; +mod key; +pub use key::*; +mod value; +pub use value::*; +mod snapshot; +pub use snapshot::*; + +use alloy_primitives::{B256, BlockNumber}; +use reth_db::{ + BlockNumberList, TableSet, TableType, TableViewer, + table::{DupSort, TableInfo}, + tables, +}; +use reth_primitives_traits::{Account, StorageEntry}; +use reth_trie_common::{BranchNodeCompact, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey}; +use std::fmt; + +tables! { + /// Stores historical branch nodes for the account state trie. + /// + /// Each entry maps a compact-encoded trie path (`StoredNibbles`) to its versioned branch node. + /// Multiple versions of the same node are stored using the block number as a subkey. + table AccountTrieHistory { + type Key = StoredNibbles; + type Value = VersionedValue; + type SubKey = u64; // block number + } + + /// Stores historical branch nodes for the storage trie of each account. + /// + /// Each entry is identified by a composite key combining the account’s hashed address and the + /// compact-encoded trie path. Versions are tracked using block numbers as subkeys. + table StorageTrieHistory { + type Key = StorageTrieKey; + type Value = VersionedValue; + type SubKey = u64; // block number + } + + /// Stores versioned account state across block history. + /// + /// Each entry maps a hashed account address to its serialized account data (balance, nonce, + /// code hash, storage root). + table HashedAccountHistory { + type Key = B256; + type Value = VersionedValue; + type SubKey = u64; // block number + } + + /// Stores versioned storage state across block history. + /// + /// Each entry maps a composite key of (hashed address, storage key) to its stored value. + /// Used for reconstructing contract storage at any historical block height. + table HashedStorageHistory { + type Key = HashedStorageKey; + type Value = VersionedValue; + type SubKey = u64; // block number + } + + /// Tracks the active proof window in the external historical storage. + /// + /// Stores the earliest and latest block numbers (and corresponding hashes) + /// for which historical trie data is retained. + table ProofWindow { + type Key = ProofWindowKey; + type Value = BlockNumberHash; + } + + /// A reverse mapping of block numbers to a keys of the tables. + /// This is used for efficiently locating data by block number. + table BlockChangeSet { + type Key = u64; // Block number + type Value = ChangeSet; + } + + // ==================== V2 Tables ==================== + // + // The v2 schema uses the 3-table-per-data-type pattern. All v2 tables are + // prefixed with `V2` to clearly distinguish them from v1 tables and to ensure + // each store only reads/writes its own tables. + // + // - **Current state** tables hold the latest values for fast reads. + // - **ChangeSet** tables group changes by block number for efficient pruning/unwinding. + // - **History** tables store sharded bitmaps for historical lookups. + // + + // -------------------- Proof Window -------------------- + + /// V2 proof window tracking (independent of the v1 [`ProofWindow`] table). + table V2ProofWindow { + type Key = ProofWindowKey; + type Value = BlockNumberHash; + } + + // -------------------- Hashed Accounts -------------------- + + /// Sharded history index for hashed accounts. + /// + /// Maps `ShardedKey` (hashed address + highest block number in shard) + /// to a bitmap of block numbers that modified this account. Used for historical + /// lookups: find the relevant block in the bitmap, then read the changeset. + table V2HashedAccountsHistory { + type Key = HashedAccountShardedKey; + type Value = BlockNumberList; + } + + /// Account changesets grouped by block number. + /// + /// Each entry stores the hashed address and the account state **before** the + /// block was applied (`None` if the account didn't exist). Grouped by block + /// number for efficient pruning (delete all entries for a block in one + /// operation) and unwinding (restore old values on reorg). + table V2HashedAccountChangeSets { + type Key = BlockNumber; + type Value = HashedAccountBeforeTx; + type SubKey = B256; + } + + /// Current state of all accounts, keyed by `keccak256(address)`. + /// + /// Holds the latest account data (nonce, balance, code hash, storage root). + /// Primary read target for state root computation and proof generation — + /// no version lookup needed. + table V2HashedAccounts { + type Key = B256; + type Value = Account; + } + + // -------------------- Hashed Storages -------------------- + + /// Sharded history index for storage slots. + /// + /// Composite key of `(hashed_address, hashed_storage_key, highest_block_number)`. + /// Maps to a bitmap of block numbers that modified this storage slot. + table V2HashedStoragesHistory { + type Key = HashedStorageShardedKey; + type Value = BlockNumberList; + } + + /// Storage changesets grouped by block number and account. + /// + /// Composite key of `(block_number, hashed_address)`. Each entry stores the + /// hashed storage key and value **before** the block was applied. + /// A value of [`U256::ZERO`](alloy_primitives::U256::ZERO) means the slot + /// did not exist (needs to be removed on unwind). + table V2HashedStorageChangeSets { + type Key = BlockNumberHashedAddress; + type Value = StorageEntry; + type SubKey = B256; + } + + /// Current storage values, keyed by hashed address with hashed storage key + /// as the `DupSort` subkey. + /// + /// Holds the latest storage slot values for each account. Primary read target + /// for storage proof generation. + table V2HashedStorages { + type Key = B256; + type Value = StorageEntry; + type SubKey = B256; + } + + // -------------------- Account Trie -------------------- + + /// Sharded history index for the account state trie. + /// + /// Maps `ShardedKey` (trie path + highest block number in shard) + /// to a bitmap of block numbers that modified this path. + table V2AccountsTrieHistory { + type Key = AccountTrieShardedKey; + type Value = BlockNumberList; + } + + /// Account trie changesets grouped by block number. + /// + /// Each entry stores the trie path and the branch node value **before** the + /// block was applied (`None` if the node didn't exist). Enables efficient + /// pruning and unwinding of trie state. + table V2AccountTrieChangeSets { + type Key = BlockNumber; + type Value = TrieChangeSetsEntry; + type SubKey = StoredNibblesSubKey; + } + + /// Current state of the account Merkle Patricia Trie. + /// + /// Maps trie paths to the latest branch node. Primary read target during + /// proof generation — no version lookup needed. + table V2AccountsTrie { + type Key = StoredNibbles; + type Value = BranchNodeCompact; + } + + // -------------------- Storage Trie -------------------- + + /// Sharded history index for per-account storage tries. + /// + /// Composite key of `(hashed_address, trie_path, highest_block_number)`. + /// Maps to a bitmap of block numbers that modified this storage trie node. + table V2StoragesTrieHistory { + type Key = StorageTrieShardedKey; + type Value = BlockNumberList; + } + + /// Storage trie changesets grouped by block number and account. + /// + /// Composite key of `(block_number, hashed_address)`. Each entry stores the + /// trie path and the branch node value **before** the block was applied. + table V2StorageTrieChangeSets { + type Key = BlockNumberHashedAddress; + type Value = TrieChangeSetsEntry; + type SubKey = StoredNibblesSubKey; + } + + /// Current state of each account's storage Merkle Patricia Trie. + /// + /// Keyed by hashed account address, with the trie path as the `DupSort` subkey. + /// Holds the latest branch node for each path in each account's storage trie. + table V2StoragesTrie { + type Key = B256; + type Value = StorageTrieEntry; + type SubKey = StoredNibblesSubKey; + } + + /// Snapshot of [`V2AccountsTrie`] reflecting trie state at block x. + /// + /// Same schema as [`V2AccountsTrie`]. Populated by `SnapshotInitJob`. + table V2AccountsTrieSnapshot { + type Key = StoredNibbles; + type Value = BranchNodeCompact; + } + + /// Snapshot of [`V2StoragesTrie`] reflecting trie state at block x. + /// + /// Same schema as [`V2StoragesTrie`]. + table V2StoragesTrieSnapshot { + type Key = B256; + type Value = StorageTrieEntry; + type SubKey = StoredNibblesSubKey; + } + + /// Snapshot of [`V2HashedAccounts`] reflecting hashed-account leaves at the + /// snapshot anchor block. + table V2HashedAccountsSnapshot { + type Key = B256; + type Value = Account; + } + + /// Snapshot of [`V2HashedStorages`] reflecting hashed-storage leaves at the + /// snapshot anchor block. Same shape as [`V2HashedStorages`]. + table V2HashedStoragesSnapshot { + type Key = B256; + type Value = StorageEntry; + type SubKey = B256; + } + + /// Single-row metadata for the snapshot: which block its trie state + /// reflects, and whether it's [`SnapshotStatus::Ready`] for reads. + table V2SnapshotMeta { + type Key = SnapshotMetaKey; + type Value = SnapshotMeta; + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/snapshot.rs b/vendor/reth-optimism-trie/src/db/models/snapshot.rs new file mode 100644 index 0000000..3ef2aec --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/snapshot.rs @@ -0,0 +1,146 @@ +//! Models for the backfill trie-state snapshot (see +//! [`crate::backfill`] for the design rationale). + +use alloy_eips::BlockNumHash; +use alloy_primitives::B256; +use bytes::BufMut; +use reth_codecs::DecompressError; +use reth_db::{ + DatabaseError, + table::{Compress, Decode, Decompress, Encode}, +}; +use serde::{Deserialize, Serialize}; + +/// Single-row key for the snapshot metadata table. +/// +/// There is only ever one snapshot per proofs store, so the table has a +/// fixed singleton key. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(u8)] +pub enum SnapshotMetaKey { + /// The singleton key — there is only ever one snapshot meta row. + Singleton = 0, +} + +impl Encode for SnapshotMetaKey { + type Encoded = [u8; 1]; + + fn encode(self) -> Self::Encoded { + [self as u8] + } +} + +impl Decode for SnapshotMetaKey { + fn decode(value: &[u8]) -> Result { + match value.first() { + Some(&0) => Ok(Self::Singleton), + _ => Err(DatabaseError::Decode), + } + } +} + +/// Lifecycle status of the trie snapshot. +/// +/// A snapshot's invariant is that it reflects trie state at +/// [`SnapshotMeta::anchor`]. Either it's being built and not yet +/// trustworthy ([`Self::Building`]), or it's done and reflects that block +/// exactly ([`Self::Ready`]). If a snapshot ever falls out of sync it is +/// dropped via +/// [`OpProofsBackfillProvider::clear_snapshot`](crate::api::OpProofsBackfillProvider::clear_snapshot), +/// not left around in a third state. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[repr(u8)] +pub enum SnapshotStatus { + /// Snapshot is being constructed by [`crate::snapshot::SnapshotInitJob`]. + /// Reads must be refused until status transitions to [`Self::Ready`]. + Building = 0, + /// Snapshot is consistent and reflects trie state at [`SnapshotMeta::anchor`]. + Ready = 1, +} + +/// Metadata for the trie-state snapshot. +/// +/// Encoding: `[status: 1B] ‖ [block_number: 8B BE] ‖ [block_hash: 32B]` (= 41 B). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct SnapshotMeta { + /// The block (number + hash) the snapshot's trie state corresponds to. + pub anchor: BlockNumHash, + /// Current lifecycle state. + pub status: SnapshotStatus, +} + +impl SnapshotMeta { + /// Encoded byte length. + pub const ENCODED_LEN: usize = 1 + 8 + 32; + + /// Convenience constructor. + pub const fn new(anchor: BlockNumHash, status: SnapshotStatus) -> Self { + Self { anchor, status } + } +} + +impl Compress for SnapshotMeta { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + buf.put_u8(self.status as u8); + buf.put_u64(self.anchor.number); + buf.put_slice(self.anchor.hash.as_slice()); + } +} + +impl Decompress for SnapshotMeta { + fn decompress(value: &[u8]) -> Result { + if value.len() != Self::ENCODED_LEN { + return Err(DecompressError::new(DatabaseError::Decode)); + } + let status = match value[0] { + 0 => SnapshotStatus::Building, + 1 => SnapshotStatus::Ready, + _ => return Err(DecompressError::new(DatabaseError::Decode)), + }; + let number = u64::from_be_bytes( + value[1..9].try_into().map_err(|_| DecompressError::new(DatabaseError::Decode))?, + ); + let hash = B256::from_slice(&value[9..41]); + Ok(Self { anchor: BlockNumHash::new(number, hash), status }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn snapshot_meta_key_roundtrip() { + let encoded = SnapshotMetaKey::Singleton.encode(); + let decoded = SnapshotMetaKey::decode(&encoded).unwrap(); + assert_eq!(decoded, SnapshotMetaKey::Singleton); + } + + #[test] + fn snapshot_meta_roundtrip_all_statuses() { + let earliest = BlockNumHash::new(12_345_678, B256::repeat_byte(0xab)); + for status in [SnapshotStatus::Building, SnapshotStatus::Ready] { + let original = SnapshotMeta::new(earliest, status); + let compressed = original.compress(); + assert_eq!(compressed.len(), SnapshotMeta::ENCODED_LEN); + let decompressed = SnapshotMeta::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + } + + #[test] + fn snapshot_meta_decompress_rejects_wrong_length() { + assert!(SnapshotMeta::decompress(&[0u8; 10]).is_err()); + assert!(SnapshotMeta::decompress(&[0u8; 41 + 1]).is_err()); + } + + #[test] + fn snapshot_meta_decompress_rejects_invalid_status() { + let mut buf = vec![0xff_u8; SnapshotMeta::ENCODED_LEN]; + // status byte at position 0; 0xff is not a valid SnapshotStatus. + buf[0] = 0xff; + assert!(SnapshotMeta::decompress(&buf).is_err()); + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/storage.rs b/vendor/reth-optimism-trie/src/db/models/storage.rs new file mode 100644 index 0000000..ad520c1 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/storage.rs @@ -0,0 +1,258 @@ +use alloy_primitives::{B256, U256}; +use derive_more::{Constructor, From, Into}; +use reth_codecs::DecompressError; +use reth_db::{ + DatabaseError, + table::{Compress, Decode, Decompress, Encode}, +}; +use reth_trie_common::StoredNibbles; +use serde::{Deserialize, Serialize}; + +/// Composite key: `(hashed-address, path)` for storage trie branches +/// +/// Used to efficiently index storage branches by both account address and trie path. +/// The encoding ensures lexicographic ordering: first by address, then by path. +#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct StorageTrieKey { + /// Hashed account address + pub hashed_address: B256, + /// Trie path as nibbles + pub path: StoredNibbles, +} + +impl StorageTrieKey { + /// Create a new storage branch key + pub const fn new(hashed_address: B256, path: StoredNibbles) -> Self { + Self { hashed_address, path } + } +} + +impl Encode for StorageTrieKey { + type Encoded = Vec; + + fn encode(self) -> Self::Encoded { + let mut buf = Vec::with_capacity(32 + self.path.0.len()); + // First encode the address (32 bytes) + buf.extend_from_slice(self.hashed_address.as_slice()); + // Then encode the path + buf.extend_from_slice(&self.path.encode()); + buf + } +} + +impl Decode for StorageTrieKey { + fn decode(value: &[u8]) -> Result { + if value.len() < 32 { + return Err(DatabaseError::Decode); + } + + // First 32 bytes are the address + let hashed_address = B256::from_slice(&value[..32]); + + // Remaining bytes are the path + let path = StoredNibbles::decode(&value[32..])?; + + Ok(Self { hashed_address, path }) + } +} + +/// Composite key: (`hashed_address`, `hashed_storage_key`) for hashed storage values +/// +/// Used to efficiently index storage values by both account address and storage key. +/// The encoding ensures lexicographic ordering: first by address, then by storage key. +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct HashedStorageKey { + /// Hashed account address + pub hashed_address: B256, + /// Hashed storage key + pub hashed_storage_key: B256, +} + +impl HashedStorageKey { + /// Create a new hashed storage key + pub const fn new(hashed_address: B256, hashed_storage_key: B256) -> Self { + Self { hashed_address, hashed_storage_key } + } +} + +impl Encode for HashedStorageKey { + type Encoded = [u8; 64]; + + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; 64]; + // First 32 bytes: address + buf[..32].copy_from_slice(self.hashed_address.as_slice()); + // Next 32 bytes: storage key + buf[32..].copy_from_slice(self.hashed_storage_key.as_slice()); + buf + } +} + +impl Decode for HashedStorageKey { + fn decode(value: &[u8]) -> Result { + if value.len() != 64 { + return Err(DatabaseError::Decode); + } + + let hashed_address = B256::from_slice(&value[..32]); + let hashed_storage_key = B256::from_slice(&value[32..64]); + + Ok(Self { hashed_address, hashed_storage_key }) + } +} + +/// Storage value wrapper for U256 values +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, From, Into, Constructor)] +pub struct StorageValue(pub U256); + +impl Compress for StorageValue { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + let be: [u8; 32] = self.0.to_be_bytes::<32>(); + buf.put_slice(&be); + } +} + +impl Decompress for StorageValue { + fn decompress(value: &[u8]) -> Result { + if value.len() != 32 { + return Err(DecompressError::new(DatabaseError::Decode)); + } + let bytes: [u8; 32] = + value.try_into().map_err(|_| DecompressError::new(DatabaseError::Decode))?; + Ok(Self(U256::from_be_bytes(bytes))) + } +} + +/// Proof Window key for tracking active proof window bounds +/// +/// Used to store earliest and latest block numbers in the external storage. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(u8)] +pub enum ProofWindowKey { + /// Earliest block number stored in external storage + EarliestBlock = 0, + /// Latest block number stored in external storage + LatestBlock = 1, + /// Anchor block from where the initial state initialization started + InitialStateAnchor = 2, +} + +impl Encode for ProofWindowKey { + type Encoded = [u8; 1]; + + fn encode(self) -> Self::Encoded { + [self as u8] + } +} + +impl Decode for ProofWindowKey { + fn decode(value: &[u8]) -> Result { + match value.first() { + Some(&0) => Ok(Self::EarliestBlock), + Some(&1) => Ok(Self::LatestBlock), + Some(&2) => Ok(Self::InitialStateAnchor), + _ => Err(DatabaseError::Decode), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_trie::Nibbles; + + #[test] + fn test_storage_branch_subkey_encode_decode() { + let addr = B256::from([1u8; 32]); + let path = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 2, 3, 4])); + let key = StorageTrieKey::new(addr, path.clone()); + + let encoded = key.clone().encode(); + let decoded = StorageTrieKey::decode(&encoded).unwrap(); + + assert_eq!(key, decoded); + assert_eq!(decoded.hashed_address, addr); + assert_eq!(decoded.path, path); + } + + #[test] + fn test_storage_branch_subkey_ordering() { + let addr1 = B256::from([1u8; 32]); + let addr2 = B256::from([2u8; 32]); + let path1 = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 2])); + let path2 = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 3])); + + let key1 = StorageTrieKey::new(addr1, path1.clone()); + let key2 = StorageTrieKey::new(addr1, path2); + let key3 = StorageTrieKey::new(addr2, path1); + + // Encoded bytes should be sortable: first by address, then by path + let enc1 = key1.encode(); + let enc2 = key2.encode(); + let enc3 = key3.encode(); + + assert!(enc1 < enc2, "Same address, path1 < path2"); + assert!(enc1 < enc3, "addr1 < addr2"); + assert!(enc2 < enc3, "addr1 < addr2 (even with larger path)"); + } + + #[test] + fn test_hashed_storage_subkey_encode_decode() { + let addr = B256::from([1u8; 32]); + let storage_key = B256::from([2u8; 32]); + let key = HashedStorageKey::new(addr, storage_key); + + let encoded = key.clone().encode(); + let decoded = HashedStorageKey::decode(&encoded).unwrap(); + + assert_eq!(key, decoded); + assert_eq!(decoded.hashed_address, addr); + assert_eq!(decoded.hashed_storage_key, storage_key); + } + + #[test] + fn test_hashed_storage_subkey_ordering() { + let addr1 = B256::from([1u8; 32]); + let addr2 = B256::from([2u8; 32]); + let storage1 = B256::from([10u8; 32]); + let storage2 = B256::from([20u8; 32]); + + let key1 = HashedStorageKey::new(addr1, storage1); + let key2 = HashedStorageKey::new(addr1, storage2); + let key3 = HashedStorageKey::new(addr2, storage1); + + // Encoded bytes should be sortable: first by address, then by storage key + let enc1 = key1.encode(); + let enc2 = key2.encode(); + let enc3 = key3.encode(); + + assert!(enc1 < enc2, "Same address, storage1 < storage2"); + assert!(enc1 < enc3, "addr1 < addr2"); + assert!(enc2 < enc3, "addr1 < addr2 (even with larger storage key)"); + } + + #[test] + fn test_hashed_storage_subkey_size() { + let addr = B256::from([1u8; 32]); + let storage_key = B256::from([2u8; 32]); + let key = HashedStorageKey::new(addr, storage_key); + + let encoded = key.encode(); + assert_eq!(encoded.len(), 64, "Encoded size should be exactly 64 bytes"); + } + + #[test] + fn test_metadata_key_encode_decode() { + let key = ProofWindowKey::EarliestBlock; + let encoded = key.encode(); + let decoded = ProofWindowKey::decode(&encoded).unwrap(); + assert_eq!(key, decoded); + + let key = ProofWindowKey::LatestBlock; + let encoded = key.encode(); + let decoded = ProofWindowKey::decode(&encoded).unwrap(); + assert_eq!(key, decoded); + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/value.rs b/vendor/reth-optimism-trie/src/db/models/value.rs new file mode 100644 index 0000000..1b3f680 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/value.rs @@ -0,0 +1,210 @@ +use alloy_primitives::B256; +use bytes::BufMut; +use reth_codecs::{Compact, DecompressError}; +use reth_db::{ + DatabaseError, + table::{Compress, Decompress}, +}; +use reth_primitives_traits::{Account, ValueWithSubKey}; +use reth_trie_common::{BranchNodeCompact, StoredNibblesSubKey}; +use serde::{Deserialize, Serialize}; + +/// Account state before a block, keyed by hashed address. +/// +/// This is the hashed-address equivalent of reth's +/// `AccountBeforeTx`, designed for our v2 `AccountChangeSets` +/// table where keys are `keccak256(address)`. +/// +/// Layout: `[hashed_address: 32 bytes][account: Compact-encoded or empty]` +/// +/// - The 32-byte hashed address acts as the [`DupSort::SubKey`]. +/// - An empty remainder means the account did not exist before this block (creation). +/// - A non-empty remainder is the [`Account`] state before the block was applied. +/// +/// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct HashedAccountBeforeTx { + /// Hashed address (`keccak256(address)`). Acts as `DupSort::SubKey`. + pub hashed_address: B256, + /// Account state before the block. `None` means the account didn't exist. + pub info: Option, +} + +impl HashedAccountBeforeTx { + /// Creates a new instance. + pub const fn new(hashed_address: B256, info: Option) -> Self { + Self { hashed_address, info } + } +} + +impl ValueWithSubKey for HashedAccountBeforeTx { + type SubKey = B256; + + fn get_subkey(&self) -> Self::SubKey { + self.hashed_address + } +} + +impl Compress for HashedAccountBeforeTx { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + // SubKey: raw 32 bytes (uncompressed so MDBX can seek by it) + buf.put_slice(self.hashed_address.as_slice()); + // Value: compress the account if present, otherwise write nothing + if let Some(account) = &self.info { + account.compress_to_buf(buf); + } + } +} + +impl Decompress for HashedAccountBeforeTx { + fn decompress(value: &[u8]) -> Result { + if value.len() < 32 { + return Err(DecompressError::new(DatabaseError::Decode)); + } + + let hashed_address = B256::from_slice(&value[..32]); + let info = if value.len() > 32 { Some(Account::decompress(&value[32..])?) } else { None }; + + Ok(Self { hashed_address, info }) + } +} + +/// Trie changeset entry representing the state of a trie node before a block. +/// +/// `nibbles` is the subkey when used as a value in the changeset tables. +/// This is a local definition since the upstream `reth-trie-common` crate does +/// not provide this type. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TrieChangeSetsEntry { + /// The nibbles of the intermediate node + pub nibbles: StoredNibblesSubKey, + /// Node value prior to the block being processed, None indicating it didn't exist. + pub node: Option, +} + +impl ValueWithSubKey for TrieChangeSetsEntry { + type SubKey = StoredNibblesSubKey; + + fn get_subkey(&self) -> Self::SubKey { + self.nibbles.clone() + } +} + +impl Compress for TrieChangeSetsEntry { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + let _ = self.nibbles.to_compact(buf); + if let Some(ref node) = self.node { + let _ = node.to_compact(buf); + } + } +} + +impl Decompress for TrieChangeSetsEntry { + fn decompress(value: &[u8]) -> Result { + if value.is_empty() { + return Ok(Self { + nibbles: StoredNibblesSubKey::from(reth_trie_common::Nibbles::default()), + node: None, + }); + } + + let (nibbles, rest) = StoredNibblesSubKey::from_compact(value, 65); + let node = if rest.is_empty() { + None + } else { + Some(BranchNodeCompact::from_compact(rest, rest.len()).0) + }; + Ok(Self { nibbles, node }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_db::table::{Compress, Decompress}; + + #[test] + fn test_hashed_account_before_tx_roundtrip_some() { + let original = HashedAccountBeforeTx { + hashed_address: B256::repeat_byte(0xaa), + info: Some(Account { + nonce: 42, + balance: alloy_primitives::U256::from(1000u64), + bytecode_hash: None, + }), + }; + + let compressed = original.clone().compress(); + assert!(compressed.len() > 32, "Should contain address + account data"); + + let decompressed = HashedAccountBeforeTx::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_hashed_account_before_tx_roundtrip_none() { + let original = + HashedAccountBeforeTx { hashed_address: B256::repeat_byte(0xbb), info: None }; + + let compressed = original.clone().compress(); + assert_eq!(compressed.len(), 32, "None account should be just the 32-byte address"); + + let decompressed = HashedAccountBeforeTx::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_hashed_account_before_tx_subkey() { + let addr = B256::repeat_byte(0xcc); + let entry = HashedAccountBeforeTx::new(addr, None); + assert_eq!(entry.get_subkey(), addr); + } + + #[test] + fn test_trie_changesets_entry_roundtrip_with_node() { + let nibbles = + StoredNibblesSubKey(reth_trie_common::Nibbles::from_nibbles_unchecked([0x0A, 0x0B])); + let node = BranchNodeCompact::new(0b11, 0, 0, vec![], Some(B256::repeat_byte(0xDD))); + let original = TrieChangeSetsEntry { nibbles, node: Some(node) }; + + let compressed = original.clone().compress(); + let decompressed = TrieChangeSetsEntry::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_trie_changesets_entry_roundtrip_none_node() { + let nibbles = StoredNibblesSubKey(reth_trie_common::Nibbles::from_nibbles_unchecked([ + 0x01, 0x02, 0x03, + ])); + let original = TrieChangeSetsEntry { nibbles, node: None }; + + let compressed = original.clone().compress(); + let decompressed = TrieChangeSetsEntry::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_trie_changesets_entry_roundtrip_empty() { + let original = TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(reth_trie_common::Nibbles::default()), + node: None, + }; + + let compressed = original.clone().compress(); + let decompressed = TrieChangeSetsEntry::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_trie_changesets_entry_subkey() { + let nibbles = + StoredNibblesSubKey(reth_trie_common::Nibbles::from_nibbles_unchecked([0x05, 0x06])); + let entry = TrieChangeSetsEntry { nibbles: nibbles.clone(), node: None }; + assert_eq!(entry.get_subkey(), nibbles); + } +} diff --git a/vendor/reth-optimism-trie/src/db/models/version.rs b/vendor/reth-optimism-trie/src/db/models/version.rs new file mode 100644 index 0000000..90af120 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/models/version.rs @@ -0,0 +1,192 @@ +use bytes::{Buf, BufMut}; +use reth_codecs::DecompressError; +use reth_db::{ + DatabaseError, + table::{Compress, Decompress}, +}; +use reth_primitives_traits::ValueWithSubKey; +use serde::{Deserialize, Serialize}; + +/// Wrapper type for `Option` that implements [`Compress`] and [`Decompress`] +/// +/// Encoding: +/// - `None` => empty byte array (length 0) +/// - `Some(value)` => compressed bytes of value (length > 0) +/// +/// This assumes the inner type `T` always compresses to non-empty bytes when it exists. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MaybeDeleted(pub Option); + +impl From> for MaybeDeleted { + fn from(opt: Option) -> Self { + Self(opt) + } +} + +impl From> for Option { + fn from(maybe: MaybeDeleted) -> Self { + maybe.0 + } +} + +impl Compress for MaybeDeleted { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + match &self.0 { + None => { + // Empty = deleted, write nothing + } + Some(value) => { + // Compress the inner value to the buffer + value.compress_to_buf(buf); + } + } + } +} + +impl Decompress for MaybeDeleted { + fn decompress(value: &[u8]) -> Result { + if value.is_empty() { + // Empty = deleted + Ok(Self(None)) + } else { + // Non-empty = present + let inner = T::decompress(value)?; + Ok(Self(Some(inner))) + } + } +} + +/// Versioned value wrapper for [`DupSort`] tables +/// +/// For [`DupSort`] tables in MDBX, the Value type must contain the [`DupSort::SubKey`] as a field. +/// This wrapper combines a [`block_number`] (the [`DupSort::SubKey`]) with +/// the actual value. +/// +/// [`DupSort`]: reth_db::table::DupSort +/// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey +/// [`block_number`]: Self::block_number +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct VersionedValue { + /// Block number ([`DupSort::SubKey`] for [`DupSort`]) + /// + /// [`DupSort`]: reth_db::table::DupSort + /// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey + pub block_number: u64, + /// The actual value (may be deleted) + pub value: MaybeDeleted, +} + +impl VersionedValue { + /// Create a new versioned value + pub const fn new(block_number: u64, value: MaybeDeleted) -> Self { + Self { block_number, value } + } +} + +impl Compress for VersionedValue { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + // Encode block number first (8 bytes, big-endian) + buf.put_u64(self.block_number); + // Then encode the value + self.value.compress_to_buf(buf); + } +} + +impl Decompress for VersionedValue { + fn decompress(value: &[u8]) -> Result { + if value.len() < 8 { + return Err(DecompressError::new(DatabaseError::Decode)); + } + + let mut buf: &[u8] = value; + let block_number = buf.get_u64(); + let value = MaybeDeleted::::decompress(&value[8..])?; + + Ok(Self { block_number, value }) + } +} + +impl ValueWithSubKey for VersionedValue { + type SubKey = u64; + + fn get_subkey(&self) -> Self::SubKey { + self.block_number + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_primitives_traits::Account; + use reth_trie::BranchNodeCompact; + + #[test] + fn test_maybe_deleted_none() { + let none: MaybeDeleted = MaybeDeleted(None); + let compressed = none.compress(); + assert!(compressed.is_empty(), "None should compress to empty bytes"); + + let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); + assert_eq!(decompressed.0, None); + } + + #[test] + fn test_maybe_deleted_some_account() { + let account = Account { + nonce: 42, + balance: alloy_primitives::U256::from(1000u64), + bytecode_hash: None, + }; + let some = MaybeDeleted(Some(account)); + let compressed = some.compress(); + assert!(!compressed.is_empty(), "Some should compress to non-empty bytes"); + + let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); + assert_eq!(decompressed.0, Some(account)); + } + + #[test] + fn test_maybe_deleted_some_branch() { + // Create a simple valid BranchNodeCompact (empty is valid) + let branch = BranchNodeCompact::new( + 0, // state_mask + 0, // tree_mask + 0, // hash_mask + vec![], // hashes + None, // root_hash + ); + let some = MaybeDeleted(Some(branch.clone())); + let compressed = some.compress(); + assert!(!compressed.is_empty(), "Some should compress to non-empty bytes"); + + let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); + assert_eq!(decompressed.0, Some(branch)); + } + + #[test] + fn test_maybe_deleted_roundtrip() { + let test_cases = vec![ + MaybeDeleted(None), + MaybeDeleted(Some(Account { + nonce: 0, + balance: alloy_primitives::U256::ZERO, + bytecode_hash: None, + })), + MaybeDeleted(Some(Account { + nonce: 999, + balance: alloy_primitives::U256::MAX, + bytecode_hash: Some([0xff; 32].into()), + })), + ]; + + for original in test_cases { + let compressed = original.clone().compress(); + let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + } +} diff --git a/vendor/reth-optimism-trie/src/db/schema.md b/vendor/reth-optimism-trie/src/db/schema.md new file mode 100644 index 0000000..4c2b04f --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/schema.md @@ -0,0 +1,366 @@ +# Proof History Database Schema + +> Location: `crates/optimism/trie/src/db` +> Backend: **MDBX** (via `reth-db`) +> Purpose: Serve **historical `eth_getProof`** by storing versioned trie data in a bounded window. + +--- + +## Design Overview + +This database is a **versioned, append-only history store** for Ethereum state tries. + +Each logical key is stored with **multiple historical versions**, each tagged by a **block number**. Reads select the latest version whose block number is **≤ the requested block**. + +### Core principles + +* History tables are **DupSort** tables +* Each entry is versioned by `block_number` +* Deletions are encoded as **tombstones** +* A reverse index (`BlockChangeSet`) enables **range pruning** +* Proof window bounds are tracked explicitly + +--- + +## Version Encoding + +All historical values are wrapped in `VersionedValue`. + +### `VersionedValue` + +| Field | Type | Encoding | +| -------------- | ----------------- | -------------------- | +| `block_number` | `u64` | big-endian (8 bytes) | +| `value` | `MaybeDeleted` | see below | + +``` +VersionedValue := block_number || maybe_deleted_value +``` + +--- + +### `MaybeDeleted` + +Encodes value presence or deletion: + +| Logical value | Encoding | +| ------------- | ---------------------------- | +| `Some(T)` | `T::compress()` | +| `None` | empty byte slice (`len = 0`) | + +An empty value represents **deletion at that block**. + +--- + +## Tables + +--- + +## 1. `AccountTrieHistory` (DupSort) + +Historical **branch nodes** of the **account trie**. + +### Purpose + +Reconstruct account trie structure at any historical block. + +### Schema + +| Component | Type | +| --------- | ----------------------------------- | +| Key | `StoredNibbles` | +| SubKey | `u64` (block number) | +| Value | `VersionedValue` | + +### Key encoding + +* `StoredNibbles` = compact-encoded trie path + +### Semantics + +For a given trie path: + +* Multiple versions may exist +* Reader selects highest `block_number ≤ target_block` + +--- + +## 2. `StorageTrieHistory` (DupSort) + +Historical **branch nodes** of **per-account storage tries**. + +### Schema + +| Component | Type | +| --------- | ----------------------------------- | +| Key | `StorageTrieKey` | +| SubKey | `u64` | +| Value | `VersionedValue` | + +### `StorageTrieKey` encoding + +``` +StorageTrieKey := + hashed_address (32 bytes) + || StoredNibbles::encode(path) +``` + +Ordering: + +1. `hashed_address` +2. trie path bytes + +--- + +## 3. `HashedAccountHistory` (DupSort) + +Historical **account leaf values**. + +### Schema + +| Component | Type | +| --------- | ------------------------- | +| Key | `B256` (hashed address) | +| SubKey | `u64` | +| Value | `VersionedValue` | + +### Semantics + +Stores nonce, balance, code hash, and storage root per account per block. + +--- + +## 4. `HashedStorageHistory` (DupSort) + +Historical **storage slot values**. + +### Schema + +| Component | Type | +| --------- | ------------------------------ | +| Key | `HashedStorageKey` | +| SubKey | `u64` | +| Value | `VersionedValue` | + +### `HashedStorageKey` encoding + +Fixed 64 bytes: + +``` +hashed_address (32 bytes) || hashed_storage_key (32 bytes) +``` + +### `StorageValue` encoding + +* Wraps `U256` +* Encoded as **32-byte big-endian** + +--- + +## 5. `BlockChangeSet` + +Reverse index of **which keys were modified in a block**. + +### Purpose + +Efficient pruning by block range. + +### Schema + +| Component | Type | +| --------- | -------------------- | +| Key | `u64` (block number) | +| Value | `ChangeSet` | + +### `ChangeSet` structure + +```rust +pub struct ChangeSet { + pub account_trie_keys: Vec, + pub storage_trie_keys: Vec, + pub hashed_account_keys: Vec, + pub hashed_storage_keys: Vec, +} +``` + +### Encoding + +* Serialized using **bincode** + +--- + +## 6. `ProofWindow` + +Tracks active proof window bounds. + +### Schema + +| Component | Type | +| --------- | ----------------- | +| Key | `ProofWindowKey` | +| Value | `BlockNumberHash` | + +### `ProofWindowKey` + +| Variant | Encoding | +| --------------- | -------- | +| `EarliestBlock` | `0u8` | +| `LatestBlock` | `1u8` | + +### `BlockNumberHash` encoding + +``` +block_number (u64 BE, 8 bytes) +|| block_hash (B256, 32 bytes) +``` + +Total size: **40 bytes** + +--- +Here is a **short, clean, professional** version suitable for `SCHEMA.md`: + +--- + +## Reads: Hashed & Trie Cursors + +Historical reads are performed using **hashed cursors** and **trie cursors**, both operating on versioned history tables. + +All reads follow the same rule: + +> Select the newest entry whose block number is **≤ the requested block**. + +--- + +### Hashed Cursors + +Hashed cursors read **leaf values** from: + +* `HashedAccountHistory` +* `HashedStorageHistory` + +They answer: + +> *What was the value of this account or storage slot at block B?* + +For a given key, the cursor scans historical versions and returns the latest valid value. Tombstones indicate deletion and are treated as non-existence. + +--- + +### Trie Cursors + +Trie cursors read **trie branch nodes** from: + +* `AccountTrieHistory` +* `StorageTrieHistory` + +They answer: + +> *Which trie nodes existed at this path at block B?* + +These cursors enable reconstruction of Merkle paths required for proof generation. + +--- + +### Combined Usage + +When serving `eth_getProof`: + +* Trie cursors reconstruct the Merkle path +* Hashed cursors supply the leaf values + +Both are evaluated at the same target explained block to produce deterministic historical proofs. + + +--- +## Writes: `store_trie_updates` (Append-Only) + +`store_trie_updates` persists all state changes introduced by a block using a strictly **append-only** write model. + + +### Purpose + +The function records **historical trie updates** so that state and proofs can be reconstructed at any later block. + +--- + +### What is written + +For a processed block `B`, the following data is appended: + +* Account trie branch nodes → `AccountTrieHistory` +* Storage trie branch nodes → `StorageTrieHistory` +* Account leaf updates → `HashedAccountHistory` +* Storage slot updates → `HashedStorageHistory` +* Modified keys → `BlockChangeSet[B]` + +All entries are tagged with the same `block_number`. + +--- + +### How writes work + +For each updated item: + +1. A `VersionedValue` is created with: + + * `block_number = B` + * the encoded node or value + +2. The entry is appended to the corresponding history table. + +No existing entries are modified or replaced. + + +--- +Here is a **concise, professional, SCHEMA.md-style** explanation aligned exactly with your clarification: + +--- + +## Initial State Backfill + +### Source database (Reth) + +The initial state is sourced from **Reth’s main execution database**, which only contains data for the **current canonical state**. + +The backfill reads from the following Reth tables: + +* `HashedAccounts` – current account leaf values +* `HashedStorages` – current storage slot values +* `AccountsTrie` – current account trie branch nodes +* `StoragesTrie` – current storage trie branch nodes + +These tables do **not** contain historical versions; they represent a single finalized state snapshot. + +--- + +### Destination database (Proofs storage) + +The data is copied into the **proofs history database** (`OpProofsStore`), which is a **versioned, append-only** store designed for historical proof generation. + +--- + +### How the initial state is created + +During backfill: + +1. The current state is fully scanned from Reth tables using read-only cursors. +2. All entries are written into the proofs storage as versioned records. +3. This creates a complete **baseline state** inside the proofs DB. + +The backfill runs only once and is skipped if the proofs DB already has an `earliest_block` set. + +--- + +### Why block `0` is used + +Since Reth tables only represent the **current state**, the copied data must be assigned a synthetic version. + +Block **`0`** is used as the baseline version because: + +* It is ≤ any real block number +* It establishes a stable initial version for all keys +* Later block updates naturally override it using higher block numbers + +This makes block `0` the canonical **initial state anchor** for versioned reads. + +--- diff --git a/vendor/reth-optimism-trie/src/db/store.rs b/vendor/reth-optimism-trie/src/db/store.rs new file mode 100644 index 0000000..7efebc2 --- /dev/null +++ b/vendor/reth-optimism-trie/src/db/store.rs @@ -0,0 +1,4014 @@ +use super::{BlockNumberHash, ProofWindow, ProofWindowKey, Tables}; +use crate::{ + BlockStateDiff, OpProofsStorageError, + OpProofsStorageError::NoBlocksFound, + OpProofsStorageResult, + api::{ + InitialStateAnchor, InitialStateStatus, OpProofsInitProvider, OpProofsProviderRO, + OpProofsProviderRw, OpProofsStore, ProofWindowRange, WriteCounts, + }, + db::{ + MdbxAccountCursor, MdbxStorageCursor, MdbxTrieCursor, + models::{ + AccountTrieHistory, BlockChangeSet, ChangeSet, HashedAccountHistory, + HashedStorageHistory, HashedStorageKey, MaybeDeleted, StorageTrieHistory, + StorageTrieKey, StorageValue, VersionedValue, kv::IntoKV, + }, + }, +}; +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use alloy_primitives::{B256, U256, map::HashMap}; +#[cfg(feature = "metrics")] +use metrics::{Label, gauge}; +use reth_db::{ + Database, DatabaseEnv, DatabaseError, + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, + mdbx::{DatabaseArguments, init_db_for}, + table::{DupSort, Table}, + transaction::{DbTx, DbTxMut}, +}; +use reth_primitives_traits::Account; +use reth_trie::{hashed_cursor::HashedCursor, trie_cursor::TrieCursor}; +use reth_trie_common::{ + BranchNodeCompact, HashedPostState, Nibbles, StoredNibbles, + updates::{StorageTrieUpdates, TrieUpdates}, +}; +use std::{fmt::Debug, ops::RangeBounds, path::Path, sync::Arc}; + +/// Preprocessed prune plan for a target block number +#[derive(Debug, Clone)] +struct PrunePlan { + earliest_block: u64, + acc_survivors: Vec<(StoredNibbles, u64)>, + storage_survivors: Vec<(StorageTrieKey, u64)>, + hashed_acc_survivors: Vec<(B256, u64)>, + hashed_storage_survivors: Vec<(HashedStorageKey, u64)>, +} + +/// Preprocessed delete work for a prune range +#[derive(Debug, Default, Clone)] +struct HistoryDeleteBatch { + account_trie: Vec<(::Key, u64)>, + storage_trie: Vec<(::Key, u64)>, + hashed_account: Vec<(::Key, u64)>, + hashed_storage: Vec<(::Key, u64)>, +} + +/// MDBX implementation of [`OpProofsStore`]. +#[derive(Debug)] +pub struct MdbxProofsStorage { + env: DatabaseEnv, +} + +impl MdbxProofsStorage { + /// Creates a new [`MdbxProofsStorage`] instance with the given path. + pub fn new(path: &Path) -> Result { + let env = init_db_for::<_, Tables>(path, DatabaseArguments::default()) + .map_err(|e| DatabaseError::Other(format!("Failed to open database: {e}")))?; + Ok(Self { env }) + } +} + +/// MDBX provider for proof storage +#[derive(Debug)] +pub struct MdbxProofsProvider { + pub(crate) tx: TX, +} + +impl MdbxProofsProvider { + /// Creates a new `MdbxProofsProvider` instance with the given transaction. + pub const fn new(tx: TX) -> Self { + Self { tx } + } +} + +impl MdbxProofsProvider { + fn get_block_number_hash_inner(&self, key: ProofWindowKey) -> OpProofsStorageResult { + let mut cursor = self.tx.cursor_read::()?; + cursor + .seek_exact(key)? + .map(|(_, val)| NumHash::new(val.number(), *val.hash())) + .ok_or(NoBlocksFound) + } + + /// Phase 1 of pruning: Calculate survivors. + /// Scans change sets to find the LATEST update for every key in the range. + fn calculate_prune_plan(&self, target_block: u64) -> OpProofsStorageResult> { + let earliest = self.get_block_number_hash_inner(ProofWindowKey::EarliestBlock)?.number; + if earliest >= target_block { + return Err(OpProofsStorageError::PruneBeyondEarliest { + target_block_number: target_block, + earliest_block_number: earliest, + }); + } + + let mut acc_candidates: HashMap = HashMap::default(); + let mut storage_candidates: HashMap = HashMap::default(); + let mut hashed_acc_candidates: HashMap = HashMap::default(); + let mut hashed_storage_candidates: HashMap = HashMap::default(); + + let range = (earliest + 1)..=target_block; + let mut cs_cursor = self.tx.cursor_read::()?; + let mut walker = cs_cursor.walk_range(range)?; + + while let Some(Ok((block_number, cs))) = walker.next() { + Self::track_latest(&mut acc_candidates, cs.account_trie_keys, block_number); + Self::track_latest(&mut storage_candidates, cs.storage_trie_keys, block_number); + Self::track_latest(&mut hashed_acc_candidates, cs.hashed_account_keys, block_number); + Self::track_latest( + &mut hashed_storage_candidates, + cs.hashed_storage_keys, + block_number, + ); + } + + Ok(Some(PrunePlan { + earliest_block: earliest, + acc_survivors: Self::flatten_and_sort(acc_candidates), + storage_survivors: Self::flatten_and_sort(storage_candidates), + hashed_acc_survivors: Self::flatten_and_sort(hashed_acc_candidates), + hashed_storage_survivors: Self::flatten_and_sort(hashed_storage_candidates), + })) + } + + /// Records the latest block number for each key in a candidate map. + /// Used during prune planning to identify survivor versions. + fn track_latest( + candidates: &mut HashMap, + keys: impl IntoIterator, + block_number: u64, + ) { + for k in keys { + candidates + .entry(k) + .and_modify(|curr| *curr = (*curr).max(block_number)) + .or_insert(block_number); + } + } + + /// Helper to flatten `HashMap` into a sorted Vector of survivors. + /// Sorting is required to ensure optimal sequential seek performance in MDBX. + fn flatten_and_sort(map: HashMap) -> Vec<(K, u64)> { + let mut v: Vec<_> = map.into_iter().collect(); + v.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + v + } +} + +impl MdbxProofsProvider { + fn set_earliest_block_number_inner( + &self, + block_number: u64, + hash: B256, + ) -> OpProofsStorageResult<()> { + let mut cursor = self.tx.cursor_write::()?; + cursor.upsert(ProofWindowKey::EarliestBlock, &BlockNumberHash::new(block_number, hash))?; + Ok(()) + } + + fn set_latest_block_number_inner( + &self, + block_number: u64, + hash: B256, + ) -> OpProofsStorageResult<()> { + let mut cursor = self.tx.cursor_write::()?; + cursor.upsert(ProofWindowKey::LatestBlock, &BlockNumberHash::new(block_number, hash))?; + Ok(()) + } + + /// Persist a batch of versioned history entries to a dup-sorted table. + /// + /// # Parameters + /// - `block_number`: Target block number for versioning entries + /// - `items`: **Must be sorted** - iterator of entries to persist + /// - `append_mode`: Mode selector for write strategy: + /// - `true` (Append): Appends all entries including tombstones for forward progress + /// - `false` (Prune): Removes tombstones, writes non-tombstones to block 0 + /// + /// The cost of pruning is the cost of (append + deleting tombstones + deleting old block 0). + /// The tombstones deletion is expensive as it requires a seek for each (key + subkey). + /// + /// Uses [`reth_db::mdbx::cursor::Cursor::upsert`] for upsert operation. + fn persist_history_batch( + &self, + block_number: T::SubKey, + items: I, + append_mode: bool, + ) -> OpProofsStorageResult> + where + T: Table> + DupSort, + T::Key: Clone, + I: IntoIterator, + I::Item: IntoKV, + { + let mut cur = self.tx.cursor_dup_write::()?; + let mut keys = Vec::::new(); + + // Materialize iterator to enable partitioning and collect keys + let mut pairs: Vec<(T::Key, T::Value)> = Vec::new(); + for it in items { + let (k, vv) = it.into_kv(block_number); + pairs.push((k.clone(), vv)); + keys.push(k) + } + + if append_mode { + // Append all entries (including tombstones) to preserve full history + for (k, vv) in pairs { + cur.append_dup(k.clone(), vv)?; + } + return Ok(keys); + } + + // Drop current cursor to start clean for Phase 1 + drop(cur); + + // Phase 1: Batch Delete (Sequential) + // Remove all existing state at Block 0 for these keys. + { + let mut del_cur = self.tx.cursor_dup_write::()?; + for (k, _) in &pairs { + // Seek to (Key, Block 0) + if let Some(vv) = del_cur.seek_by_key_subkey(k.clone(), 0)? && + vv.block_number == 0 + { + del_cur.delete_current()?; + } + } + } + + // Phase 2: Batch Write (Sequential) + // Write new values (skipping tombstones). + { + let mut write_cur = self.tx.cursor_dup_write::()?; + for (k, vv) in pairs { + if vv.value.0.is_some() { + write_cur.upsert(k, &vv)?; + } + } + } + + Ok(keys) + } + + /// Delete entries for `items` at exactly `block_number` in a dup-sorted table. + /// Seeks (key, block) and deletes current if the subkey matches. + fn delete_dup_sorted(&self, items: I) -> OpProofsStorageResult<()> + where + T: Table> + DupSort, + T::Key: Clone, + T::SubKey: PartialEq + Clone, + I: IntoIterator, + { + let mut cur = self.tx.cursor_dup_write::()?; + for (key, subkey) in items { + if let Some(vv) = cur.seek_by_key_subkey(key, subkey)? + // ensure we didn't land on a >subkey + && vv.block_number == subkey + { + cur.delete_current()?; + } + } + Ok(()) + } + + /// Delete history versions for `items` that are strictly older than the provided block number. + /// `items` is a list of (Key, `SurvivorBlock`). Everything strictly older than `SurvivorBlock` + /// is deleted. Returns the number of entries deleted. + fn prune_history_preceding( + &self, + cutoff_items: Vec<(T::Key, u64)>, + ) -> OpProofsStorageResult + where + T: Table> + DupSort, + T::Key: Clone + Ord, + { + if cutoff_items.is_empty() { + return Ok(0); + } + + let mut deleted_count = 0; + let mut cur = self.tx.cursor_dup_write::()?; + for (key, survivor_block) in cutoff_items { + // Seek to the start of history for this key (Block 0) + if let Some(mut entry) = cur.seek_by_key_subkey(key.clone(), 0)? { + loop { + if entry.block_number >= survivor_block { + // Reached the survivor version (or newer). Stop deleting for this key. + + // If the survivor is a tombstone (None), delete it too. + // Since we just deleted all older history, a tombstone at the start of + // history is redundant (it implies "does not + // exist"). + if entry.block_number == survivor_block && entry.value.0.is_none() { + cur.delete_current()?; + deleted_count += 1; + } + break; + } + + // Entry is strictly older than survivor. Delete it. + cur.delete_current()?; + deleted_count += 1; + + // MDBX delete_current() automatically advances the cursor to the next item. + // We check if the next item is still the same key. + match cur.current() { + Ok(Some((k, v))) => { + if k != key { + break; // Moved past the key + } + entry = v; + } + _ => break, // End of table or error + } + } + } + } + Ok(deleted_count) + } + + /// Collect versioned history over `block_range` using `BlockChangeSet`. + fn collect_history_ranged( + &self, + block_range: impl RangeBounds, + ) -> OpProofsStorageResult { + let mut history = HistoryDeleteBatch::default(); + let mut change_set_cursor = self.tx.cursor_read::()?; + let mut walker = change_set_cursor.walk_range(block_range)?; + + while let Some(Ok((block_number, change_set))) = walker.next() { + // Push (key, subkey=block_number) pairs + history + .account_trie + .extend(change_set.account_trie_keys.into_iter().map(|k| (k, block_number))); + history + .storage_trie + .extend(change_set.storage_trie_keys.into_iter().map(|k| (k, block_number))); + history + .hashed_account + .extend(change_set.hashed_account_keys.into_iter().map(|k| (k, block_number))); + history + .hashed_storage + .extend(change_set.hashed_storage_keys.into_iter().map(|k| (k, block_number))); + } + + // Sorting by tuple sorts by key first, then by block_number. + history.account_trie.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); + history.storage_trie.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); + history.hashed_account.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); + history.hashed_storage.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); + + Ok(history) + } + + /// Delete versioned history over `block_range` using history batch. + fn delete_history_ranged( + &self, + block_range: impl RangeBounds, + history: HistoryDeleteBatch, + ) -> OpProofsStorageResult { + let mut change_set_cursor = self.tx.cursor_write::()?; + let mut walker = change_set_cursor.walk_range(block_range)?; + + while let Some(Ok((_, _))) = walker.next() { + walker.delete_current()?; + } + + // Delete using the simplified API: iterator of (key, subkey) + self.delete_dup_sorted::(history.clone().account_trie)?; + self.delete_dup_sorted::(history.clone().storage_trie)?; + self.delete_dup_sorted::(history.clone().hashed_account)?; + self.delete_dup_sorted::(history.clone().hashed_storage)?; + + Ok(WriteCounts { + account_trie_updates_written_total: history.account_trie.len() as u64, + storage_trie_updates_written_total: history.storage_trie.len() as u64, + hashed_accounts_written_total: history.hashed_account.len() as u64, + hashed_storages_written_total: history.hashed_storage.len() as u64, + }) + } + + /// Write trie/state history for `block_number` from `block_state_diff`. + fn store_trie_updates_for_block( + &self, + block_number: u64, + block_state_diff: BlockStateDiff, + append_mode: bool, + ) -> OpProofsStorageResult { + let BlockStateDiff { sorted_trie_updates, sorted_post_state } = block_state_diff; + + let storage_trie_len = sorted_trie_updates.storage_tries_ref().len(); + let hashed_storage_len = sorted_post_state.storages.len(); + + let account_trie_keys = self.persist_history_batch( + block_number, + sorted_trie_updates.account_nodes_ref().iter().cloned(), + append_mode, + )?; + let hashed_account_keys = self.persist_history_batch( + block_number, + sorted_post_state.accounts.iter().copied(), + append_mode, + )?; + + let mut storage_trie_keys = Vec::::with_capacity(storage_trie_len); + for (hashed_address, nodes) in sorted_trie_updates.storage_tries_ref() { + // Handle wiped - mark all storage trie as deleted at the current block number + if nodes.is_deleted && append_mode { + let cursor = self.tx.cursor_dup_read::()?; + let mut ro = MdbxTrieCursor::new(cursor, block_number - 1, Some(*hashed_address)); + + // Merge old tombstones with new nodes in sorted key order. + // A BTreeMap ensures each path appears once and append_dup + // sees strictly increasing keys. + let mut merged: std::collections::BTreeMap> = + std::collections::BTreeMap::new(); + + // Old paths → tombstones + while let Some((path, _node)) = ro.next()? { + merged.insert(path, None); + } + + // New nodes override tombstones or add fresh entries + for (path, node) in nodes.storage_nodes_ref().iter().cloned() { + merged.insert(path, node); + } + + let mut cur = self.tx.cursor_dup_write::()?; + for (path, value) in merged { + let key = StorageTrieKey::new(*hashed_address, StoredNibbles::from(path)); + let vv = VersionedValue { block_number, value: MaybeDeleted(value) }; + cur.append_dup(key.clone(), vv)?; + storage_trie_keys.push(key); + } + + continue; + } + + let keys = self.persist_history_batch( + block_number, + nodes + .storage_nodes_ref() + .iter() + .cloned() + .map(|(path, node)| (*hashed_address, path, node)), + append_mode, + )?; + storage_trie_keys.extend(keys); + } + + let mut hashed_storage_keys = Vec::::with_capacity(hashed_storage_len); + for (hashed_address, storage) in sorted_post_state.storages { + if append_mode && storage.is_wiped() { + let cursor = self.tx.cursor_dup_read::()?; + let mut ro = MdbxStorageCursor::new(cursor, block_number - 1, hashed_address); + + // Merge old tombstones with new slot values in sorted key + // order. A BTreeMap ensures each slot appears once and + // append_dup sees strictly increasing keys. + let mut merged: std::collections::BTreeMap> = + std::collections::BTreeMap::new(); + + // Old slots → tombstones + while let Some((slot, _val)) = ro.next()? { + merged.insert(slot, None); + } + + // New slots override tombstones or add fresh entries + for (slot, val) in storage.storage_slots_ref() { + merged.insert(*slot, Some(StorageValue(*val))); + } + + let mut cur = self.tx.cursor_dup_write::()?; + for (slot, value) in merged { + let key = HashedStorageKey::new(hashed_address, slot); + let vv = VersionedValue { block_number, value: MaybeDeleted(value) }; + cur.append_dup(key.clone(), vv)?; + hashed_storage_keys.push(key); + } + + continue; + } + let keys = self.persist_history_batch( + block_number, + storage + .storage_slots_ref() + .iter() + .map(|(key, val)| (hashed_address, *key, Some(StorageValue(*val)))), + append_mode, + )?; + hashed_storage_keys.extend(keys); + } + + Ok(ChangeSet { + account_trie_keys, + storage_trie_keys, + hashed_account_keys, + hashed_storage_keys, + }) + } + + fn store_trie_updates_append_only_inner( + &self, + block_ref: BlockWithParent, + block_state_diff: BlockStateDiff, + ) -> OpProofsStorageResult { + let block_number = block_ref.block.number; + + let latest_block_hash = self.get_block_number_hash_inner(ProofWindowKey::LatestBlock)?.hash; + + if latest_block_hash != block_ref.parent { + return Err(OpProofsStorageError::OutOfOrder { + block_number, + parent_block_hash: block_ref.parent, + latest_block_hash, + }); + } + + let change_set = + &self.store_trie_updates_for_block(block_number, block_state_diff, true)?; + + let mut change_set_cursor = self.tx.cursor_write::()?; + change_set_cursor.append(block_number, change_set)?; + + self.set_latest_block_number_inner(block_number, block_ref.block.hash)?; + + Ok(WriteCounts { + account_trie_updates_written_total: change_set.account_trie_keys.len() as u64, + storage_trie_updates_written_total: change_set.storage_trie_keys.len() as u64, + hashed_accounts_written_total: change_set.hashed_account_keys.len() as u64, + hashed_storages_written_total: change_set.hashed_storage_keys.len() as u64, + }) + } + + fn get_initial_state_anchor_inner(&self) -> OpProofsStorageResult> { + let mut cur = self.tx.cursor_read::()?; + Ok(cur.seek_exact(ProofWindowKey::InitialStateAnchor)?.map(|(_k, v)| v.into())) + } + + fn get_latest_key_inner(&self) -> OpProofsStorageResult> + where + T: Table, + { + let mut cursor = self.tx.cursor_read::()?; + Ok(cursor.last()?.map(|(k, _)| k)) + } +} + +impl OpProofsProviderRO for MdbxProofsProvider { + type StorageTrieCursor<'tx> + = MdbxTrieCursor> + where + Self: 'tx, + TX: 'tx; + + type AccountTrieCursor<'tx> + = MdbxTrieCursor> + where + Self: 'tx, + TX: 'tx; + + type StorageCursor<'tx> + = MdbxStorageCursor> + where + Self: 'tx, + TX: 'tx; + + type AccountHashedCursor<'tx> + = MdbxAccountCursor> + where + Self: 'tx, + TX: 'tx; + + fn get_earliest_block(&self) -> OpProofsStorageResult { + self.get_block_number_hash_inner(ProofWindowKey::EarliestBlock) + } + + fn get_latest_block(&self) -> OpProofsStorageResult { + self.get_block_number_hash_inner(ProofWindowKey::LatestBlock) + } + + fn get_proof_window(&self) -> OpProofsStorageResult { + let mut cursor = self.tx.cursor_read::()?; + let earliest = cursor + .seek_exact(ProofWindowKey::EarliestBlock)? + .map(|(_, val)| NumHash::new(val.number(), *val.hash())) + .ok_or(NoBlocksFound)?; + let latest = cursor + .seek_exact(ProofWindowKey::LatestBlock)? + .map(|(_, val)| NumHash::new(val.number(), *val.hash())) + .ok_or(NoBlocksFound)?; + Ok(ProofWindowRange { earliest, latest }) + } + + fn storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.tx.cursor_dup_read::()?; + Ok(MdbxTrieCursor::new(cursor, max_block_number, Some(hashed_address))) + } + + fn account_trie_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.tx.cursor_dup_read::()?; + Ok(MdbxTrieCursor::new(cursor, max_block_number, None)) + } + + fn storage_hashed_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.tx.cursor_dup_read::()?; + Ok(MdbxStorageCursor::new(cursor, max_block_number, hashed_address)) + } + + fn account_hashed_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.tx.cursor_dup_read::()?; + Ok(MdbxAccountCursor::new(cursor, max_block_number)) + } + + fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { + let mut change_set_cursor = self.tx.cursor_read::()?; + let (_, change_set) = change_set_cursor + .seek_exact(block_number)? + .ok_or(OpProofsStorageError::NoChangeSetForBlock(block_number))?; + + let mut account_trie_cursor = self.tx.cursor_dup_read::()?; + let mut storage_trie_cursor = self.tx.cursor_dup_read::()?; + let mut hashed_account_cursor = self.tx.cursor_dup_read::()?; + let mut hashed_storage_cursor = self.tx.cursor_dup_read::()?; + + let mut trie_updates = TrieUpdates::default(); + for key in change_set.account_trie_keys { + let entry = match account_trie_cursor.seek_by_key_subkey(key.clone(), block_number)? { + Some(v) if v.block_number == block_number => v.value.0, + _ => { + return Err(OpProofsStorageError::MissingAccountTrieHistory( + key.0, + block_number, + )); + } + }; + + if let Some(value) = entry { + trie_updates.account_nodes.insert(key.0, value); + } else { + trie_updates.removed_nodes.insert(key.0); + } + } + + for key in change_set.storage_trie_keys { + let entry = match storage_trie_cursor.seek_by_key_subkey(key.clone(), block_number)? { + Some(v) if v.block_number == block_number => v.value.0, + _ => { + return Err(OpProofsStorageError::MissingStorageTrieHistory( + key.hashed_address, + key.path.0, + block_number, + )); + } + }; + + let stu = trie_updates + .storage_tries + .entry(key.hashed_address) + .or_insert_with(StorageTrieUpdates::default); + + if let Some(value) = entry { + stu.storage_nodes.insert(key.path.0, value); + } else { + stu.removed_nodes.insert(key.path.0); + } + } + + let mut post_state = HashedPostState::with_capacity(change_set.hashed_account_keys.len()); + for key in change_set.hashed_account_keys { + let entry = match hashed_account_cursor.seek_by_key_subkey(key, block_number)? { + Some(v) if v.block_number == block_number => v.value.0, + _ => { + return Err(OpProofsStorageError::MissingHashedAccountHistory( + key, + block_number, + )); + } + }; + + post_state.accounts.insert(key, entry); + } + + for key in change_set.hashed_storage_keys { + let entry = match hashed_storage_cursor.seek_by_key_subkey(key.clone(), block_number)? { + Some(v) if v.block_number == block_number => v.value.0, + _ => { + return Err(OpProofsStorageError::MissingHashedStorageHistory { + hashed_address: key.hashed_address, + hashed_storage_key: key.hashed_storage_key, + block_number, + }); + } + }; + + let hs = post_state.storages.entry(key.hashed_address).or_default(); + + if let Some(value) = entry { + hs.storage.insert(key.hashed_storage_key, value.0); + } else { + hs.storage.insert(key.hashed_storage_key, U256::ZERO); + } + } + + Ok(BlockStateDiff { + sorted_trie_updates: trie_updates.into_sorted(), + sorted_post_state: post_state.into_sorted(), + }) + } +} + +impl OpProofsProviderRw + for MdbxProofsProvider +{ + fn store_trie_updates( + &self, + block_ref: BlockWithParent, + block_state_diff: BlockStateDiff, + ) -> OpProofsStorageResult { + self.store_trie_updates_append_only_inner(block_ref, block_state_diff) + } + + fn store_trie_updates_batch( + &self, + updates: Vec<(BlockWithParent, BlockStateDiff)>, + ) -> OpProofsStorageResult { + let mut total_counts = WriteCounts::default(); + for (block_ref, block_state_diff) in updates { + let counts = self.store_trie_updates_append_only_inner(block_ref, block_state_diff)?; + + total_counts.account_trie_updates_written_total += + counts.account_trie_updates_written_total; + total_counts.storage_trie_updates_written_total += + counts.storage_trie_updates_written_total; + total_counts.hashed_accounts_written_total += counts.hashed_accounts_written_total; + total_counts.hashed_storages_written_total += counts.hashed_storages_written_total; + } + + Ok(total_counts) + } + + fn prune_earliest_state( + &self, + new_earliest_block_ref: BlockWithParent, + ) -> OpProofsStorageResult { + let target_block = new_earliest_block_ref.block.number; + + let plan = self.calculate_prune_plan(target_block)?; + let Some(plan) = plan else { + return Ok(WriteCounts::default()); + }; + + let acc_deleted = + self.prune_history_preceding::(plan.acc_survivors)?; + + let st_deleted = + self.prune_history_preceding::(plan.storage_survivors)?; + + let ha_deleted = + self.prune_history_preceding::(plan.hashed_acc_survivors)?; + + let hs_deleted = + self.prune_history_preceding::(plan.hashed_storage_survivors)?; + + let counts = WriteCounts { + account_trie_updates_written_total: acc_deleted, + storage_trie_updates_written_total: st_deleted, + hashed_accounts_written_total: ha_deleted, + hashed_storages_written_total: hs_deleted, + }; + + let range = (plan.earliest_block + 1)..=target_block; + let mut cs_cursor = self.tx.cursor_write::()?; + let mut walker = cs_cursor.walk_range(range)?; + while walker.next().is_some() { + walker.delete_current()?; + } + + self.set_earliest_block_number_inner(target_block, new_earliest_block_ref.block.hash)?; + + Ok(counts) + } + + fn unwind_history(&self, to: BlockWithParent) -> OpProofsStorageResult<()> { + let history_to_delete = self.collect_history_ranged(to.block.number..)?; + + let proof_window = self.get_proof_window()?; + + if to.block.number > proof_window.latest.number { + return Ok(()); + } + + if to.block.number <= proof_window.earliest.number { + return Err(OpProofsStorageError::UnwindBeyondEarliest { + unwind_block_number: to.block.number, + earliest_block_number: proof_window.earliest.number, + }); + } + + self.delete_history_ranged(to.block.number.., history_to_delete)?; + + let new_latest_block = BlockNumberHash::new(to.block.number.saturating_sub(1), to.parent); + + self.set_latest_block_number_inner(new_latest_block.number(), *new_latest_block.hash())?; + + Ok(()) + } + + fn replace_updates( + &self, + latest_common_block: BlockNumHash, + mut blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)>, + ) -> OpProofsStorageResult<()> { + let proof_window = self.get_proof_window()?; + + if latest_common_block.number <= proof_window.earliest.number || + latest_common_block.number > proof_window.latest.number + { + return Err(OpProofsStorageError::ReorgBaseOutOfWindow { + block_number: latest_common_block.number, + earliest_block_number: proof_window.earliest.number, + latest_block_number: proof_window.latest.number, + }); + } + + blocks_to_add.sort_unstable_by_key(|(bwp, _)| bwp.block.number); + + let history_to_delete = self.collect_history_ranged(latest_common_block.number + 1..)?; + self.delete_history_ranged(latest_common_block.number + 1.., history_to_delete)?; + + self.set_latest_block_number_inner(latest_common_block.number, latest_common_block.hash)?; + + for (block_with_parent, diff) in blocks_to_add { + self.store_trie_updates_append_only_inner(block_with_parent, diff)?; + } + Ok(()) + } + + fn commit(self) -> OpProofsStorageResult<()> { + self.tx.commit()?; + Ok(()) + } +} + +impl OpProofsInitProvider + for MdbxProofsProvider +{ + fn initial_state_anchor(&self) -> OpProofsStorageResult { + let Some(block) = self.get_initial_state_anchor_inner()? else { + return Ok(InitialStateAnchor::default()); + }; + + let status = match self.get_block_number_hash_inner(ProofWindowKey::EarliestBlock) { + Ok(_) => InitialStateStatus::Completed, + Err(NoBlocksFound) => InitialStateStatus::InProgress, + Err(err) => return Err(err), + }; + + Ok(InitialStateAnchor { + block: Some(block), + status, + latest_account_trie_key: self.get_latest_key_inner::()?, + latest_storage_trie_key: self.get_latest_key_inner::()?, + latest_hashed_account_key: self.get_latest_key_inner::()?, + latest_hashed_storage_key: self.get_latest_key_inner::()?, + }) + } + + fn set_initial_state_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()> { + let mut cur = self.tx.cursor_write::()?; + cur.insert(ProofWindowKey::InitialStateAnchor, &anchor.into())?; + Ok(()) + } + + fn store_account_branches( + &self, + account_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()> { + if account_nodes.is_empty() { + return Ok(()); + } + self.persist_history_batch(0, account_nodes.into_iter(), true)?; + Ok(()) + } + + fn store_storage_branches( + &self, + hashed_address: B256, + storage_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()> { + if storage_nodes.is_empty() { + return Ok(()); + } + self.persist_history_batch( + 0, + storage_nodes.into_iter().map(|(key, val)| (hashed_address, key, val)), + true, + )?; + Ok(()) + } + + fn store_hashed_accounts( + &self, + accounts: Vec<(B256, Option)>, + ) -> OpProofsStorageResult<()> { + if accounts.is_empty() { + return Ok(()); + } + self.persist_history_batch(0, accounts.into_iter(), true)?; + Ok(()) + } + + fn store_hashed_storages( + &self, + hashed_address: B256, + storages: Vec<(B256, U256)>, + ) -> OpProofsStorageResult<()> { + if storages.is_empty() { + return Ok(()); + } + self.persist_history_batch( + 0, + storages.into_iter().map(|(key, val)| (hashed_address, key, Some(StorageValue(val)))), + true, + )?; + Ok(()) + } + + fn commit_initial_state(&self) -> OpProofsStorageResult { + let anchor = self.get_initial_state_anchor_inner()?.ok_or(NoBlocksFound)?; + self.set_earliest_block_number_inner(anchor.number, anchor.hash)?; + self.set_latest_block_number_inner(anchor.number, anchor.hash)?; + Ok(anchor) + } + + fn commit(self) -> OpProofsStorageResult<()> { + self.tx.commit()?; + Ok(()) + } +} + +impl OpProofsStore for MdbxProofsStorage { + type ProviderRO<'a> = Arc::TX>>; + type ProviderRw<'a> = MdbxProofsProvider<::TXMut>; + type Initializer<'a> = MdbxProofsProvider<::TXMut>; + + fn provider_ro<'a>(&'a self) -> OpProofsStorageResult> { + Ok(Arc::new(MdbxProofsProvider::new(self.env.tx()?))) + } + + fn provider_rw<'a>(&'a self) -> OpProofsStorageResult> { + Ok(MdbxProofsProvider::new(self.env.tx_mut()?)) + } + + fn initialization_provider<'a>(&'a self) -> OpProofsStorageResult> { + Ok(MdbxProofsProvider::new(self.env.tx_mut()?)) + } +} + +/// This implementation is copied from the +/// [`DatabaseMetrics`](reth_db::database_metrics::DatabaseMetrics) implementation for +/// [`DatabaseEnv`]. As the implementation hard-coded the table name, we need to reimplement it. +#[cfg(feature = "metrics")] +impl reth_db::database_metrics::DatabaseMetrics for MdbxProofsStorage { + fn report_metrics(&self) { + for (name, value, labels) in self.gauge_metrics() { + gauge!(name, labels).set(value); + } + } + + fn gauge_metrics(&self) -> Vec<(&'static str, f64, Vec

{ + provider: P, + metrics: Arc, +} + +impl OpProofsProviderRO for OpProofsProviderROWithMetrics

{ + type StorageTrieCursor<'tx> + = OpProofsTrieCursorWithMetrics> + where + Self: 'tx; + type AccountTrieCursor<'tx> + = OpProofsTrieCursorWithMetrics> + where + Self: 'tx; + type StorageCursor<'tx> + = OpProofsHashedCursorWithMetrics> + where + Self: 'tx; + type AccountHashedCursor<'tx> + = OpProofsHashedCursorWithMetrics> + where + Self: 'tx; + + #[inline] + fn get_earliest_block(&self) -> OpProofsStorageResult { + let result = self.provider.get_earliest_block()?; + self.metrics.proof_window.earliest.set(result.number as f64); + Ok(result) + } + + #[inline] + fn get_latest_block(&self) -> OpProofsStorageResult { + let result = self.provider.get_latest_block()?; + self.metrics.proof_window.latest.set(result.number as f64); + Ok(result) + } + + #[inline] + fn get_proof_window(&self) -> OpProofsStorageResult { + let result = self.provider.get_proof_window()?; + self.metrics.proof_window.earliest.set(result.earliest.number as f64); + self.metrics.proof_window.latest.set(result.latest.number as f64); + Ok(result) + } + + #[inline] + fn storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.storage_trie_cursor(hashed_address, max_block_number)?; + Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn account_trie_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.account_trie_cursor(max_block_number)?; + Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn storage_hashed_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.storage_hashed_cursor(hashed_address, max_block_number)?; + Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn account_hashed_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.account_hashed_cursor(max_block_number)?; + Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { + self.provider.fetch_trie_updates(block_number) + } +} + +/// Wrapper for [`OpProofsProviderRw`] that records metrics. +#[derive(Debug, Constructor, Clone)] +pub struct OpProofsProviderRwWithMetrics

{ + provider: P, + metrics: Arc, +} + +impl OpProofsProviderRO for OpProofsProviderRwWithMetrics

{ + type StorageTrieCursor<'tx> + = OpProofsTrieCursorWithMetrics> + where + Self: 'tx; + type AccountTrieCursor<'tx> + = OpProofsTrieCursorWithMetrics> + where + Self: 'tx; + type StorageCursor<'tx> + = OpProofsHashedCursorWithMetrics> + where + Self: 'tx; + type AccountHashedCursor<'tx> + = OpProofsHashedCursorWithMetrics> + where + Self: 'tx; + + #[inline] + fn get_earliest_block(&self) -> OpProofsStorageResult { + let result = self.provider.get_earliest_block()?; + self.metrics.proof_window.earliest.set(result.number as f64); + Ok(result) + } + + #[inline] + fn get_latest_block(&self) -> OpProofsStorageResult { + let result = self.provider.get_latest_block()?; + self.metrics.proof_window.latest.set(result.number as f64); + Ok(result) + } + + #[inline] + fn get_proof_window(&self) -> OpProofsStorageResult { + let result = self.provider.get_proof_window()?; + self.metrics.proof_window.earliest.set(result.earliest.number as f64); + self.metrics.proof_window.latest.set(result.latest.number as f64); + Ok(result) + } + + #[inline] + fn storage_trie_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.storage_trie_cursor(hashed_address, max_block_number)?; + Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn account_trie_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.account_trie_cursor(max_block_number)?; + Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn storage_hashed_cursor<'tx>( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.storage_hashed_cursor(hashed_address, max_block_number)?; + Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn account_hashed_cursor<'tx>( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult> { + let cursor = self.provider.account_hashed_cursor(max_block_number)?; + Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) + } + + #[inline] + fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { + self.provider.fetch_trie_updates(block_number) + } +} + +impl OpProofsProviderRw for OpProofsProviderRwWithMetrics

{ + #[inline] + fn store_trie_updates( + &self, + block_ref: BlockWithParent, + block_state_diff: BlockStateDiff, + ) -> OpProofsStorageResult { + let result = self.provider.store_trie_updates(block_ref, block_state_diff)?; + self.metrics.proof_window.latest.set(block_ref.block.number as f64); + Ok(result) + } + + #[inline] + fn store_trie_updates_batch( + &self, + updates: Vec<(BlockWithParent, BlockStateDiff)>, + ) -> OpProofsStorageResult { + let result = self.provider.store_trie_updates_batch(updates.clone())?; + if let Some((latest_block_ref, _)) = updates.last() { + self.metrics.proof_window.latest.set(latest_block_ref.block.number as f64); + } + Ok(result) + } + + #[inline] + fn prune_earliest_state( + &self, + new_earliest_block_ref: BlockWithParent, + ) -> OpProofsStorageResult { + self.metrics.proof_window.earliest.set(new_earliest_block_ref.block.number as f64); + self.provider.prune_earliest_state(new_earliest_block_ref) + } + + #[inline] + fn unwind_history(&self, to: BlockWithParent) -> OpProofsStorageResult<()> { + self.provider.unwind_history(to) + } + + #[inline] + fn replace_updates( + &self, + latest_common_block: BlockNumHash, + blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)>, + ) -> OpProofsStorageResult<()> { + self.provider.replace_updates(latest_common_block, blocks_to_add) + } + + #[inline] + fn commit(self) -> OpProofsStorageResult<()> { + self.provider.commit() + } +} + +/// Wrapper for [`OpProofsInitProvider`] that records metrics. +#[derive(Debug, Constructor)] +pub struct OpProofsInitProviderWithMetrics

{ + provider: P, + metrics: Arc, +} + +impl OpProofsInitProvider for OpProofsInitProviderWithMetrics

{ + #[inline] + fn initial_state_anchor(&self) -> OpProofsStorageResult { + self.provider.initial_state_anchor() + } + + #[inline] + fn set_initial_state_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()> { + self.provider.set_initial_state_anchor(anchor) + } + + #[inline] + fn store_account_branches( + &self, + account_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()> { + let count = account_nodes.len(); + let start = Instant::now(); + let result = self.provider.store_account_branches(account_nodes); + let duration = start.elapsed(); + + // Record per-item duration + if count > 0 { + self.metrics.record_duration_per_item( + StorageOperation::StoreAccountBranch, + duration, + count, + ); + } + + result + } + + #[inline] + fn store_storage_branches( + &self, + hashed_address: B256, + storage_nodes: Vec<(Nibbles, Option)>, + ) -> OpProofsStorageResult<()> { + let count = storage_nodes.len(); + let start = Instant::now(); + let result = self.provider.store_storage_branches(hashed_address, storage_nodes); + let duration = start.elapsed(); + + // Record per-item duration + if count > 0 { + self.metrics.record_duration_per_item( + StorageOperation::StoreStorageBranch, + duration, + count, + ); + } + + result + } + + #[inline] + fn store_hashed_accounts( + &self, + accounts: Vec<(B256, Option)>, + ) -> OpProofsStorageResult<()> { + let count = accounts.len(); + let start = Instant::now(); + let result = self.provider.store_hashed_accounts(accounts); + let duration = start.elapsed(); + + // Record per-item duration + if count > 0 { + self.metrics.record_duration_per_item( + StorageOperation::StoreHashedAccount, + duration, + count, + ); + } + + result + } + + #[inline] + fn store_hashed_storages( + &self, + hashed_address: B256, + storages: Vec<(B256, U256)>, + ) -> OpProofsStorageResult<()> { + let count = storages.len(); + let start = Instant::now(); + let result = self.provider.store_hashed_storages(hashed_address, storages); + let duration = start.elapsed(); + + // Record per-item duration + if count > 0 { + self.metrics.record_duration_per_item( + StorageOperation::StoreHashedStorage, + duration, + count, + ); + } + + result + } + + #[inline] + fn commit_initial_state(&self) -> OpProofsStorageResult { + let block = self.provider.commit_initial_state()?; + self.metrics.proof_window.earliest.set(block.number as f64); + Ok(block) + } + + #[inline] + fn commit(self) -> OpProofsStorageResult<()> { + self.provider.commit() + } +} + +impl From for OpProofsStoreWithMetrics +where + S: OpProofsStore + Clone + 'static, +{ + fn from(storage: S) -> Self { + Self::new(storage) + } +} diff --git a/vendor/reth-optimism-trie/src/proof.rs b/vendor/reth-optimism-trie/src/proof.rs new file mode 100644 index 0000000..075ac6e --- /dev/null +++ b/vendor/reth-optimism-trie/src/proof.rs @@ -0,0 +1,403 @@ +//! Provides proof operation implementations for [`crate::OpProofsStorage`]. + +use crate::{ + OpProofsHashedAccountCursorFactory, OpProofsTrieCursorFactory, api::OpProofsProviderRO, +}; +use alloy_primitives::{ + Address, B256, Bytes, keccak256, + map::{B256Map, HashMap}, +}; +use reth_execution_errors::{StateProofError, StateRootError, StorageRootError, TrieWitnessError}; +use reth_trie::{ + StateRoot, StorageRoot, TrieType, + hashed_cursor::HashedPostStateCursorFactory, + metrics::TrieRootMetrics, + proof::{self, Proof}, + trie_cursor::InMemoryTrieCursorFactory, + witness::TrieWitness, +}; +use reth_trie_common::{ + AccountProof, HashedPostState, HashedPostStateSorted, HashedStorage, MultiProof, + MultiProofTargets, StorageMultiProof, StorageProof, TrieInput, updates::TrieUpdates, +}; + +/// Extends [`Proof`] with operations specific for working with [`crate::OpProofsStorage`]. +pub trait DatabaseProof

{ + /// Creates a new `DatabaseProof` instance from external storage. + fn from_provider(provider: P, block_number: u64) -> Self; + + /// Generates the state proof for target account based on [`TrieInput`]. + fn overlay_account_proof( + provider: P, + block_number: u64, + input: TrieInput, + address: Address, + slots: &[B256], + ) -> Result; + + /// Generates the state [`MultiProof`] for target hashed account and storage keys. + fn overlay_multiproof( + provider: P, + block_number: u64, + input: TrieInput, + targets: MultiProofTargets, + ) -> Result; +} + +impl

DatabaseProof

+ for Proof, OpProofsHashedAccountCursorFactory

> +where + P: OpProofsProviderRO + Clone, +{ + /// Create a new [`Proof`] instance from [`crate::OpProofsStorage`]. + fn from_provider(provider: P, block_number: u64) -> Self { + Self::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + OpProofsHashedAccountCursorFactory::new(provider, block_number), + ) + } + + /// Generates the state proof for target account based on [`TrieInput`]. + fn overlay_account_proof( + provider: P, + block_number: u64, + input: TrieInput, + address: Address, + slots: &[B256], + ) -> Result { + let nodes_sorted = input.nodes.into_sorted(); + let state_sorted = input.state.into_sorted(); + Self::from_provider(provider.clone(), block_number) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + &nodes_sorted, + )) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + )) + .with_prefix_sets_mut(input.prefix_sets) + .account_proof(address, slots) + } + + /// Generates the state [`MultiProof`] for target hashed account and storage keys. + fn overlay_multiproof( + provider: P, + block_number: u64, + input: TrieInput, + targets: MultiProofTargets, + ) -> Result { + let nodes_sorted = input.nodes.into_sorted(); + let state_sorted = input.state.into_sorted(); + Self::from_provider(provider.clone(), block_number) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + &nodes_sorted, + )) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + )) + .with_prefix_sets_mut(input.prefix_sets) + .multiproof(targets) + } +} + +/// Extends [`StorageProof`] with operations specific for working with [`crate::OpProofsStorage`]. +pub trait DatabaseStorageProof

{ + /// Create a new [`StorageProof`] from [`crate::OpProofsStorage`] and account address. + fn from_provider(provider: P, block_number: u64, address: Address) -> Self; + + /// Generates the storage proof for target slot based on [`TrieInput`]. + fn overlay_storage_proof( + provider: P, + block_number: u64, + address: Address, + slot: B256, + storage: HashedStorage, + ) -> Result; + + /// Generates the storage multiproof for target slots based on [`TrieInput`]. + fn overlay_storage_multiproof( + provider: P, + block_number: u64, + address: Address, + slots: &[B256], + storage: HashedStorage, + ) -> Result; +} + +impl

DatabaseStorageProof

+ for proof::StorageProof< + 'static, + OpProofsTrieCursorFactory

, + OpProofsHashedAccountCursorFactory

, + > +where + P: OpProofsProviderRO + Clone, +{ + /// Create a new [`StorageProof`] from [`crate::OpProofsStorage`] and account address. + fn from_provider(provider: P, block_number: u64, address: Address) -> Self { + Self::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + OpProofsHashedAccountCursorFactory::new(provider, block_number), + address, + ) + } + + fn overlay_storage_proof( + provider: P, + block_number: u64, + address: Address, + slot: B256, + hashed_storage: HashedStorage, + ) -> Result { + let hashed_address = keccak256(address); + let prefix_set = hashed_storage.construct_prefix_set(); + let state_sorted = HashedPostStateSorted::new( + Default::default(), + HashMap::from_iter([(hashed_address, hashed_storage.into_sorted())]), + ); + Self::from_provider(provider.clone(), block_number, address) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + )) + .with_prefix_set_mut(prefix_set) + .storage_proof(slot) + } + + fn overlay_storage_multiproof( + provider: P, + block_number: u64, + address: Address, + slots: &[B256], + hashed_storage: HashedStorage, + ) -> Result { + let hashed_address = keccak256(address); + let targets = slots.iter().map(keccak256).collect(); + let prefix_set = hashed_storage.construct_prefix_set(); + let state_sorted = HashedPostStateSorted::new( + Default::default(), + HashMap::from_iter([(hashed_address, hashed_storage.into_sorted())]), + ); + Self::from_provider(provider.clone(), block_number, address) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + )) + .with_prefix_set_mut(prefix_set) + .storage_multiproof(targets) + } +} + +/// Extends [`StateRoot`] with operations specific for working with [`crate::OpProofsStorage`]. +pub trait DatabaseStateRoot

: Sized { + /// Calculate the state root for this [`HashedPostState`]. + /// Internally, this method retrieves prefixsets and uses them + /// to calculate incremental state root. + /// + /// # Returns + /// + /// The state root for this [`HashedPostState`]. + fn overlay_root( + provider: P, + block_number: u64, + post_state: HashedPostState, + ) -> Result; + + /// Calculates the state root for this [`HashedPostState`] and returns it alongside trie + /// updates. See [`Self::overlay_root`] for more info. + fn overlay_root_with_updates( + provider: P, + block_number: u64, + post_state: HashedPostState, + ) -> Result<(B256, TrieUpdates), StateRootError>; + + /// Calculates the state root for provided [`HashedPostState`] using cached intermediate nodes. + fn overlay_root_from_nodes( + provider: P, + block_number: u64, + input: TrieInput, + ) -> Result; + + /// Calculates the state root and trie updates for provided [`HashedPostState`] using + /// cached intermediate nodes. + fn overlay_root_from_nodes_with_updates( + provider: P, + block_number: u64, + input: TrieInput, + ) -> Result<(B256, TrieUpdates), StateRootError>; +} + +impl

DatabaseStateRoot

+ for StateRoot, OpProofsHashedAccountCursorFactory

> +where + P: OpProofsProviderRO + Clone, +{ + fn overlay_root( + provider: P, + block_number: u64, + post_state: HashedPostState, + ) -> Result { + let prefix_sets = post_state.construct_prefix_sets().freeze(); + let state_sorted = post_state.into_sorted(); + StateRoot::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + ), + ) + .with_prefix_sets(prefix_sets) + .root() + } + + fn overlay_root_with_updates( + provider: P, + block_number: u64, + post_state: HashedPostState, + ) -> Result<(B256, TrieUpdates), StateRootError> { + let prefix_sets = post_state.construct_prefix_sets().freeze(); + let state_sorted = post_state.into_sorted(); + StateRoot::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + ), + ) + .with_prefix_sets(prefix_sets) + .root_with_updates() + } + + fn overlay_root_from_nodes( + provider: P, + block_number: u64, + input: TrieInput, + ) -> Result { + let state_sorted = input.state.into_sorted(); + let nodes_sorted = input.nodes.into_sorted(); + StateRoot::new( + InMemoryTrieCursorFactory::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + &nodes_sorted, + ), + HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + ), + ) + .with_prefix_sets(input.prefix_sets.freeze()) + .root() + } + + fn overlay_root_from_nodes_with_updates( + provider: P, + block_number: u64, + input: TrieInput, + ) -> Result<(B256, TrieUpdates), StateRootError> { + let state_sorted = input.state.into_sorted(); + let nodes_sorted = input.nodes.into_sorted(); + StateRoot::new( + InMemoryTrieCursorFactory::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + &nodes_sorted, + ), + HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + ), + ) + .with_prefix_sets(input.prefix_sets.freeze()) + .root_with_updates() + } +} + +/// Extends [`StorageRoot`] with operations specific for working with [`crate::OpProofsStorage`]. +pub trait DatabaseStorageRoot

{ + /// Calculates the storage root for provided [`HashedStorage`]. + fn overlay_root( + provider: P, + block_number: u64, + address: Address, + hashed_storage: HashedStorage, + ) -> Result; +} + +impl

DatabaseStorageRoot

+ for StorageRoot, OpProofsHashedAccountCursorFactory

> +where + P: OpProofsProviderRO + Clone, +{ + fn overlay_root( + provider: P, + block_number: u64, + address: Address, + hashed_storage: HashedStorage, + ) -> Result { + let prefix_set = hashed_storage.construct_prefix_set().freeze(); + let state_sorted = + HashedPostState::from_hashed_storage(keccak256(address), hashed_storage).into_sorted(); + StorageRoot::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + ), + address, + prefix_set, + TrieRootMetrics::new(TrieType::Custom("op_historical_proofs_storage")), + ) + .root() + } +} + +/// Extends [`TrieWitness`] with operations specific for working with [`crate::OpProofsStorage`]. +pub trait DatabaseTrieWitness

{ + /// Creates a new [`TrieWitness`] instance from [`crate::OpProofsStorage`]. + fn from_provider(provider: P, block_number: u64) -> Self; + + /// Generates the trie witness for the target state based on [`TrieInput`]. + fn overlay_witness( + provider: P, + block_number: u64, + input: TrieInput, + target: HashedPostState, + ) -> Result, TrieWitnessError>; +} + +impl

DatabaseTrieWitness

+ for TrieWitness, OpProofsHashedAccountCursorFactory

> +where + P: OpProofsProviderRO + Clone, +{ + fn from_provider(provider: P, block_number: u64) -> Self { + Self::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + OpProofsHashedAccountCursorFactory::new(provider, block_number), + ) + } + + fn overlay_witness( + provider: P, + block_number: u64, + input: TrieInput, + target: HashedPostState, + ) -> Result, TrieWitnessError> { + let nodes_sorted = input.nodes.into_sorted(); + let state_sorted = input.state.into_sorted(); + Self::from_provider(provider.clone(), block_number) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( + OpProofsTrieCursorFactory::new(provider.clone(), block_number), + &nodes_sorted, + )) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( + OpProofsHashedAccountCursorFactory::new(provider, block_number), + &state_sorted, + )) + .with_prefix_sets_mut(input.prefix_sets) + .always_include_root_node() + .compute(target) + } +} diff --git a/vendor/reth-optimism-trie/src/provider.rs b/vendor/reth-optimism-trie/src/provider.rs new file mode 100644 index 0000000..17af634 --- /dev/null +++ b/vendor/reth-optimism-trie/src/provider.rs @@ -0,0 +1,257 @@ +//! Provider for external proofs storage + +use crate::{ + OpProofsProviderRO, OpProofsStorageError, + proof::{ + DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, + DatabaseTrieWitness, + }, +}; +use alloy_primitives::keccak256; +use derive_more::Constructor; +use reth_primitives_traits::{Account, Bytecode}; +use reth_provider::{ + AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, ProviderError, + ProviderResult, StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, +}; +use reth_revm::{ + db::BundleState, + primitives::{Address, B256, Bytes, StorageValue, alloy_primitives::BlockNumber}, +}; +use reth_trie::{ + ExecutionWitnessMode, StateRoot, StorageRoot, + hashed_cursor::HashedCursor, + proof::{self, Proof}, + witness::TrieWitness, +}; +use reth_trie_common::{ + AccountProof, HashedPostState, HashedStorage, KeccakKeyHasher, MultiProof, MultiProofTargets, + StorageMultiProof, StorageProof, TrieInput, updates::TrieUpdates, +}; +use std::fmt::Debug; + +/// State provider for external proofs storage. +#[derive(Constructor)] +pub struct OpProofsStateProviderRef<'a, P> { + /// Historical state provider for non-state related tasks. + latest: Box, + + /// Storage provider for state lookups. + provider: P, + + /// Max block number that can be used for state lookups. + block_number: BlockNumber, +} + +impl<'a, P> Debug for OpProofsStateProviderRef<'a, P> +where + P: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OpProofsStateProviderRef") + .field("provider", &self.provider) + .field("block_number", &self.block_number) + .finish() + } +} + +impl From for ProviderError { + fn from(error: OpProofsStorageError) -> Self { + Self::other(error) + } +} + +impl<'a, P> BlockHashReader for OpProofsStateProviderRef<'a, P> { + fn block_hash(&self, number: BlockNumber) -> ProviderResult> { + self.latest.block_hash(number) + } + + fn canonical_hashes_range( + &self, + start: BlockNumber, + end: BlockNumber, + ) -> ProviderResult> { + self.latest.canonical_hashes_range(start, end) + } +} + +impl<'a, P> StateRootProvider for OpProofsStateProviderRef<'a, P> +where + P: OpProofsProviderRO + Clone, +{ + fn state_root(&self, state: HashedPostState) -> ProviderResult { + Ok(StateRoot::overlay_root(self.provider.clone(), self.block_number, state)?) + } + + fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult { + Ok(StateRoot::overlay_root_from_nodes(self.provider.clone(), self.block_number, input)?) + } + + fn state_root_with_updates( + &self, + state: HashedPostState, + ) -> ProviderResult<(B256, TrieUpdates)> { + Ok(StateRoot::overlay_root_with_updates(self.provider.clone(), self.block_number, state)?) + } + + fn state_root_from_nodes_with_updates( + &self, + input: TrieInput, + ) -> ProviderResult<(B256, TrieUpdates)> { + Ok(StateRoot::overlay_root_from_nodes_with_updates( + self.provider.clone(), + self.block_number, + input, + )?) + } +} + +impl<'a, P> StorageRootProvider for OpProofsStateProviderRef<'a, P> +where + P: OpProofsProviderRO + Clone, +{ + fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult { + StorageRoot::overlay_root(self.provider.clone(), self.block_number, address, storage) + .map_err(|err| ProviderError::Database(err.into())) + } + + fn storage_proof( + &self, + address: Address, + slot: B256, + storage: HashedStorage, + ) -> ProviderResult { + proof::StorageProof::overlay_storage_proof( + self.provider.clone(), + self.block_number, + address, + slot, + storage, + ) + .map_err(ProviderError::from) + } + + fn storage_multiproof( + &self, + address: Address, + slots: &[B256], + storage: HashedStorage, + ) -> ProviderResult { + proof::StorageProof::overlay_storage_multiproof( + self.provider.clone(), + self.block_number, + address, + slots, + storage, + ) + .map_err(ProviderError::from) + } +} + +impl<'a, P> StateProofProvider for OpProofsStateProviderRef<'a, P> +where + P: OpProofsProviderRO + Clone, +{ + fn proof( + &self, + input: TrieInput, + address: Address, + slots: &[B256], + ) -> ProviderResult { + Proof::overlay_account_proof( + self.provider.clone(), + self.block_number, + input, + address, + slots, + ) + .map_err(ProviderError::from) + } + + fn multiproof( + &self, + input: TrieInput, + targets: MultiProofTargets, + ) -> ProviderResult { + Proof::overlay_multiproof(self.provider.clone(), self.block_number, input, targets) + .map_err(ProviderError::from) + } + + fn witness( + &self, + input: TrieInput, + target: HashedPostState, + _mode: ExecutionWitnessMode, + ) -> ProviderResult> { + TrieWitness::overlay_witness(self.provider.clone(), self.block_number, input, target) + .map_err(ProviderError::from) + .map(|hm| hm.into_values().collect()) + } +} + +impl<'a, P> HashedPostStateProvider for OpProofsStateProviderRef<'a, P> { + fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState { + HashedPostState::from_bundle_state::(bundle_state.state()) + } +} + +impl<'a, P> AccountReader for OpProofsStateProviderRef<'a, P> +where + P: OpProofsProviderRO, +{ + fn basic_account(&self, address: &Address) -> ProviderResult> { + let hashed_key = keccak256(address.0); + Ok(self + .provider + .account_hashed_cursor(self.block_number) + .map_err(Into::::into)? + .seek(hashed_key) + .map_err(Into::::into)? + .and_then(|(key, account)| (key == hashed_key).then_some(account))) + } +} + +impl<'a, P> StateProvider for OpProofsStateProviderRef<'a, P> +where + P: OpProofsProviderRO + Clone, +{ + fn storage(&self, address: Address, storage_key: B256) -> ProviderResult> { + let hashed_key = keccak256(storage_key); + Ok(self + .provider + .storage_hashed_cursor(keccak256(address.0), self.block_number) + .map_err(Into::::into)? + .seek(hashed_key) + .map_err(Into::::into)? + .and_then(|(key, storage)| (key == hashed_key).then_some(storage))) + } +} + +impl<'a, P> BytecodeReader for OpProofsStateProviderRef<'a, P> { + fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { + self.latest.bytecode_by_hash(code_hash) + } +} + +#[cfg(all(test, not(feature = "metrics")))] +mod tests { + use super::*; + use crate::{InMemoryProofsStorage, api::OpProofsStore}; + use reth_provider::noop::NoopProvider; + + #[test] + fn test_op_proofs_state_provider_ref_debug() { + let latest: Box = Box::new(NoopProvider::default()); + let storage: crate::OpProofsStorage = InMemoryProofsStorage::new(); + // Create a provider from the store (in memory storage implements OpProofsStore) + let provider_ro = storage.provider_ro().unwrap(); + let block_number = 42u64; + + let provider = OpProofsStateProviderRef::new(latest, provider_ro, block_number); + + assert_eq!( + format!("{:?}", provider), + "OpProofsStateProviderRef { provider: InMemoryProofsProvider { inner: RwLock { data: InMemoryStorageInner { account_branches: {}, storage_branches: {}, hashed_accounts: {}, hashed_storages: {}, trie_updates: {}, post_states: {}, earliest_block: None, latest_block: None, anchor_block: None } } }, block_number: 42 }" + ); + } +} diff --git a/vendor/reth-optimism-trie/src/prune/error.rs b/vendor/reth-optimism-trie/src/prune/error.rs new file mode 100644 index 0000000..965def8 --- /dev/null +++ b/vendor/reth-optimism-trie/src/prune/error.rs @@ -0,0 +1,96 @@ +use crate::{OpProofsStorageError, api::WriteCounts}; +use reth_provider::ProviderError; +use std::{ + fmt, + fmt::{Display, Formatter}, + time::Duration, +}; +use strum::Display; +use thiserror::Error; + +/// Result of [`OpProofStoragePruner::run`](crate::OpProofStoragePruner::run) execution. +pub type OpProofStoragePrunerResult = Result; + +/// Successful prune summary. +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct PrunerOutput { + /// Total elapsed wall time for this run (fetch + apply). + pub duration: Duration, + /// Earliest block at the start of the run. + pub start_block: u64, + /// New earliest block at the end of the run. + pub end_block: u64, + /// Number of entries updated/removed per table. + pub write_counts: WriteCounts, +} + +impl Display for PrunerOutput { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let blocks = self.end_block.saturating_sub(self.start_block); + let total_entries = self.write_counts.hashed_accounts_written_total + + self.write_counts.hashed_storages_written_total + + self.write_counts.account_trie_updates_written_total + + self.write_counts.storage_trie_updates_written_total; + write!( + f, + "Pruned {}→{} ({} blocks), entries={}, elapsed={:.3}s", + self.start_block, + self.end_block, + blocks, + total_entries, + self.duration.as_secs_f64(), + ) + } +} + +impl PrunerOutput { + /// extend the current [`PrunerOutput`] with another [`PrunerOutput`] + pub fn extend_ref(&mut self, other: Self) { + self.duration += other.duration; + // take the earliest start block + if self.start_block > other.start_block { + self.start_block = other.start_block; + } + // take the latest end block + if self.end_block < other.end_block { + self.end_block = other.end_block; + } + self.write_counts += other.write_counts; + } +} + +/// Error returned by the pruner. +#[derive(Debug, Error, Display)] +pub enum PrunerError { + /// Wrapped error from the underlying `OpProofStorage` layer. + Storage(#[from] OpProofsStorageError), + + /// Wrapped error from the reth db provider. + Provider(#[from] ProviderError), + + /// Block not found in the underlying reth storage provider. + BlockNotFound(u64), + + /// The pruner timed out before finishing the prune + TimedOut(Duration), +} + +#[cfg(test)] +mod tests { + use super::PrunerOutput; + use crate::api::WriteCounts; + use std::time::Duration; + + #[test] + fn test_pruner_output_display() { + let pruner_output = PrunerOutput { + duration: Duration::from_secs(10), + start_block: 1, + end_block: 2, + write_counts: WriteCounts::new(1, 2, 3, 4), + }; + let formatted_pruner_output = format!("{pruner_output}"); + + assert_eq!(formatted_pruner_output, "Pruned 1→2 (1 blocks), entries=10, elapsed=10.000s"); + } +} diff --git a/vendor/reth-optimism-trie/src/prune/metrics.rs b/vendor/reth-optimism-trie/src/prune/metrics.rs new file mode 100644 index 0000000..35457c8 --- /dev/null +++ b/vendor/reth-optimism-trie/src/prune/metrics.rs @@ -0,0 +1,39 @@ +use crate::PrunerOutput; +use reth_metrics::{ + Metrics, + metrics::{Gauge, Histogram}, +}; + +#[derive(Metrics)] +#[metrics(scope = "optimism_trie.pruner")] +pub(crate) struct Metrics { + /// Pruning duration + pub(crate) total_duration_seconds: Histogram, + /// Number of pruned blocks + pub(crate) pruned_blocks: Gauge, + /// Number of account trie updates written in the prune run + pub(crate) account_trie_updates_written: Gauge, + /// Number of storage trie updates written in the prune run + pub(crate) storage_trie_updates_written: Gauge, + /// Number of hashed accounts written in the prune run + pub(crate) hashed_accounts_written: Gauge, + /// Number of hashed storages written in the prune run + pub(crate) hashed_storages_written: Gauge, +} + +impl Metrics { + pub(crate) fn record_prune_result(&self, result: PrunerOutput) { + let blocks_pruned = result.end_block - result.start_block; + if blocks_pruned > 0 { + self.total_duration_seconds.record(result.duration.as_secs_f64()); + self.pruned_blocks.set(blocks_pruned as f64); + + // Consume write counts + let wc = &result.write_counts; + self.account_trie_updates_written.set(wc.account_trie_updates_written_total as f64); + self.storage_trie_updates_written.set(wc.storage_trie_updates_written_total as f64); + self.hashed_accounts_written.set(wc.hashed_accounts_written_total as f64); + self.hashed_storages_written.set(wc.hashed_storages_written_total as f64); + } + } +} diff --git a/vendor/reth-optimism-trie/src/prune/mod.rs b/vendor/reth-optimism-trie/src/prune/mod.rs new file mode 100644 index 0000000..8880658 --- /dev/null +++ b/vendor/reth-optimism-trie/src/prune/mod.rs @@ -0,0 +1,11 @@ +mod error; +pub use error::{OpProofStoragePrunerResult, PrunerError, PrunerOutput}; + +mod pruner; +pub use pruner::OpProofStoragePruner; + +#[cfg(feature = "metrics")] +mod metrics; + +mod task; +pub use task::OpProofStoragePrunerTask; diff --git a/vendor/reth-optimism-trie/src/prune/pruner.rs b/vendor/reth-optimism-trie/src/prune/pruner.rs new file mode 100644 index 0000000..9ba631f --- /dev/null +++ b/vendor/reth-optimism-trie/src/prune/pruner.rs @@ -0,0 +1,683 @@ +#[cfg(feature = "metrics")] +use crate::prune::metrics::Metrics; +use crate::{ + OpProofsProviderRO, OpProofsProviderRw, OpProofsStorageError, OpProofsStore, + prune::error::{OpProofStoragePrunerResult, PrunerError, PrunerOutput}, +}; + +use alloy_eips::{BlockNumHash, eip1898::BlockWithParent}; +use reth_provider::BlockHashReader; +use std::cmp; +use tokio::time::Instant; +use tracing::{debug, error, info, trace}; + +/// Default batch size for pruning operations. +const DEFAULT_PRUNE_BATCH_SIZE: u64 = 50; + +/// Prunes the proof storage by calling `prune_earliest_state` on the storage provider. +#[derive(Debug)] +pub struct OpProofStoragePruner { + /// Storage backend for the prune + store: S, + /// Reader to fetch block hash by block number + block_hash_reader: H, + /// Keep at least these many recent blocks + min_block_interval: u64, + /// Maximum number of blocks to prune in one database transaction + prune_batch_size: u64, + // TODO: add timeout - Maximum time for one pruner run. If `None`, no timeout. + #[doc(hidden)] + #[cfg(feature = "metrics")] + metrics: Metrics, +} + +impl OpProofStoragePruner { + /// Create a new pruner. + pub fn new(store: S, block_hash_reader: H, min_block_interval: u64) -> Self { + Self { + store, + block_hash_reader, + min_block_interval, + prune_batch_size: DEFAULT_PRUNE_BATCH_SIZE, + #[cfg(feature = "metrics")] + metrics: Metrics::default(), + } + } + + /// Set the batch size for pruning operations. The pruner will prune + /// at most this many blocks in one database transaction. + pub const fn with_batch_size(mut self, prune_batch_size: u64) -> Self { + self.prune_batch_size = prune_batch_size; + self + } +} + +impl OpProofStoragePruner +where + S: OpProofsStore, + H: BlockHashReader, +{ + fn run_inner(&self) -> OpProofStoragePrunerResult { + let provider_ro = self.store.provider_ro()?; + let Some((earliest_block, target_earliest_block, mut prune_output)) = + self.resolve_prune_range(&provider_ro)? + else { + return Ok(PrunerOutput::default()); + }; + // Drop the read-only provider before starting write transactions + drop(provider_ro); + + info!( + target: "trie::prune::pruner", + from_block = earliest_block, + to_block = target_earliest_block, + "Starting pruning proof storage", + ); + + // Prune in batches, committing each batch separately to avoid + // holding a large write transaction for the entire prune window. + let mut current_earliest_block = earliest_block; + while current_earliest_block < target_earliest_block { + let batch_end_block = + cmp::min(current_earliest_block + self.prune_batch_size, target_earliest_block); + + let provider_rw = self.store.provider_rw()?; + let batch_output = self.prune_batch_on_provider( + &provider_rw, + current_earliest_block, + batch_end_block, + )?; + provider_rw.commit()?; + + prune_output.extend_ref(batch_output); + current_earliest_block = batch_end_block; + } + + Ok(prune_output) + } + + /// Prune proof storage using the given write provider, without committing. + /// + /// This allows callers to batch pruning with other write operations (e.g., storing + /// new block updates) in a single database transaction. The caller is responsible + /// for committing the transaction. + pub fn prune_with_provider( + &self, + provider_rw: &RW, + ) -> OpProofStoragePrunerResult { + let Some((earliest_block, target_earliest_block, mut prune_output)) = + self.resolve_prune_range(provider_rw)? + else { + debug!(target: "trie::prune::pruner", "Nothing to prune in the given range"); + return Ok(PrunerOutput::default()); + }; + + info!( + target: "trie::prune::pruner", + from_block = earliest_block, + to_block = target_earliest_block, + "Starting pruning proof storage (in-tx)", + ); + + let mut current_earliest_block = earliest_block; + while current_earliest_block < target_earliest_block { + let batch_end_block = + cmp::min(current_earliest_block + self.prune_batch_size, target_earliest_block); + + let batch_output = + self.prune_batch_on_provider(provider_rw, current_earliest_block, batch_end_block)?; + + prune_output.extend_ref(batch_output); + current_earliest_block = batch_end_block; + } + + Ok(prune_output) + } + + /// Resolve the prune range from the given provider. + /// + /// Returns `None` if there is nothing to prune (storage empty or window not exceeded). + fn resolve_prune_range( + &self, + provider: &P, + ) -> Result, PrunerError> { + let window = match provider.get_proof_window() { + Ok(w) => w, + Err(OpProofsStorageError::NoBlocksFound) => { + trace!(target: "trie::prune::pruner", "Proof storage is empty"); + return Ok(None); + } + Err(err) => return Err(err.into()), + }; + let (latest_block, earliest_block) = (window.latest.number, window.earliest.number); + if latest_block.saturating_sub(earliest_block) <= self.min_block_interval { + trace!(target: "trie::prune::pruner", "Nothing to prune"); + return Ok(None); + } + let target_earliest_block = latest_block - self.min_block_interval; + let prune_output = PrunerOutput { + start_block: earliest_block, + end_block: target_earliest_block, + ..Default::default() + }; + Ok(Some((earliest_block, target_earliest_block, prune_output))) + } + + /// Execute a single prune batch on the given write provider without committing. + fn prune_batch_on_provider( + &self, + provider_rw: &RW, + start_block: u64, + end_block: u64, + ) -> Result { + let batch_start_time = Instant::now(); + + let new_earliest_block_hash = self + .block_hash_reader + .block_hash(end_block) + .inspect_err(|err| { + error!( + target: "trie::prune::pruner", + block = end_block, + ?err, + "Failed to fetch block hash for new earliest block during pruning" + ) + })? + .ok_or(PrunerError::BlockNotFound(end_block))?; + + let parent_block_num = end_block - 1; + let parent_block_hash = self + .block_hash_reader + .block_hash(parent_block_num) + .inspect_err(|err| { + error!( + target: "trie::prune::pruner", + block = parent_block_num, + ?err, + "Failed to fetch block hash for parent block during pruning" + ) + })? + .ok_or(PrunerError::BlockNotFound(parent_block_num))?; + + let block_with_parent = BlockWithParent { + parent: parent_block_hash, + block: BlockNumHash { number: end_block, hash: new_earliest_block_hash }, + }; + + let write_counts = provider_rw.prune_earliest_state(block_with_parent)?; + + let duration = batch_start_time.elapsed(); + let batch_output = PrunerOutput { duration, start_block, end_block, write_counts }; + + #[cfg(feature = "metrics")] + self.metrics.record_prune_result(batch_output.clone()); + + info!( + target: "trie::prune::pruner", + ?batch_output, + "Finished pruning batch of proof storage", + ); + Ok(batch_output) + } + + /// Run the pruner + pub fn run(&self) { + let res = self.run_inner(); + if let Err(e) = res { + error!(target: "trie::prune::pruner", err=%e, "Pruner failed"); + return; + } + info!(target: "trie::prune::pruner", result = %res.unwrap(), "Finished pruning proof storage"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{BlockStateDiff, OpProofsInitProvider, OpProofsStorage, db::MdbxProofsStorage}; + use alloy_eips::{BlockHashOrNumber, BlockNumHash, NumHash}; + use alloy_primitives::{B256, BlockNumber, U256}; + use mockall::mock; + use reth_primitives_traits::Account; + use reth_storage_errors::provider::ProviderResult; + use reth_trie::{ + BranchNodeCompact, HashedPostState, HashedStorage, Nibbles, + hashed_cursor::HashedCursor, + trie_cursor::TrieCursor, + updates::{StorageTrieUpdates, TrieUpdates, TrieUpdatesSorted}, + }; + use std::sync::Arc; + use tempfile::TempDir; + + mock! ( + #[derive(Debug)] + pub BlockHashReader {} + + impl BlockHashReader for BlockHashReader { + fn block_hash(&self, number: BlockNumber) -> ProviderResult>; + + fn convert_block_hash( + &self, + _hash_or_number: BlockHashOrNumber, + ) -> ProviderResult>; + + fn canonical_hashes_range( + &self, + _start: BlockNumber, + _end: BlockNumber, + ) -> ProviderResult>; + } + ); + + fn b256(n: u64) -> B256 { + use alloy_primitives::keccak256; + keccak256(n.to_be_bytes()) + } + + /// Build a block-with-parent for number `n` with deterministic hash. + fn block(n: u64, parent: B256) -> BlockWithParent { + BlockWithParent::new(parent, NumHash::new(n, b256(n))) + } + + #[tokio::test] + async fn run_inner_and_and_verify_updated_state() { + // --- env/store --- + let dir = TempDir::new().unwrap(); + let store = Arc::new(MdbxProofsStorage::new(dir.path()).expect("env")); + + { + let init = store.initialization_provider().expect("init"); + init.set_initial_state_anchor(BlockNumHash { number: 0, hash: B256::ZERO }) + .expect("anchor"); + init.commit_initial_state().expect("commit init"); + OpProofsInitProvider::commit(init).expect("commit"); + } + + // --- entities --- + // accounts + let a1 = B256::from([0xA1; 32]); + let a2 = B256::from([0xA2; 32]); + let a3 = B256::from([0xA3; 32]); // introduced later + + // one storage address with 3 slots + let stor_addr = B256::from([0x10; 32]); + let s1 = B256::from([0xB1; 32]); + let s2 = B256::from([0xB2; 32]); + let s3 = B256::from([0xB3; 32]); + + // account-trie paths (p1 gets removed by block 3; p2 remains; p3 added later) + let p1 = Nibbles::from_nibbles_unchecked([0x01, 0x02]); + let p2 = Nibbles::from_nibbles_unchecked([0x03, 0x04]); + let p3 = Nibbles::from_nibbles_unchecked([0x05, 0x06]); + + let node_p1 = BranchNodeCompact::new(0b1, 0, 0, vec![], Some(B256::from([0x11; 32]))); + let node_p2 = BranchNodeCompact::new(0b10, 0, 0, vec![], Some(B256::from([0x22; 32]))); + let node_p3 = BranchNodeCompact::new(0b11, 0, 0, vec![], Some(B256::from([0x33; 32]))); + + // storage-trie paths (st1 removed by block 3; st2 remains; st3 added later) + let st1 = Nibbles::from_nibbles_unchecked([0x0A]); + let st2 = Nibbles::from_nibbles_unchecked([0x0B]); + let st3 = Nibbles::from_nibbles_unchecked([0x0C]); + + let node_st2 = BranchNodeCompact::new(0b101, 0, 0, vec![], Some(B256::from([0x44; 32]))); + let node_st3 = BranchNodeCompact::new(0b110, 0, 0, vec![], Some(B256::from([0x55; 32]))); + + // --- write 5 blocks manually --- + let mut parent = B256::ZERO; + + // Block 1: add a1,a2; s1=100, s2=200; add p1, st1 + { + let b1 = block(1, parent); + + let mut d_trie_updates = TrieUpdates::default(); + let mut d_post_state = HashedPostState::default(); + + d_post_state.accounts.insert( + a1, + Some(Account { nonce: 1, balance: U256::from(1_001), ..Default::default() }), + ); + d_post_state.accounts.insert( + a2, + Some(Account { nonce: 1, balance: U256::from(1_002), ..Default::default() }), + ); + + let mut hs = HashedStorage::default(); + hs.storage.insert(s1, U256::from(100)); + hs.storage.insert(s2, U256::from(200)); + d_post_state.storages.insert(stor_addr, hs); + + d_trie_updates.account_nodes.insert(p1, node_p1); + let e = d_trie_updates.storage_tries.entry(stor_addr).or_default(); + e.storage_nodes.insert(st1, BranchNodeCompact::default()); + + let d = BlockStateDiff { + sorted_post_state: d_post_state.into_sorted(), + sorted_trie_updates: d_trie_updates.into_sorted(), + }; + let provider = store.provider_rw().expect("provider_rw"); + provider.store_trie_updates(b1, d).expect("b1"); + OpProofsProviderRw::commit(provider).expect("commit"); + parent = b256(1); + } + + // Block 2: update a2; add a3; s2=220, s3=300; add p2, st2 + { + let b2 = block(2, parent); + + let mut d_trie_updates = TrieUpdates::default(); + let mut d_post_state = HashedPostState::default(); + + d_post_state.accounts.insert( + a2, + Some(Account { nonce: 2, balance: U256::from(2_002), ..Default::default() }), + ); + d_post_state.accounts.insert( + a3, + Some(Account { nonce: 1, balance: U256::from(1_003), ..Default::default() }), + ); + + let mut hs = HashedStorage::default(); + hs.storage.insert(s2, U256::from(220)); + hs.storage.insert(s3, U256::from(300)); + d_post_state.storages.insert(stor_addr, hs); + + d_trie_updates.account_nodes.insert(p2, node_p2.clone()); + let e = d_trie_updates.storage_tries.entry(stor_addr).or_default(); + e.storage_nodes.insert(st2, node_st2.clone()); + + let d = BlockStateDiff { + sorted_post_state: d_post_state.into_sorted(), + sorted_trie_updates: d_trie_updates.into_sorted(), + }; + let provider = store.provider_rw().expect("provider_rw"); + provider.store_trie_updates(b2, d).expect("b2"); + OpProofsProviderRw::commit(provider).expect("commit"); + parent = b256(2); + } + + // Block 3: delete a1; leave a2,a3; remove p1; remove st1 (storage-trie) + { + let b3 = block(3, parent); + + let mut d_trie_updates = TrieUpdates::default(); + let mut d_post_state = HashedPostState::default(); + + // delete a1, keep a2 & a3 values unchanged for this block + d_post_state.accounts.insert(a1, None); + + // remove account trie node p1 + d_trie_updates.removed_nodes.insert(p1); + + // remove storage-trie node st1 + let mut st_upd = StorageTrieUpdates::default(); + st_upd.removed_nodes.insert(st1); + d_trie_updates.storage_tries.insert(stor_addr, st_upd); + + let d = BlockStateDiff { + sorted_post_state: d_post_state.into_sorted(), + sorted_trie_updates: d_trie_updates.into_sorted(), + }; + let provider = store.provider_rw().expect("provider_rw"); + provider.store_trie_updates(b3, d).expect("b3"); + OpProofsProviderRw::commit(provider).expect("commit"); + parent = b256(3); + } + + // Block 4 (kept): update a2; s1=140; add p3, st3 + { + let b4 = block(4, parent); + + let mut d_trie_updates = TrieUpdates::default(); + let mut d_post_state = HashedPostState::default(); + + d_post_state.accounts.insert( + a2, + Some(Account { nonce: 3, balance: U256::from(3_002), ..Default::default() }), + ); + + let mut hs = HashedStorage::default(); + hs.storage.insert(s1, U256::from(140)); + d_post_state.storages.insert(stor_addr, hs); + d_trie_updates.account_nodes.insert(p3, node_p3.clone()); + let e = d_trie_updates.storage_tries.entry(stor_addr).or_default(); + e.storage_nodes.insert(st3, node_st3.clone()); + + let d = BlockStateDiff { + sorted_post_state: d_post_state.into_sorted(), + sorted_trie_updates: d_trie_updates.into_sorted(), + }; + let provider = store.provider_rw().expect("provider_rw"); + provider.store_trie_updates(b4, d).expect("b4"); + OpProofsProviderRw::commit(provider).expect("commit"); + parent = b256(4); + } + + // Block 5 (kept): update a3; s3=330 + { + let b5 = block(5, parent); + + let mut d_post_state = HashedPostState::default(); + + d_post_state.accounts.insert( + a3, + Some(Account { nonce: 2, balance: U256::from(2_003), ..Default::default() }), + ); + + let mut hs = HashedStorage::default(); + hs.storage.insert(s3, U256::from(330)); + d_post_state.storages.insert(stor_addr, hs); + + let d = BlockStateDiff { + sorted_post_state: d_post_state.into_sorted(), + sorted_trie_updates: TrieUpdatesSorted::default(), + }; + let provider = store.provider_rw().expect("provider_rw"); + provider.store_trie_updates(b5, d).expect("b5"); + OpProofsProviderRw::commit(provider).expect("commit"); + } + + // sanity: earliest=0, latest=5 + { + let provider = store.provider_ro().expect("provider_ro"); + let e = provider.get_earliest_block().expect("earliest"); + let l = provider.get_latest_block().expect("latest"); + assert_eq!(e.number, 0); + assert_eq!(l.number, 5); + } + + // --- prune: remove the first 3 blocks, keep 4 and 5 + // new_earliest = 5-1 = 4 + let mut block_hash_reader = MockBlockHashReader::new(); + block_hash_reader + .expect_block_hash() + .withf(move |block_num| *block_num == 4) + .returning(move |_| Ok(Some(b256(4)))); + + block_hash_reader + .expect_block_hash() + .withf(move |block_num| *block_num == 3) + .returning(move |_| Ok(Some(b256(3)))); + + let pruner = OpProofStoragePruner::new(store.clone(), block_hash_reader, 1); + let out = pruner.run_inner().expect("pruner ok"); + assert_eq!(out.start_block, 0); + assert_eq!(out.end_block, 4, "pruned up to 4 (inclusive); new earliest is 4"); + + // proof window moved: earliest=4, latest=5 + { + let provider = store.provider_ro().expect("provider_ro"); + let e = provider.get_earliest_block().expect("earliest"); + let l = provider.get_latest_block().expect("latest"); + assert_eq!(e, NumHash::new(4, b256(4))); + assert_eq!(l, NumHash::new(5, b256(5))); + } + + // --- DB checks + let provider = store.provider_ro().expect("provider_ro"); + let mut acc_cur = provider.account_hashed_cursor(4).expect("acc cur"); + let mut stor_cur = provider.storage_hashed_cursor(stor_addr, 4).expect("stor cur"); + let mut acc_trie_cur = provider.account_trie_cursor(4).expect("acc trie cur"); + let mut stor_trie_cur = provider.storage_trie_cursor(stor_addr, 4).expect("stor trie cur"); + + // Check these histories have been removed + let pruned_hashed_account = a1; + let pruned_trie_accounts = p1; + let pruned_trie_storage = st1; + + assert_ne!( + acc_cur.seek(pruned_hashed_account).expect("seek").unwrap().0, + pruned_hashed_account, + "deleted account must not exist in earliest snapshot" + ); + assert_ne!( + acc_trie_cur.seek(pruned_trie_accounts).expect("seek").unwrap().0, + pruned_trie_accounts, + "deleted account trie must not exist in earliest snapshot" + ); + assert_ne!( + stor_trie_cur.seek(pruned_trie_storage).expect("seek").unwrap().0, + pruned_trie_storage, + "deleted storage trie must not exist in earliest snapshot" + ); + + // Check these histories have been updated - till block 4 + let updated_hashed_accounts = vec![ + (a2, Account { nonce: 3, balance: U256::from(3_002), ..Default::default() }), /* block 4 */ + (a3, Account { nonce: 1, balance: U256::from(1_003), ..Default::default() }), /* block 2 */ + ]; + let updated_hashed_storage = vec![ + (s1, U256::from(140)), // block 4 + (s2, U256::from(220)), // block 2 + (s3, U256::from(300)), // block 2 + ]; + let updated_trie_accounts = vec![ + (p2, node_p2), // block 2 + (p3, node_p3), // block 4 + ]; + let updated_trie_storage = vec![ + (st2, node_st2), // block 2 + (st3, node_st3), // block 4 + ]; + + for (key, val) in updated_hashed_accounts { + let (k, vv) = acc_cur.seek(key).expect("seek").unwrap(); + assert_eq!(key, k, "key must exist"); + assert_eq!(val, vv, "value must be updated"); + } + + for (key, val) in updated_hashed_storage { + let (k, vv) = stor_cur.seek(key).expect("seek").unwrap(); + assert_eq!(key, k, "key must exist"); + assert_eq!(val, vv, "value must be updated"); + } + + for (key, val) in updated_trie_accounts { + let (k, vv) = acc_trie_cur.seek(key).expect("seek").unwrap(); + assert_eq!(key, k, "key must exist"); + assert_eq!(val, vv, "value must be updated"); + } + for (key, val) in updated_trie_storage { + let (k, vv) = stor_trie_cur.seek(key).expect("seek").unwrap(); + assert_eq!(key, k, "key must exist"); + assert_eq!(val, vv, "value must be updated"); + } + } + + // Both latest and earliest blocks are None -> early return default; DB untouched. + #[tokio::test] + async fn run_inner_where_latest_block_is_none() { + let dir = TempDir::new().unwrap(); + let store: OpProofsStorage> = + OpProofsStorage::from(Arc::new(MdbxProofsStorage::new(dir.path()).expect("env"))); + + { + let provider = store.provider_ro().unwrap(); + let earliest = provider.get_earliest_block(); + let latest = provider.get_latest_block(); + println!("{:?} {:?}", earliest, latest); + assert!(matches!(earliest, Err(OpProofsStorageError::NoBlocksFound))); + assert!(matches!(latest, Err(OpProofsStorageError::NoBlocksFound))); + } + + let block_hash_reader = MockBlockHashReader::new(); + let pruner = OpProofStoragePruner::new(store, block_hash_reader, 10); + let out = pruner.run_inner().expect("ok"); + assert_eq!(out, PrunerOutput::default(), "should early-return default output"); + } + + // When only earliest is set (and latest is absent), latest falls back to earliest. + // This yields interval=0, so pruning should no-op. + #[tokio::test] + async fn run_inner_earliest_none_real_db() { + let dir = TempDir::new().unwrap(); + let store: OpProofsStorage> = + OpProofsStorage::from(Arc::new(MdbxProofsStorage::new(dir.path()).expect("env"))); + + // Bootstrap the chain anchor via the init flow; commit_initial_state sets both + // earliest and latest. + { + let init = store.initialization_provider().expect("init"); + init.set_initial_state_anchor(BlockNumHash { number: 3, hash: b256(3) }) + .expect("anchor"); + init.commit_initial_state().expect("commit init"); + OpProofsInitProvider::commit(init).expect("commit"); + } + + { + let provider = store.provider_ro().unwrap(); + let earliest = provider.get_earliest_block().unwrap(); + let latest = provider.get_latest_block().unwrap(); + assert_eq!(earliest.number, 3); + assert_eq!(latest.number, 3, "commit_initial_state bootstraps both anchors"); + } + + let block_hash_reader = MockBlockHashReader::new(); + let pruner = OpProofStoragePruner::new(store, block_hash_reader, 1); + let out = pruner.run_inner().expect("ok"); + assert_eq!(out, PrunerOutput::default(), "should early-return default output"); + } + + // interval < min_block_interval -> "Nothing to prune" path; default output. + #[tokio::test] + async fn run_inner_interval_too_small_real_db() { + use crate::BlockStateDiff; + + let dir = TempDir::new().unwrap(); + let store: OpProofsStorage> = + OpProofsStorage::from(Arc::new(MdbxProofsStorage::new(dir.path()).expect("env"))); + + // Bootstrap earliest=4 via the init flow. + let earliest_num = 4u64; + let h4 = b256(4); + { + let init = store.initialization_provider().expect("init"); + init.set_initial_state_anchor(BlockNumHash { number: earliest_num, hash: h4 }) + .expect("anchor"); + init.commit_initial_state().expect("commit init"); + OpProofsInitProvider::commit(init).expect("commit"); + } + + // Set latest=5 by storing block 5. + { + let provider = store.provider_rw().expect("provider_rw"); + let b5 = block(5, h4); + provider.store_trie_updates(b5, BlockStateDiff::default()).expect("store b5"); + OpProofsProviderRw::commit(provider).expect("commit"); + } + + // Sanity: earliest=4, latest=5 => interval=1 + { + let provider = store.provider_ro().unwrap(); + let e = provider.get_earliest_block().unwrap(); + let l = provider.get_latest_block().unwrap(); + assert_eq!(e.number, 4); + assert_eq!(l.number, 5); + } + + // Require min_block_interval=2 (or greater) so interval < min + let block_hash_reader = MockBlockHashReader::new(); + let pruner = OpProofStoragePruner::new(store, block_hash_reader, 2); + let out = pruner.run_inner().expect("ok"); + assert_eq!(out, PrunerOutput::default(), "no pruning should occur"); + } +} diff --git a/vendor/reth-optimism-trie/src/prune/task.rs b/vendor/reth-optimism-trie/src/prune/task.rs new file mode 100644 index 0000000..65df878 --- /dev/null +++ b/vendor/reth-optimism-trie/src/prune/task.rs @@ -0,0 +1,61 @@ +use crate::{OpProofsStore, prune::OpProofStoragePruner}; +use reth_provider::BlockHashReader; +use reth_tasks::shutdown::GracefulShutdown; +use tokio::{ + time, + time::{Duration, MissedTickBehavior}, +}; +use tracing::info; + +/// Periodic pruner task: constructs the pruner and runs it every interval. +#[derive(Debug)] +pub struct OpProofStoragePrunerTask { + pruner: OpProofStoragePruner, + min_block_interval: u64, + task_run_interval: Duration, +} + +impl OpProofStoragePrunerTask +where + S: OpProofsStore, + H: BlockHashReader, +{ + /// Initialize a new [`OpProofStoragePrunerTask`] + pub fn new( + store: S, + hash_reader: H, + min_block_interval: u64, + task_run_interval: Duration, + ) -> Self { + let pruner = OpProofStoragePruner::new(store, hash_reader, min_block_interval); + Self { pruner, min_block_interval, task_run_interval } + } + + /// Run forever (until `cancel`), executing one prune pass per `task_run_interval`. + pub async fn run(self, mut signal: GracefulShutdown) { + info!( + target: "trie::prune::task", + min_block_interval = self.min_block_interval, + interval_secs = self.task_run_interval.as_secs(), + "Starting pruner task" + ); + + // Drive pruning with a periodic ticker + let mut interval = time::interval(self.task_run_interval); + interval.set_missed_tick_behavior(MissedTickBehavior::Delay); + + loop { + tokio::select! { + _ = &mut signal => { + info!(target: "trie::prune::task", "Pruner task cancelled; exiting"); + break; + } + _ = interval.tick() => { + self.pruner.run() + } + } + } + + info!(target: "trie::prune::task", "Pruner task stopped"); + } +} diff --git a/vendor/reth-optimism-trie/src/snapshot/error.rs b/vendor/reth-optimism-trie/src/snapshot/error.rs new file mode 100644 index 0000000..fabbbbb --- /dev/null +++ b/vendor/reth-optimism-trie/src/snapshot/error.rs @@ -0,0 +1,70 @@ +//! Error type for snapshot-init operations. + +use crate::{OpProofsStorageError, api::SnapshotInitStatus}; +use alloy_primitives::B256; +use reth_db::DatabaseError; +use reth_execution_errors::StateRootError; +use reth_provider::ProviderError; + +/// Error type for [`super::SnapshotInitJob`]. +#[derive(Debug, thiserror::Error)] +pub enum SnapshotError { + /// Error bubbled up from proofs storage operations. + #[error(transparent)] + Storage(#[from] OpProofsStorageError), + /// Error from reth provider operations. + #[error(transparent)] + Provider(#[from] ProviderError), + /// State root computation failed. + #[error(transparent)] + StateRoot(#[from] StateRootError), + /// Computed state root does not match the expected root from the header. + #[error( + "State root mismatch at block {block_number}: computed {computed:?}, expected {expected:?}" + )] + StateRootMismatch { + /// Block whose state root was validated. + block_number: u64, + /// Computed root from the snapshot tables + live hashed leaves. + computed: B256, + /// Expected root from reth's block header. + expected: B256, + }, + /// Snapshot init target block is outside the proofs window. + #[error( + "snapshot init target block {target_block} is outside proof window [{earliest}, {latest}]" + )] + SnapshotInitTargetOutsideWindow { + /// The block requested as the snapshot anchor. + target_block: u64, + /// Current earliest persisted block. + earliest: u64, + /// Current latest persisted block. + latest: u64, + }, + /// Resume requested but the existing `Building` anchor doesn't match. + #[error("snapshot resume drift detected at anchor block {anchor_block}: {reason}")] + SnapshotResumeDriftDetected { + /// Anchor block of the partial snapshot already on disk. + anchor_block: u64, + /// Human-readable reason describing the drift. + reason: &'static str, + }, + /// A snapshot already exists at a different anchor; the caller must drop + /// it before requesting a new build. + #[error( + "snapshot already exists at block {existing_block} (status {existing_status:?}); drop it before rebuilding" + )] + SnapshotAlreadyExists { + /// Anchor block of the existing snapshot. + existing_block: u64, + /// Lifecycle status of the existing snapshot. + existing_status: SnapshotInitStatus, + }, +} + +impl From for SnapshotError { + fn from(err: DatabaseError) -> Self { + Self::Storage(OpProofsStorageError::from(err)) + } +} diff --git a/vendor/reth-optimism-trie/src/snapshot/job.rs b/vendor/reth-optimism-trie/src/snapshot/job.rs new file mode 100644 index 0000000..8c2e679 --- /dev/null +++ b/vendor/reth-optimism-trie/src/snapshot/job.rs @@ -0,0 +1,675 @@ +//! [`SnapshotInitJob`] — chunked builder for the one-time trie-state snapshot. + +use super::SnapshotError; +use crate::{ + OpProofsProviderRO, OpProofsSnapshotInitProvider, SnapshotHashedCursorFactory, + SnapshotInitAnchor, SnapshotInitStatus, SnapshotTrieCursorFactory, + db::{HashedStorageKey, StorageTrieKey}, + initialize::CompletionEstimatable, +}; +use alloy_eips::BlockNumHash; +use alloy_primitives::{B256, BlockNumber, U256}; +use reth_primitives_traits::{Account, AlloyBlockHeader}; +use reth_provider::{BlockHashReader, HeaderProvider, ProviderError}; +use reth_trie::{ + BranchNodeCompact, HashedPostState, Nibbles, StateRoot, StoredNibbles, + hashed_cursor::{HashedCursor, HashedPostStateCursorFactory}, + trie_cursor::TrieCursor, +}; +use std::time::Instant; +use tracing::info; + +/// Default rows copied per chunked init transaction. Used by +/// [`SnapshotInitJob::new`]; callers can override via +/// [`SnapshotInitJob::with_chunk_size`]. +const SNAPSHOT_INIT_CHUNK_SIZE: usize = 50_000; + +/// Storage-trie chunk grouped by hashed address. Each inner vec is the +/// per-address payload accepted by +/// [`OpProofsSnapshotInitProvider::store_storage_trie_snapshot_branches`]. +type StorageChunk = Vec<(B256, Vec<(Nibbles, Option)>)>; + +/// Hashed-storage chunk grouped by hashed address. Each inner vec is the +/// per-address payload accepted by +/// [`OpProofsSnapshotInitProvider::store_hashed_storages_snapshot`]. +type HashedStorageChunk = Vec<(B256, Vec<(B256, U256)>)>; + +/// Output of a successful [`SnapshotInitJob::run`] call. +/// +/// Shape mirrors [`crate::api::SnapshotInitAnchor`]: the anchor `block` plus +/// the lifecycle `status` (always [`SnapshotInitStatus::Completed`] on success). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SnapshotInitOutcome { + /// Block the snapshot's trie state corresponds to. + pub block: BlockNumHash, + /// Lifecycle status — always [`SnapshotInitStatus::Completed`] for a + /// successful run. + pub status: SnapshotInitStatus, + /// Number of account trie nodes copied during this run (does **not** + /// include rows already present from a prior resumable run). + pub account_nodes_copied: u64, + /// Number of storage trie nodes copied during this run. + pub storage_nodes_copied: u64, + /// Number of hashed account leaves copied during this run. + pub hashed_accounts_copied: u64, + /// Number of hashed storage leaves copied during this run. + pub hashed_storages_copied: u64, +} + +/// Builds the one-time trie-state snapshot. +#[derive(Debug)] +pub struct SnapshotInitJob { + /// Reth DB provider (used to look up the target block's hash + header). + provider: P, + /// Op-reth proofs storage that owns the snapshot tables. + storage: S, + /// Rows committed per chunked rw-tx during the drain phases. Larger + /// values trade peak memory for fewer commits. + chunk_size: usize, +} + +impl SnapshotInitJob { + /// Build a job with the default chunk size. + pub const fn new(provider: P, storage: S) -> Self { + Self { provider, storage, chunk_size: SNAPSHOT_INIT_CHUNK_SIZE } + } + + /// Override the per-tx chunk size. Useful for operators tuning memory + /// usage on small/large databases, or for tests that want to drive the + /// chunk loop in a few iterations. + pub const fn with_chunk_size(mut self, chunk_size: usize) -> Self { + self.chunk_size = chunk_size; + self + } +} + +impl SnapshotInitJob +where + P: HeaderProvider + BlockHashReader + Send, + S: crate::OpProofsBackfillStore + Send, +{ + /// Build a snapshot at `target_block`, validating against the reth header. + /// + /// `target_block` must fall inside the proofs window's `[earliest, latest]`. + /// Auto-resumes a partial `Building` snapshot if the existing anchor + /// matches; refuses to run if a `Ready` snapshot exists at a different + /// anchor (the caller must drop it first). + pub fn run(&self, target_block: BlockNumber) -> Result { + let start = Instant::now(); + + let target = self.prepare_anchor(target_block)?; + // Read the init anchor once: lifecycle classification uses status/block, + // and the drain phases use the destination-table resume keys. + let init_anchor = + self.storage.snapshot_initialization_provider()?.snapshot_init_anchor()?; + self.start_or_resume(target, &init_anchor)?; + + let expected_root = self.expected_state_root(target_block)?; + + let copy_start = Instant::now(); + let account_nodes_copied = + self.drain_account_trie(target_block, init_anchor.last_account_trie_key)?; + let storage_nodes_copied = + self.drain_storage_trie(target_block, init_anchor.last_storage_trie_key)?; + let hashed_accounts_copied = + self.drain_hashed_accounts(target_block, init_anchor.last_hashed_account_key)?; + let hashed_storages_copied = + self.drain_hashed_storages(target_block, init_anchor.last_hashed_storage_key)?; + let copy_elapsed = copy_start.elapsed(); + + let validate_start = Instant::now(); + self.validate_state_root(target_block, expected_root)?; + let validate_elapsed = validate_start.elapsed(); + + self.finalize_ready()?; + + info!( + target: "reth::op-proofs::snapshot-init", + block = target_block, + account_nodes_copied, + storage_nodes_copied, + hashed_accounts_copied, + hashed_storages_copied, + copy_elapsed = ?copy_elapsed, + validate_elapsed = ?validate_elapsed, + total_elapsed = ?start.elapsed(), + "Snapshot init complete" + ); + + Ok(SnapshotInitOutcome { + block: target, + status: SnapshotInitStatus::Completed, + account_nodes_copied, + storage_nodes_copied, + hashed_accounts_copied, + hashed_storages_copied, + }) + } + + /// Validate `target_block` is inside the proofs window and resolve its hash. + fn prepare_anchor(&self, target_block: BlockNumber) -> Result { + let ro = self.storage.provider_ro()?; + let window = ro.get_proof_window()?; + if target_block < window.earliest.number || target_block > window.latest.number { + return Err(SnapshotError::SnapshotInitTargetOutsideWindow { + target_block, + earliest: window.earliest.number, + latest: window.latest.number, + }); + } + + let target_hash = self + .provider + .block_hash(target_block)? + .ok_or_else(|| ProviderError::HeaderNotFound(target_block.into()))?; + Ok(BlockNumHash::new(target_block, target_hash)) + } + + /// Decide whether this run starts fresh or resumes an in-flight build, + /// and plant a new `Building` row when fresh. + /// + /// Errors on drift (`InProgress` at a different target) or refusal + /// (`Completed` snapshot already exists). + fn start_or_resume( + &self, + target: BlockNumHash, + init_anchor: &SnapshotInitAnchor, + ) -> Result<(), SnapshotError> { + // Invariant: `InProgress`/`Completed` always carry an anchor block — + // `set_snapshot_init_anchor` plants status + block atomically. + let resume = match init_anchor.status { + SnapshotInitStatus::NotStarted => false, + SnapshotInitStatus::InProgress => { + let b = init_anchor.block.expect("InProgress implies anchor planted"); + if b == target { + true + } else { + return Err(SnapshotError::SnapshotResumeDriftDetected { + anchor_block: b.number, + reason: "snapshot init target does not match the in-progress snapshot anchor", + }); + } + } + SnapshotInitStatus::Completed => { + let b = init_anchor.block.expect("Completed implies anchor planted"); + return Err(SnapshotError::SnapshotAlreadyExists { + existing_block: b.number, + existing_status: SnapshotInitStatus::Completed, + }); + } + }; + + if !resume { + let sp = self.storage.snapshot_initialization_provider()?; + sp.set_snapshot_init_anchor(target)?; + OpProofsSnapshotInitProvider::commit(sp)?; + } + info!( + target: "reth::op-proofs::snapshot-init", + block = target.number, + resume, + "Starting snapshot init" + ); + Ok(()) + } + + /// Look up the expected state root for `target_block` from reth's headers. + fn expected_state_root(&self, target_block: BlockNumber) -> Result { + Ok(self + .provider + .header_by_number(target_block)? + .ok_or_else(|| ProviderError::HeaderNotFound(target_block.into()))? + .state_root()) + } + + /// Drain the history-aware account trie cursor at `target_block` into + /// `V2AccountsTrieSnapshot`, one chunk per rw-tx. Resumes past whatever's + /// currently in the snapshot. + /// + /// Returns the number of rows copied during *this* call (excluding rows + /// already present from prior runs). + fn drain_account_trie( + &self, + target_block: BlockNumber, + mut resume_after: Option, + ) -> Result { + let phase_start = Instant::now(); + let mut initial_progress: Option = None; + let mut copied = 0u64; + + // `resume_after` is the destination table's last key at run start + // (read once in `run`); thereafter we advance it locally as we + // commit each chunk. + loop { + let chunk = { + let ro = self.storage.provider_ro()?; + let mut cursor = ro.account_trie_cursor(target_block)?; + collect_account_chunk(&mut cursor, resume_after.clone(), self.chunk_size)? + }; + if chunk.is_empty() { + break; + } + let n = chunk.len() as u64; + let last_key = chunk.last().expect("non-empty").0.clone(); + let sp = self.storage.snapshot_initialization_provider()?; + sp.store_account_trie_snapshot_branches(chunk)?; + OpProofsSnapshotInitProvider::commit(sp)?; + copied += n; + log_phase_progress("accounts", &last_key, &mut initial_progress, phase_start, copied); + resume_after = Some(last_key); + } + Ok(copied) + } + + /// Walk hashed accounts at `target_block`, drain each account's historical + /// storage trie cursor into `V2StoragesTrieSnapshot`, one chunk per rw-tx. + /// + /// Resume tracks the last `StorageTrieKey` written. + fn drain_storage_trie( + &self, + target_block: BlockNumber, + mut resume_after: Option, + ) -> Result { + let phase_start = Instant::now(); + let mut initial_progress: Option = None; + let mut copied = 0u64; + + // `resume_after` is the destination table's last key at run start + // (read once in `run`); thereafter we advance it locally as we + // commit each chunk. + loop { + let chunk = { + let ro = self.storage.provider_ro()?; + collect_storage_chunk(&ro, target_block, resume_after.clone(), self.chunk_size)? + }; + if chunk.is_empty() { + break; + } + let n: u64 = chunk.iter().map(|(_, nodes)| nodes.len() as u64).sum(); + // The chunk's last storage-trie key is the last (addr, path) pair we + // just committed. Storage progress tracks the hashed address, so + // this is what feeds `estimate_progress`. + let (last_addr, last_path_group) = chunk.last().expect("non-empty"); + let last_key = StorageTrieKey::new( + *last_addr, + StoredNibbles(last_path_group.last().expect("non-empty group").0), + ); + let sp = self.storage.snapshot_initialization_provider()?; + for (addr, nodes) in chunk { + sp.store_storage_trie_snapshot_branches(addr, nodes)?; + } + OpProofsSnapshotInitProvider::commit(sp)?; + copied += n; + log_phase_progress("storages", &last_key, &mut initial_progress, phase_start, copied); + resume_after = Some(last_key); + } + Ok(copied) + } + + /// Drain the history-aware hashed-account cursor at `target_block` into + /// [`crate::db::V2HashedAccountsSnapshot`], one chunk per rw-tx. + fn drain_hashed_accounts( + &self, + target_block: BlockNumber, + mut resume_after: Option, + ) -> Result { + let phase_start = Instant::now(); + let mut initial_progress: Option = None; + let mut copied = 0u64; + + loop { + let chunk = { + let ro = self.storage.provider_ro()?; + let mut cursor = ro.account_hashed_cursor(target_block)?; + collect_hashed_account_chunk(&mut cursor, resume_after, SNAPSHOT_INIT_CHUNK_SIZE)? + }; + if chunk.is_empty() { + break; + } + let n = chunk.len() as u64; + let last_key = chunk.last().expect("non-empty").0; + let sp = self.storage.snapshot_initialization_provider()?; + sp.store_hashed_accounts_snapshot(chunk)?; + OpProofsSnapshotInitProvider::commit(sp)?; + copied += n; + log_phase_progress( + "hashed_accounts", + &last_key, + &mut initial_progress, + phase_start, + copied, + ); + resume_after = Some(last_key); + } + Ok(copied) + } + + /// Drain hashed storage leaves at `target_block` into + /// [`crate::db::V2HashedStoragesSnapshot`], one chunk per rw-tx. Walks + /// accounts via the history-aware account cursor; for each account, drains + /// its history-aware storage cursor. + fn drain_hashed_storages( + &self, + target_block: BlockNumber, + mut resume_after: Option, + ) -> Result { + let phase_start = Instant::now(); + let mut initial_progress: Option = None; + let mut copied = 0u64; + + loop { + let chunk = { + let ro = self.storage.provider_ro()?; + collect_hashed_storage_chunk( + &ro, + target_block, + resume_after.clone(), + SNAPSHOT_INIT_CHUNK_SIZE, + )? + }; + if chunk.is_empty() { + break; + } + let n: u64 = chunk.iter().map(|(_, entries)| entries.len() as u64).sum(); + let (last_addr, last_group) = chunk.last().expect("non-empty"); + let last_key = + HashedStorageKey::new(*last_addr, last_group.last().expect("non-empty group").0); + let sp = self.storage.snapshot_initialization_provider()?; + for (addr, entries) in chunk { + sp.store_hashed_storages_snapshot(addr, entries)?; + } + OpProofsSnapshotInitProvider::commit(sp)?; + copied += n; + log_phase_progress( + "hashed_storages", + &last_key, + &mut initial_progress, + phase_start, + copied, + ); + resume_after = Some(last_key); + } + Ok(copied) + } + + /// Compute the state root from the snapshot tables and the live hashed + /// leaves and compare against `expected_root`. + /// + /// On mismatch the meta is **not** advanced — it stays at `Building` so + /// a re-run can diagnose / resume / `snapshot-drop`. + fn validate_state_root( + &self, + target_block: BlockNumber, + expected_root: B256, + ) -> Result<(), SnapshotError> { + let sp = self.storage.snapshot_provider_ro()?; + let state_sorted = HashedPostState::default().into_sorted(); + let computed_root = StateRoot::new( + SnapshotTrieCursorFactory::new(sp.clone()), + HashedPostStateCursorFactory::new( + SnapshotHashedCursorFactory::new(sp.clone()), + &state_sorted, + ), + ) + .root()?; + + if computed_root != expected_root { + return Err(SnapshotError::StateRootMismatch { + block_number: target_block, + computed: computed_root, + expected: expected_root, + }); + } + Ok(()) + } + + /// Flip status to `Ready` and commit in a final rw-tx. + fn finalize_ready(&self) -> Result<(), SnapshotError> { + let sp = self.storage.snapshot_initialization_provider()?; + sp.commit_snapshot()?; + OpProofsSnapshotInitProvider::commit(sp)?; + Ok(()) + } +} + +/// Drain up to `max_entries` rows from a `TrieCursor` strictly after `resume_after`. +fn collect_account_chunk( + cursor: &mut C, + resume_after: Option, + max_entries: usize, +) -> Result, SnapshotError> { + if max_entries == 0 { + return Ok(Vec::new()); + } + let mut next = match resume_after { + None => cursor.seek(Nibbles::default())?, + Some(after) => { + // `seek` returns the first key >= `after`. If it matches exactly, + // skip past it; otherwise we're already past. + match cursor.seek(after.0)? { + Some((k, _)) if k == after.0 => cursor.next()?, + other => other, + } + } + }; + let mut out = Vec::with_capacity(max_entries); + while let Some((k, v)) = next { + if out.len() >= max_entries { + break; + } + out.push((StoredNibbles(k), v)); + next = cursor.next()?; + } + Ok(out) +} + +/// Collect a chunk of storage-trie entries grouped by hashed address. +/// +/// Walks hashed accounts at `target_block` and drains each account's storage +/// trie cursor up to `max_entries` total nodes (counted across all groups), +/// returning `Vec<(address, nodes_for_that_address)>`. Each inner vector is +/// the per-address payload accepted by +/// [`OpProofsSnapshotInitProvider::store_storage_trie_snapshot_branches`]. +/// +/// Resume semantics: `resume_after` is the last [`StorageTrieKey`] already +/// written to the snapshot. We seek the account cursor to its address, +/// position that account's storage cursor past its path, drain, then advance +/// to the next account. +fn collect_storage_chunk

( + proofs_ro: &P, + target_block: BlockNumber, + resume_after: Option, + max_entries: usize, +) -> Result +where + P: OpProofsProviderRO, +{ + if max_entries == 0 { + return Ok(Vec::new()); + } + let mut out: StorageChunk = Vec::new(); + let mut total = 0usize; + + let (start_addr, mut path_resume) = match resume_after { + None => (B256::ZERO, None), + Some(k) => (k.hashed_address, Some(k.path)), + }; + + let mut acc_cursor = proofs_ro.account_hashed_cursor(target_block)?; + let mut next_account = acc_cursor.seek(start_addr)?; + + while let Some((addr, _account)) = next_account { + let mut stor_cursor = proofs_ro.storage_trie_cursor(addr, target_block)?; + + // Position past any pending path resume (only applies to the first + // account on this call — subsequent accounts always start at the + // beginning of their trie). + let mut next_stor = match path_resume.take() { + None => stor_cursor.seek(Nibbles::default())?, + Some(p) => match stor_cursor.seek(p.0)? { + Some((k, _)) if k == p.0 => stor_cursor.next()?, + other => other, + }, + }; + + let mut group: Vec<(Nibbles, Option)> = Vec::new(); + while let Some((path, node)) = next_stor { + if total >= max_entries { + if !group.is_empty() { + out.push((addr, group)); + } + return Ok(out); + } + group.push((path, Some(node))); + total += 1; + next_stor = stor_cursor.next()?; + } + if !group.is_empty() { + out.push((addr, group)); + } + + next_account = acc_cursor.next()?; + } + + Ok(out) +} + +/// Drain up to `max_entries` rows from a [`HashedCursor`] +/// strictly after `resume_after`. +fn collect_hashed_account_chunk( + cursor: &mut C, + resume_after: Option, + max_entries: usize, +) -> Result, SnapshotError> +where + C: HashedCursor, +{ + if max_entries == 0 { + return Ok(Vec::new()); + } + let mut next = match resume_after { + None => cursor.seek(B256::ZERO)?, + Some(after) => match cursor.seek(after)? { + Some((k, _)) if k == after => cursor.next()?, + other => other, + }, + }; + let mut out = Vec::with_capacity(max_entries); + while let Some((k, v)) = next { + if out.len() >= max_entries { + break; + } + out.push((k, v)); + next = cursor.next()?; + } + Ok(out) +} + +/// Collect a chunk of hashed-storage entries grouped by hashed address. +/// +/// Mirrors [`collect_storage_chunk`] for leaves: walks hashed accounts at +/// `target_block` and drains each account's storage cursor up to +/// `max_entries` total entries. The returned vec carries the per-address +/// payload accepted by +/// [`OpProofsSnapshotInitProvider::store_hashed_storages_snapshot`]. +fn collect_hashed_storage_chunk

( + proofs_ro: &P, + target_block: BlockNumber, + resume_after: Option, + max_entries: usize, +) -> Result +where + P: OpProofsProviderRO, +{ + if max_entries == 0 { + return Ok(Vec::new()); + } + let mut out: HashedStorageChunk = Vec::new(); + let mut total = 0usize; + + let (start_addr, mut subkey_resume) = + resume_after.map_or((B256::ZERO, None), |k| (k.hashed_address, Some(k.hashed_storage_key))); + + let mut acc_cursor = proofs_ro.account_hashed_cursor(target_block)?; + let mut next_account = acc_cursor.seek(start_addr)?; + + while let Some((addr, _account)) = next_account { + let mut stor_cursor = proofs_ro.storage_hashed_cursor(addr, target_block)?; + + // Position past any pending subkey resume (only applies to the first + // account on this call). + let mut next_stor = match subkey_resume.take() { + None => stor_cursor.seek(B256::ZERO)?, + Some(p) => match stor_cursor.seek(p)? { + Some((k, _)) if k == p => stor_cursor.next()?, + other => other, + }, + }; + + let mut group: Vec<(B256, U256)> = Vec::new(); + while let Some((slot, value)) = next_stor { + if total >= max_entries { + if !group.is_empty() { + out.push((addr, group)); + } + return Ok(out); + } + group.push((slot, value)); + total += 1; + next_stor = stor_cursor.next()?; + } + if !group.is_empty() { + out.push((addr, group)); + } + + next_account = acc_cursor.next()?; + } + + Ok(out) +} + +impl CompletionEstimatable for StorageTrieKey { + /// Address dominates ordering, so progress along the storage-trie scan + /// tracks the hashed address. + fn estimate_progress(&self) -> f64 { + self.hashed_address.estimate_progress() + } +} + +impl CompletionEstimatable for HashedStorageKey { + /// Address dominates ordering, so progress along the hashed-storage scan + /// tracks the hashed address. + fn estimate_progress(&self) -> f64 { + self.hashed_address.estimate_progress() + } +} + +/// Emit an `info!` line with chunk count, cumulative count, progress %, and ETA. +/// +/// Modeled on the loop in [`crate::initialize::InitializationJob`]: `last_key`'s +/// position in keyspace gives a 0–1 progress fraction, and we extrapolate ETA +/// from how far we moved since the phase started. `initial_progress` is +/// captured on the first call so the rate excludes resume offsets. +fn log_phase_progress( + phase: &'static str, + last_key: &K, + initial_progress: &mut Option, + phase_start: Instant, + cumulative: u64, +) { + let progress = last_key.estimate_progress(); + let initial = *initial_progress.get_or_insert(progress); + let elapsed_secs = phase_start.elapsed().as_secs_f64(); + + let rate = if elapsed_secs.is_normal() { (progress - initial) / elapsed_secs } else { 0.0 }; + let eta_secs = if rate.is_normal() && rate > 0.0 { (1.0 - progress) / rate } else { 0.0 }; + + info!( + target: "reth::op-proofs::snapshot-init", + phase, + cumulative, + progress_pct = format_args!("{:.2}", progress * 100.0), + eta_secs = format_args!("{eta_secs:.0}"), + "Snapshot init progress" + ); +} diff --git a/vendor/reth-optimism-trie/src/snapshot/mod.rs b/vendor/reth-optimism-trie/src/snapshot/mod.rs new file mode 100644 index 0000000..89b0cfc --- /dev/null +++ b/vendor/reth-optimism-trie/src/snapshot/mod.rs @@ -0,0 +1,29 @@ +//! One-time trie-state snapshot at a caller-supplied target block. +//! +//! Copies the trie state at the target block into the parallel +//! `V2*TrieSnapshot` tables and marks the meta row `Ready` once the snapshot's +//! computed state root matches reth's header. +//! +//! The driver is [`SnapshotInitJob`]; failures surface as [`SnapshotError`]. +//! +//! ## Restart / resume +//! +//! Each chunked rw-tx commits independently; after a crash the meta stays at +//! [`SnapshotStatus::Building`] with the original anchor. A re-run inspects +//! [`OpProofsSnapshotInitProvider::snapshot_init_anchor`], discovers the +//! resume keys from the partially-populated destination tables, and continues +//! from there. Resume is only safe when the target block matches the existing +//! anchor — otherwise the init aborts with +//! [`SnapshotError::SnapshotResumeDriftDetected`]. +//! +//! [`SnapshotStatus::Building`]: crate::db::SnapshotStatus::Building +//! [`OpProofsSnapshotInitProvider::snapshot_init_anchor`]: crate::OpProofsSnapshotInitProvider::snapshot_init_anchor + +mod error; +mod job; + +pub use error::SnapshotError; +pub use job::{SnapshotInitJob, SnapshotInitOutcome}; + +#[cfg(test)] +mod tests; diff --git a/vendor/reth-optimism-trie/src/snapshot/tests.rs b/vendor/reth-optimism-trie/src/snapshot/tests.rs new file mode 100644 index 0000000..293dc11 --- /dev/null +++ b/vendor/reth-optimism-trie/src/snapshot/tests.rs @@ -0,0 +1,487 @@ +//! End-to-end tests for [`SnapshotInitJob`]. +//! +//! Reuses chain-construction helpers from [`crate::backfill::tests`] to +//! produce a real reth-side chain + initialized v2 proofs storage, then drives +//! the snapshot init job and asserts the resulting state. +//! +//! The job's own [`SnapshotInitJob::validate_state_root`] is the strongest +//! correctness check: a successful run means the computed root from the +//! snapshot tables + live hashed leaves matches reth's header at `target`. +//! These tests therefore focus on lifecycle behavior (outcome shape, refusal +//! to redo work, target-window validation) rather than table inspection. +//! +//! [`SnapshotInitJob::validate_state_root`]: super::job + +use super::{SnapshotError, SnapshotInitJob}; +use crate::{ + BackfillJob, InitializationJob, MdbxProofsStorageV2, OpProofsBackfillProvider, + OpProofsBackfillStore, OpProofsProviderRO, OpProofsSnapshotInitProvider, + OpProofsSnapshotProviderRO, OpProofsStore, RethTrieStorageLayout, SnapshotInitStatus, + test_utils::{ + build_chain_and_initialize_storage, build_chain_with_storage_writes_and_initialize_storage, + build_transfer_block, chain_spec_with_address, commit_block_to_database, create_storage, + deterministic_keypair, execute_block, public_key_to_address, + }, +}; +use alloy_eips::BlockNumHash; +use alloy_primitives::{Address, B256}; +use reth_db::Database; +use reth_db_common::init::init_genesis; +use reth_provider::{ + DatabaseProviderFactory, StorageSettingsCache, + test_utils::create_test_provider_factory_with_chain_spec, +}; +use reth_trie::{Nibbles, StoredNibbles, trie_cursor::TrieCursor}; +use std::sync::Arc; + +/// Count rows the history-aware `account_trie_cursor` would yield at +/// `target_block` — i.e., the number of entries the snapshot job's +/// `drain_account_trie` sees as input. +fn count_source_account_trie(storage: &Arc, target_block: u64) -> usize { + let provider = storage.provider_ro().expect("ro"); + let mut cursor = provider.account_trie_cursor(target_block).expect("cursor"); + let mut n = 0usize; + let mut entry = cursor.seek(Nibbles::default()).expect("seek"); + while entry.is_some() { + n += 1; + entry = cursor.next().expect("next"); + } + n +} + +/// Count rows in the destination `V2AccountsTrieSnapshot` table via the +/// snapshot reader cursor. +fn count_snapshot_account_trie(storage: &Arc) -> usize { + let sp = storage.snapshot_provider_ro().expect("ro"); + let mut cursor = sp.snapshot_account_trie_cursor().expect("cursor"); + let mut n = 0usize; + let mut entry = cursor.seek(Nibbles::default()).expect("seek"); + while entry.is_some() { + n += 1; + entry = cursor.next().expect("next"); + } + n +} + +#[test] +fn snapshot_init_at_latest_completes_and_anchor_matches() { + // 3-block chain; storage initialized at block 3 (earliest = latest = 3). + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(3); + let target = BlockNumHash::new(latest_num, latest_hash); + + // Drive the snapshot init job at `target`. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = + SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).expect("snapshot"); + + assert_eq!(outcome.block, target); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); + + // Invariant: the snapshot drained every account-trie row visible to the + // history-aware cursor at `target`, and the destination table now mirrors + // that count exactly. Robust to chain size: if the source trie has zero + // branches (legitimate for a small genesis with random-prefix accounts), + // all three counts are zero; otherwise they're all equal and non-zero. + let source_count = count_source_account_trie(&storage, latest_num); + let dest_count = count_snapshot_account_trie(&storage); + assert_eq!( + outcome.account_nodes_copied as usize, source_count, + "outcome count mismatch (source has {source_count} account-trie rows)" + ); + assert_eq!(dest_count, source_count, "snapshot table doesn't match source"); + + // After completion the snapshot is Ready at `target`. + let sp = storage.snapshot_provider_ro().unwrap(); + let anchor = sp.snapshot_anchor().expect("ready"); + assert_eq!(anchor, target); +} + +#[test] +fn snapshot_init_target_outside_window_errors() { + let (provider_factory, storage, _latest_num, _latest_hash) = + build_chain_and_initialize_storage(3); + + // earliest = latest = 3; target = 4 is past `latest`. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let err = SnapshotInitJob::new(reth_provider, storage).run(4).unwrap_err(); + assert!( + matches!( + err, + SnapshotError::SnapshotInitTargetOutsideWindow { + target_block: 4, + earliest: 3, + latest: 3, + } + ), + "got {err:?}" + ); +} + +#[test] +fn snapshot_init_refuses_second_run_when_completed() { + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_and_initialize_storage(3); + + // First run: succeeds. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).expect("first run"); + + // Second run on the same target: snapshot is already Completed, so the + // job must refuse rather than redo the work. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let err = SnapshotInitJob::new(reth_provider, storage).run(latest_num).unwrap_err(); + match err { + SnapshotError::SnapshotAlreadyExists { existing_block, existing_status } => { + assert_eq!(existing_block, latest_num); + assert_eq!(existing_status, SnapshotInitStatus::Completed); + } + other => panic!("expected SnapshotAlreadyExists, got {other:?}"), + } +} + +#[test] +fn snapshot_init_drift_detection_aborts_run() { + // Build a chain and plant a `Building` meta at a *different* anchor than + // the one the job will compute for `latest`. The classify step must + // notice the mismatch and bail with SnapshotResumeDriftDetected. + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_and_initialize_storage(3); + + // Plant Building meta at a fabricated anchor (different block number, so + // it can't possibly match the target the job derives for `latest_num`). + let planted_anchor = BlockNumHash::new(99, B256::repeat_byte(0xFE)); + { + let sp = storage.snapshot_initialization_provider().expect("init"); + sp.set_snapshot_init_anchor(planted_anchor).expect("plant"); + OpProofsSnapshotInitProvider::commit(sp).expect("commit"); + } + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let err = SnapshotInitJob::new(reth_provider, storage).run(latest_num).unwrap_err(); + match err { + SnapshotError::SnapshotResumeDriftDetected { anchor_block, .. } => { + assert_eq!(anchor_block, planted_anchor.number); + } + other => panic!("expected SnapshotResumeDriftDetected, got {other:?}"), + } +} + +#[test] +fn snapshot_init_succeeds_on_chain_with_storage_writes() { + // Drive the job over a chain whose every block touches a storage slot. + // This exercises the storage-trie phase (`drain_storage_trie` + + // `collect_storage_chunk`) end-to-end, including its interaction with + // `account_hashed_cursor` and per-address `storage_trie_cursor`. + // + // We don't assert `storage_nodes_copied > 0`: each block writes the same + // single slot of one contract, so the storage trie is a single leaf with + // no branch nodes — the snapshot can legitimately be empty. The job's + // internal state-root validation is the real correctness check. + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(3); + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = SnapshotInitJob::new(reth_provider, storage).run(latest_num).expect("snapshot"); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); +} + +#[test] +fn snapshot_init_with_small_chunk_size_drives_multi_chunk_drain() { + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + let target = BlockNumHash::new(latest_num, latest_hash); + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = SnapshotInitJob::new(reth_provider, storage.clone()) + .with_chunk_size(1) + .run(latest_num) + .expect("snapshot"); + + assert_eq!(outcome.block, target); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); + + // Destination must match source row-for-row even across many tiny commits. + let source_count = count_source_account_trie(&storage, latest_num); + let dest_count = count_snapshot_account_trie(&storage); + assert_eq!( + outcome.account_nodes_copied as usize, source_count, + "outcome count mismatch (source has {source_count} account-trie rows)" + ); + assert_eq!(dest_count, source_count, "snapshot table doesn't match source"); +} + +#[test] +fn snapshot_init_clear_then_rebuild_succeeds() { + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_and_initialize_storage(3); + let target = BlockNumHash::new(latest_num, latest_hash); + + // First run: lands a Completed snapshot at `target`. + { + let reth_provider = provider_factory.database_provider_ro().unwrap(); + SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).expect("first run"); + } + + // Drop the snapshot — status reverts to NotStarted as far as the init + // anchor is concerned. + { + let sp = storage.backfill_provider().expect("rw"); + sp.clear_snapshot().expect("clear"); + OpProofsBackfillProvider::commit(sp).expect("commit"); + } + + // Second run must succeed (no SnapshotAlreadyExists) and produce a fresh + // Completed snapshot at the same anchor. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = SnapshotInitJob::new(reth_provider, storage).run(latest_num).expect("rebuild"); + assert_eq!(outcome.block, target); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); +} + +/// Negative test for the snapshot's validation safety net. +/// +/// Without this, a bug in `validate_state_root` (wrong cursor factory, wrong +/// target block, inverted compare, …) would let every existing test pass +/// while silently marking a corrupt snapshot `Ready`. +#[test] +fn snapshot_init_aborts_with_state_root_mismatch_when_header_corrupted() { + // Custom chain build — need to perturb the latest block's header before + // commit, so we can't reuse `build_chain_and_initialize_storage`. + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient = Address::repeat_byte(0x42); + const NUM_BLOCKS: u64 = 3; + // Snapshot validates state_root at `target_block` itself (unlike backfill, + // which validates at block_number - 1), so we corrupt the target. + const CORRUPTED_BLOCK: u64 = NUM_BLOCKS; + const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); + + let mut last_hash = chain_spec.genesis_hash(); + for n in 1..=NUM_BLOCKS { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + if n == CORRUPTED_BLOCK { + block.set_state_root(BOGUS_ROOT); + } + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + } + + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(NUM_BLOCKS, last_hash) + .unwrap(); + } + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let err = SnapshotInitJob::new(reth_provider, storage.clone()).run(NUM_BLOCKS).unwrap_err(); + match err { + SnapshotError::StateRootMismatch { block_number, expected, .. } => { + assert_eq!( + block_number, NUM_BLOCKS, + "validation fires at target_block (not target_block - 1)" + ); + assert_eq!(expected, BOGUS_ROOT, "expected root must come from the tampered header"); + } + other => panic!("expected StateRootMismatch, got {other:?}"), + } + + let init_anchor = storage + .snapshot_initialization_provider() + .expect("init") + .snapshot_init_anchor() + .expect("anchor"); + assert_eq!( + init_anchor.status, + SnapshotInitStatus::InProgress, + "meta must stay Building on validation failure", + ); +} + +/// Resume happy path for [`SnapshotInitJob::start_or_resume`] — the +/// `InProgress` arm at the *matching* anchor +#[test] +fn snapshot_init_resumes_from_partial_building_at_matching_anchor() { + let (provider_factory, storage, latest_num, latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + let target = BlockNumHash::new(latest_num, latest_hash); + + let source_count = count_source_account_trie(&storage, latest_num); + assert!( + source_count >= 2, + "resume test needs ≥2 source rows; got {source_count}. \ + Enrich the chain helper (more genesis allocs, multi-slot storage) \ + to restore meaningful resume coverage.", + ); + + // Read the first source row — this is what we'll seed as a "previously + // committed" chunk. Must match a real source row so the snapshot table + // stays consistent with the source for state-root validation. + let first_row = { + let ro = storage.provider_ro().expect("ro"); + let mut cursor = ro.account_trie_cursor(latest_num).expect("cursor"); + let (path, node) = cursor.seek(Nibbles::default()).expect("seek").expect("first row"); + (StoredNibbles(path), node) + }; + + // Plant a partial Building snapshot at `target`: meta + one row. + { + let sp = storage.snapshot_initialization_provider().expect("init"); + sp.set_snapshot_init_anchor(target).expect("plant"); + sp.store_account_trie_snapshot_branches(vec![first_row.clone()]).expect("seed"); + OpProofsSnapshotInitProvider::commit(sp).expect("commit"); + } + + // Sanity: the planted state is what `start_or_resume` will read. + let pre = storage + .snapshot_initialization_provider() + .expect("init") + .snapshot_init_anchor() + .expect("anchor"); + assert_eq!(pre.status, SnapshotInitStatus::InProgress); + assert_eq!(pre.block, Some(target)); + assert_eq!(pre.last_account_trie_key, Some(first_row.0)); + + // Resume. `chunk_size = 1` forces every remaining row through the + // `Some(resume_after)` branch, not just the first iteration. + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = SnapshotInitJob::new(reth_provider, storage.clone()) + .with_chunk_size(1) + .run(latest_num) + .expect("resume"); + + assert_eq!(outcome.block, target); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); + assert_eq!( + outcome.account_nodes_copied as usize, + source_count - 1, + "resumed run must not re-count the seeded row", + ); + + // Destination mirrors source — drain skipped the seeded row and + // appended the rest with no duplicate-key error, no missing rows. + assert_eq!(count_snapshot_account_trie(&storage), source_count); + + // `finalize_ready` ran — snapshot_anchor() returns `target` only when + // meta is Ready. + let post = storage.snapshot_provider_ro().expect("ro").snapshot_anchor().expect("ready"); + assert_eq!(post, target); +} + +fn widen_window(provider_factory: &F, storage: Arc, target_earliest: u64) +where + F: DatabaseProviderFactory< + Provider: reth_provider::DBProvider + + reth_provider::StageCheckpointReader + + reth_provider::ChangeSetReader + + reth_provider::StorageChangeSetReader + + reth_provider::BlockNumReader + + reth_provider::BlockHashReader + + reth_provider::HeaderProvider + + reth_provider::StorageSettingsCache + + Send, + >, +{ + let provider = provider_factory.database_provider_ro().unwrap(); + BackfillJob::new(provider, storage).run(target_earliest).expect("backfill widens window"); +} + +/// Interior-target snapshot — the history-aware cursors at `target` actually +/// have to reconstruct trie state at a block below the tip. +#[test] +fn snapshot_init_at_interior_target() { + // Build chain [1..=5] then backfill earliest to 2 → window [2, 5]. + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + assert_eq!(latest_num, 5); + widen_window(&provider_factory, storage.clone(), 2); + + // Snapshot at interior block 3. + const INTERIOR: u64 = 3; + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = SnapshotInitJob::new(reth_provider, storage.clone()) + .run(INTERIOR) + .expect("interior snapshot"); + + // `outcome.block.hash` resolves to reth's `block_hash(INTERIOR)`; the job + // already validated the snapshot against `header[INTERIOR].state_root` + // (not header[latest]), so reaching `Completed` means the historical + // reconstruction agrees with reth's stored interior root. + assert_eq!(outcome.block.number, INTERIOR); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); + + let sp = storage.snapshot_provider_ro().unwrap(); + let anchor = sp.snapshot_anchor().expect("ready"); + assert_eq!(anchor.number, INTERIOR); +} + +/// Lower-bound rejection: `target_block < window.earliest` must fire +/// `SnapshotInitTargetOutsideWindow`. Existing +/// `snapshot_init_target_outside_window_errors` only hits the `> latest` +/// half; this test pins the other half of the same `if`. +#[test] +fn snapshot_init_target_below_earliest_errors() { + // Window [2, 5] — running with target=1 falls below earliest. + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + assert_eq!(latest_num, 5); + widen_window(&provider_factory, storage.clone(), 2); + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let err = SnapshotInitJob::new(reth_provider, storage).run(1).unwrap_err(); + assert!( + matches!( + err, + SnapshotError::SnapshotInitTargetOutsideWindow { + target_block: 1, + earliest: 2, + latest: 5, + } + ), + "got {err:?}", + ); +} + +/// Inclusive window boundaries: both `target == earliest` and +/// `target == latest` must accept. +#[test] +fn snapshot_init_at_earliest_and_latest_boundaries_succeed() { + // Earliest boundary (target == earliest = 2 in a [2, 5] window). + { + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + assert_eq!(latest_num, 5); + widen_window(&provider_factory, storage.clone(), 2); + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = + SnapshotInitJob::new(reth_provider, storage).run(2).expect("earliest boundary"); + assert_eq!(outcome.block.number, 2); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); + } + + // Latest boundary (target == latest = 5 in a [2, 5] window). + { + let (provider_factory, storage, latest_num, _latest_hash) = + build_chain_with_storage_writes_and_initialize_storage(5); + assert_eq!(latest_num, 5); + widen_window(&provider_factory, storage.clone(), 2); + + let reth_provider = provider_factory.database_provider_ro().unwrap(); + let outcome = SnapshotInitJob::new(reth_provider, storage).run(5).expect("latest boundary"); + assert_eq!(outcome.block.number, 5); + assert_eq!(outcome.status, SnapshotInitStatus::Completed); + } +} diff --git a/vendor/reth-optimism-trie/src/test_utils.rs b/vendor/reth-optimism-trie/src/test_utils.rs new file mode 100644 index 0000000..a81fa3e --- /dev/null +++ b/vendor/reth-optimism-trie/src/test_utils.rs @@ -0,0 +1,331 @@ +//! Shared test fixtures for OP-reth proofs storage tests. +//! +//! Builds real reth-side chains (genesis + EVM-executed blocks) and +//! initializes a V2 MDBX proofs storage on top, so downstream tests +//! (backfill, snapshot, …) can exercise end-to-end pipelines without +//! duplicating the chain-construction boilerplate. +//! +//! Gated on `#[cfg(test)]` and re-exported via `pub(crate)`; not part of the +//! crate's public surface. + +use crate::{MdbxProofsStorageV2, RethTrieStorageLayout, initialize::InitializationJob}; +use alloy_consensus::{Header, TxEip2930, constants::ETH_TO_WEI}; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256}; +use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, MAINNET, MIN_TRANSACTION_GAS}; +use reth_db::Database; +use reth_db_common::init::init_genesis; +use reth_ethereum_primitives::{Block, BlockBody, Receipt, Transaction, TransactionSigned}; +use reth_evm::{ConfigureEvm, execute::Executor}; +use reth_evm_ethereum::EthEvmConfig; +use reth_node_api::{NodePrimitives, NodeTypesWithDB}; +use reth_primitives_traits::{AlloyBlockHeader, Block as _, RecoveredBlock}; +use reth_provider::{ + BlockWriter as _, ExecutionOutcome, HashedPostStateProvider, LatestStateProviderRef, + ProviderFactory, StateRootProvider, StorageSettingsCache, providers::ProviderNodeTypes, + test_utils::create_test_provider_factory_with_chain_spec, +}; +use reth_revm::database::StateProviderDatabase; +use secp256k1::{Keypair, Secp256k1, SecretKey}; +use std::sync::Arc; +use tempfile::TempDir; + +/// Create a fresh V2 MDBX proofs storage backed by a temporary directory. +pub(crate) fn create_storage() -> Arc { + let path = TempDir::new().unwrap(); + Arc::new(MdbxProofsStorageV2::new(path.path()).unwrap()) +} + +pub(crate) fn public_key_to_address(pubkey: secp256k1::PublicKey) -> Address { + let hash = keccak256(&pubkey.serialize_uncompressed()[1..]); + Address::from_slice(&hash[12..]) +} + +/// Deterministic test keypair. Using a fixed secret key (rather than the +/// thread-local OS RNG) keeps the sender address and thus the entire chain's +/// state roots reproducible across runs. +pub(crate) fn deterministic_keypair() -> Keypair { + let secp = Secp256k1::new(); + let secret = SecretKey::from_byte_array([0x42u8; 32]).expect("valid secret"); + Keypair::from_secret_key(&secp, &secret) +} + +fn sign_tx_with_key_pair(key_pair: Keypair, tx: Transaction) -> TransactionSigned { + use alloy_consensus::SignableTransaction; + use reth_primitives_traits::crypto::secp256k1::sign_message; + let secret = B256::from_slice(&key_pair.secret_bytes()); + let sig = sign_message(secret, tx.signature_hash()).unwrap(); + tx.into_signed(sig).into() +} + +/// Pre-allocated contract address for storage-write tests. +const STORAGE_CONTRACT: Address = Address::repeat_byte(0xAB); + +/// Minimal contract that writes `BLOCKNUMBER` (i.e. current block.number) to +/// storage slot 0: +/// +/// ```text +/// 0x43 BLOCKNUMBER push block.number +/// 0x60 0x00 PUSH1 0x00 push slot 0 +/// 0x55 SSTORE store +/// 0x00 STOP +/// ``` +const STORAGE_BYTECODE: [u8; 5] = [0x43, 0x60, 0x00, 0x55, 0x00]; + +pub(crate) fn chain_spec_with_address(address: Address) -> Arc { + let alloc = std::iter::once(( + address, + GenesisAccount { balance: U256::from(10 * ETH_TO_WEI), ..Default::default() }, + )) + .chain(std::iter::once(( + STORAGE_CONTRACT, + GenesisAccount { code: Some(Bytes::from_static(&STORAGE_BYTECODE)), ..Default::default() }, + ))) + .chain((0x10u8..0x30).map(|i| { + ( + Address::repeat_byte(i), + GenesisAccount { balance: U256::from(1u64), ..Default::default() }, + ) + })) + .collect(); + + Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(Genesis { alloc, ..MAINNET.genesis.clone() }) + .paris_activated() + .build(), + ) +} + +/// Construct an unsealed block with a single simple transfer. +pub(crate) fn build_transfer_block( + block_number: u64, + parent_hash: B256, + chain_spec: &Arc, + key_pair: Keypair, + nonce: u64, + recipient: Address, +) -> RecoveredBlock { + let tx = sign_tx_with_key_pair( + key_pair, + TxEip2930 { + chain_id: chain_spec.chain.id(), + nonce, + gas_limit: MIN_TRANSACTION_GAS, + gas_price: 1_500_000_000, + to: TxKind::Call(recipient), + value: U256::from(1), + ..Default::default() + } + .into(), + ); + Block { + header: Header { + parent_hash, + receipts_root: alloy_primitives::b256!( + "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" + ), + difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), + number: block_number, + gas_limit: MIN_TRANSACTION_GAS, + gas_used: MIN_TRANSACTION_GAS, + state_root: B256::ZERO, // filled in by execute_block + ..Default::default() + }, + body: BlockBody { transactions: vec![tx], ..Default::default() }, + } + .try_into_recovered() + .unwrap() +} + +pub(crate) fn execute_block( + block: &mut RecoveredBlock, + provider_factory: &ProviderFactory, + chain_spec: &Arc, +) -> reth_evm::execute::BlockExecutionOutput +where + N: ProviderNodeTypes< + Primitives: NodePrimitives, + > + NodeTypesWithDB, +{ + let provider = provider_factory.provider().unwrap(); + let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); + let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); + let block_executor = evm_config.batch_executor(db); + let execution_result = block_executor.execute(block).unwrap(); + + let hashed_state = + LatestStateProviderRef::new(&provider).hashed_post_state(&execution_result.state); + let state_root = LatestStateProviderRef::new(&provider).state_root(hashed_state).unwrap(); + block.set_state_root(state_root); + execution_result +} + +pub(crate) fn commit_block_to_database( + block: &RecoveredBlock, + execution_output: &reth_evm::execute::BlockExecutionOutput, + provider_factory: &ProviderFactory, +) where + N: ProviderNodeTypes< + Primitives: NodePrimitives, + > + NodeTypesWithDB, +{ + let execution_outcome = ExecutionOutcome { + bundle: execution_output.state.clone(), + receipts: vec![execution_output.receipts.clone()], + first_block: block.number(), + requests: vec![execution_output.requests.clone()], + }; + let state_provider = provider_factory.provider().unwrap(); + let hashed_state = HashedPostStateProvider::hashed_post_state( + &LatestStateProviderRef::new(&state_provider), + &execution_output.state, + ); + let provider_rw = provider_factory.provider_rw().unwrap(); + provider_rw + .append_blocks_with_state( + vec![block.clone()], + &execution_outcome, + hashed_state.into_sorted(), + ) + .unwrap(); + provider_rw.commit().unwrap(); +} + +/// Construct an unsealed block whose sole tx calls [`STORAGE_CONTRACT`], +/// triggering an SSTORE of `block.number` into slot 0 of the contract's storage. +/// +/// Gas accounting: the executor recomputes `gas_used` against the actual EVM +/// trace, so we deliberately set `gas_limit == gas_used` to a value large +/// enough to cover both the 21 000-gas tx base cost and the worst-case cold +/// SSTORE (~22 100 gas). +fn build_storage_call_block( + block_number: u64, + parent_hash: B256, + chain_spec: &Arc, + key_pair: Keypair, + nonce: u64, +) -> RecoveredBlock { + const CALL_GAS_LIMIT: u64 = 100_000; + let tx = sign_tx_with_key_pair( + key_pair, + TxEip2930 { + chain_id: chain_spec.chain.id(), + nonce, + gas_limit: CALL_GAS_LIMIT, + gas_price: 1_500_000_000, + to: TxKind::Call(STORAGE_CONTRACT), + value: U256::ZERO, + ..Default::default() + } + .into(), + ); + Block { + header: Header { + parent_hash, + receipts_root: alloy_primitives::b256!( + "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" + ), + difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), + number: block_number, + gas_limit: CALL_GAS_LIMIT, + gas_used: CALL_GAS_LIMIT, + state_root: B256::ZERO, + ..Default::default() + }, + body: BlockBody { transactions: vec![tx], ..Default::default() }, + } + .try_into_recovered() + .unwrap() +} + +/// Build a chain of `num_blocks` simple transfer blocks on top of a freshly +/// initialized genesis, then initialize the v2 proofs storage at the latest +/// block. Returns the provider factory, the storage, and the latest +/// (number, hash) pair. +pub(crate) fn build_chain_and_initialize_storage( + num_blocks: u64, +) -> ( + ProviderFactory, + Arc, + u64, + B256, +) { + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient = Address::repeat_byte(0x42); + let mut last_hash = chain_spec.genesis_hash(); + let mut last_number = 0u64; + for n in 1..=num_blocks { + let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + last_number = n; + } + + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(last_number, last_hash) + .unwrap(); + } + + (provider_factory, storage, last_number, last_hash) +} + +/// Like [`build_chain_and_initialize_storage`] but every block calls +/// [`STORAGE_CONTRACT`], so each block produces hashed-storage changesets in +/// addition to the account-level ones. +pub(crate) fn build_chain_with_storage_writes_and_initialize_storage( + num_blocks: u64, +) -> ( + ProviderFactory, + Arc, + u64, + B256, +) { + let key_pair = deterministic_keypair(); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let mut last_hash = chain_spec.genesis_hash(); + let mut last_number = 0u64; + for n in 1..=num_blocks { + let mut block = build_storage_call_block(n, last_hash, &chain_spec, key_pair, n - 1); + let exec = execute_block(&mut block, &provider_factory, &chain_spec); + commit_block_to_database(&block, &exec, &provider_factory); + last_hash = block.hash(); + last_number = n; + } + + let storage = create_storage(); + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = provider_factory.db_ref().tx().unwrap(); + InitializationJob::new(storage.clone(), tx, trie_layout) + .run(last_number, last_hash) + .unwrap(); + } + + (provider_factory, storage, last_number, last_hash) +} diff --git a/vendor/reth-optimism-trie/tests/lib.rs b/vendor/reth-optimism-trie/tests/lib.rs new file mode 100644 index 0000000..7573ee3 --- /dev/null +++ b/vendor/reth-optimism-trie/tests/lib.rs @@ -0,0 +1,2163 @@ +//! Common test suite for [`OpProofsStore`] implementations. + +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use alloy_primitives::{B256, U256}; +use reth_optimism_trie::{ + BlockStateDiff, InMemoryProofsStorage, OpProofsInitProvider, OpProofsProviderRO, + OpProofsProviderRw, OpProofsStorageError, OpProofsStore, + db::{MdbxProofsStorage, MdbxProofsStorageV2}, +}; +use reth_primitives_traits::Account; +use reth_trie::{ + BranchNodeCompact, HashedPostState, HashedPostStateSorted, HashedStorage, Nibbles, TrieMask, + hashed_cursor::HashedCursor, + trie_cursor::TrieCursor, + updates::{TrieUpdates, TrieUpdatesSorted}, +}; +use serial_test::serial; +use std::sync::Arc; +use tempfile::TempDir; +use test_case::test_case; + +/// Helper to create a simple test branch node +fn create_test_branch() -> BranchNodeCompact { + let mut state_mask = TrieMask::default(); + state_mask.set_bit(0); + state_mask.set_bit(1); + + BranchNodeCompact { + state_mask, + tree_mask: TrieMask::default(), + hash_mask: TrieMask::default(), + hashes: Arc::new(vec![]), + root_hash: None, + } +} + +/// Helper to create a variant test branch node for comparison tests +fn create_test_branch_variant() -> BranchNodeCompact { + let mut state_mask = TrieMask::default(); + state_mask.set_bit(5); + state_mask.set_bit(6); + + BranchNodeCompact { + state_mask, + tree_mask: TrieMask::default(), + hash_mask: TrieMask::default(), + hashes: Arc::new(vec![]), + root_hash: None, + } +} + +/// Helper to create nibbles from a vector of u8 values +fn nibbles_from(vec: Vec) -> Nibbles { + Nibbles::from_nibbles_unchecked(vec) +} + +/// Helper to create a test account +fn create_test_account() -> Account { + Account { + nonce: 42, + balance: U256::from(1000000), + bytecode_hash: Some(B256::repeat_byte(0xBB)), + } +} + +/// Helper to create a test account with custom values +fn create_test_account_with_values(nonce: u64, balance: u64, code_hash_byte: u8) -> Account { + Account { + nonce, + balance: U256::from(balance), + bytecode_hash: Some(B256::repeat_byte(code_hash_byte)), + } +} + +/// Bootstrap a fresh store via the init flow. Equivalent to running the initial-state +/// preparation step in production. +fn bootstrap_anchor(storage: &S, anchor: BlockNumHash) { + let init = storage.initialization_provider().expect("init provider"); + init.set_initial_state_anchor(anchor).expect("set anchor"); + init.commit_initial_state().expect("commit initial state"); + OpProofsInitProvider::commit(init).expect("commit tx"); +} + +fn create_mdbx_proofs_storage() -> MdbxProofsStorage { + let path = TempDir::new().unwrap(); + let storage = MdbxProofsStorage::new(path.path()).unwrap(); + bootstrap_anchor(&storage, BlockNumHash { number: 0, hash: B256::ZERO }); + storage +} + +fn create_mdbx_proofs_storage_v2() -> MdbxProofsStorageV2 { + let path = TempDir::new().unwrap(); + let storage = MdbxProofsStorageV2::new(path.path()).unwrap(); + bootstrap_anchor(&storage, BlockNumHash { number: 0, hash: B256::ZERO }); + storage +} + +/// Test bootstrap via `commit_initial_state` populates both earliest and latest. +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(MdbxProofsStorage::new(TempDir::new().unwrap().path()).unwrap(); "Mdbx")] +#[test_case(MdbxProofsStorageV2::new(TempDir::new().unwrap().path()).unwrap(); "MdbxV2")] +#[serial] +fn test_bootstrap_initial_state(storage: S) -> Result<(), OpProofsStorageError> { + // Initially both endpoints surface NoBlocksFound. + let provider = storage.provider_ro().expect("provider ro"); + assert!(matches!(provider.get_earliest_block(), Err(OpProofsStorageError::NoBlocksFound))); + assert!(matches!(provider.get_latest_block(), Err(OpProofsStorageError::NoBlocksFound))); + + // Run the init flow. + let block_hash = B256::repeat_byte(0x42); + bootstrap_anchor(&storage, BlockNumHash { number: 100, hash: block_hash }); + + // Both endpoints point at the anchor. + let provider = storage.provider_ro().expect("provider ro"); + assert_eq!(provider.get_earliest_block()?, NumHash::new(100, block_hash)); + assert_eq!(provider.get_latest_block()?, NumHash::new(100, block_hash)); + + Ok(()) +} + +/// Test storing and retrieving trie updates +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_trie_updates_operations(storage: S) -> Result<(), OpProofsStorageError> { + let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); + let sorted_trie_updates = TrieUpdatesSorted::default(); + let sorted_post_state = HashedPostStateSorted::default(); + let block_state_diff = BlockStateDiff { + sorted_trie_updates: sorted_trie_updates.clone(), + sorted_post_state: sorted_post_state.clone(), + }; + + // Store trie updates + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref, block_state_diff)?; + provider_rw.commit()?; + + // Retrieve and verify + let provider_rw = storage.provider_rw().expect("provider ro"); + let retrieved_diff = provider_rw.fetch_trie_updates(block_ref.block.number)?; + provider_rw.commit()?; + assert_eq!(retrieved_diff.sorted_trie_updates, sorted_trie_updates); + assert_eq!(retrieved_diff.sorted_post_state, sorted_post_state); + + Ok(()) +} + +// ============================================================================= +// 1. Basic Cursor Operations +// ============================================================================= + +/// Test cursor operations on empty trie +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_cursor_empty_trie(storage: S) -> Result<(), OpProofsStorageError> { + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // All operations should return None on empty trie + assert!(cursor.seek_exact(Nibbles::default())?.is_none()); + assert!(cursor.seek(Nibbles::default())?.is_none()); + assert!(cursor.next()?.is_none()); + assert!(cursor.current()?.is_none()); + + Ok(()) +} + +/// Test cursor operations with single entry +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_cursor_single_entry(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2, 3]); + let branch = create_test_branch(); + + // Store single entry + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // Test seek_exact + let result = cursor.seek_exact(path)?.unwrap(); + assert_eq!(result.0, path); + + // Test current position + assert_eq!(cursor.current()?.unwrap(), path); + + // Test next from end should return None + assert!(cursor.next()?.is_none()); + + Ok(()) +} + +/// Test cursor operations with multiple entries +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_cursor_multiple_entries(storage: S) -> Result<(), OpProofsStorageError> { + let paths = vec![ + nibbles_from(vec![1]), + nibbles_from(vec![1, 2]), + nibbles_from(vec![2]), + nibbles_from(vec![2, 3]), + ]; + let branch = create_test_branch(); + + // Store multiple entries + let init_provider = storage.initialization_provider().expect("provider ro"); + for path in &paths { + init_provider.store_account_branches(vec![(*path, Some(branch.clone()))])?; + } + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // Test that we can iterate through all entries + let mut found_paths = Vec::new(); + while let Some((path, _)) = cursor.next()? { + found_paths.push(path); + } + + assert_eq!(found_paths.len(), 4); + // Paths should be in lexicographic order + for i in 0..paths.len() { + assert_eq!(found_paths[i], paths[i]); + } + + Ok(()) +} + +// ============================================================================= +// 2. Seek Operations +// ============================================================================= + +/// Test `seek_exact` with existing path +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_exact_existing_path(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2, 3]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let result = cursor.seek_exact(path)?.unwrap(); + assert_eq!(result.0, path); + + Ok(()) +} + +/// Test `seek_exact` with non-existing path +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_exact_non_existing_path( + storage: S, +) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2, 3]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let non_existing = nibbles_from(vec![4, 5, 6]); + assert!(cursor.seek_exact(non_existing)?.is_none()); + + Ok(()) +} + +/// Test `seek_exact` with empty path +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_exact_empty_path(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let result = cursor.seek_exact(Nibbles::default())?.unwrap(); + assert_eq!(result.0, Nibbles::default()); + + Ok(()) +} + +/// Test seek to existing path +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_to_existing_path(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2, 3]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let result = cursor.seek(path)?.unwrap(); + assert_eq!(result.0, path); + + Ok(()) +} + +/// Test seek between existing nodes +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_between_existing_nodes( + storage: S, +) -> Result<(), OpProofsStorageError> { + let path1 = nibbles_from(vec![1]); + let path2 = nibbles_from(vec![3]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path1, Some(branch.clone()))])?; + init_provider.store_account_branches(vec![(path2, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + // Seek to path between 1 and 3, should return path 3 + let seek_path = nibbles_from(vec![2]); + let result = cursor.seek(seek_path)?.unwrap(); + assert_eq!(result.0, path2); + + Ok(()) +} + +/// Test seek after all nodes +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_after_all_nodes(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + // Seek to path after all nodes + let seek_path = nibbles_from(vec![9]); + assert!(cursor.seek(seek_path)?.is_none()); + + Ok(()) +} + +/// Test seek before all nodes +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_seek_before_all_nodes(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![5]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + // Seek to path before all nodes, should return first node + let seek_path = nibbles_from(vec![1]); + let result = cursor.seek(seek_path)?.unwrap(); + assert_eq!(result.0, path); + + Ok(()) +} + +// ============================================================================= +// 3. Navigation Tests +// ============================================================================= + +/// Test next without prior seek +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_next_without_prior_seek(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + // next() without prior seek should start from beginning + let result = cursor.next()?.unwrap(); + assert_eq!(result.0, path); + + Ok(()) +} + +/// Test next after seek +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_next_after_seek(storage: S) -> Result<(), OpProofsStorageError> { + let path1 = nibbles_from(vec![1]); + let path2 = nibbles_from(vec![2]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path1, Some(branch.clone()))])?; + init_provider.store_account_branches(vec![(path2, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + cursor.seek(path1)?; + + // next() should return second node + let result = cursor.next()?.unwrap(); + assert_eq!(result.0, path2); + + Ok(()) +} + +/// Test next at end of trie +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_next_at_end_of_trie(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + cursor.seek(path)?; + + // next() at end should return None + assert!(cursor.next()?.is_none()); + + Ok(()) +} + +/// Test multiple consecutive next calls +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_multiple_consecutive_next( + storage: S, +) -> Result<(), OpProofsStorageError> { + let paths = vec![nibbles_from(vec![1]), nibbles_from(vec![2]), nibbles_from(vec![3])]; + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + for path in &paths { + init_provider.store_account_branches(vec![(*path, Some(branch.clone()))])?; + } + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // Iterate through all with consecutive next() calls + for expected_path in &paths { + let result = cursor.next()?.unwrap(); + assert_eq!(result.0, *expected_path); + } + + // Final next() should return None + assert!(cursor.next()?.is_none()); + + Ok(()) +} + +/// Test current after operations +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_current_after_operations(storage: S) -> Result<(), OpProofsStorageError> { + let path1 = nibbles_from(vec![1]); + let path2 = nibbles_from(vec![2]); + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path1, Some(branch.clone()))])?; + init_provider.store_account_branches(vec![(path2, Some(branch))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // Current should be None initially + assert!(cursor.current()?.is_none()); + + // After seek, current should track position + cursor.seek(path1)?; + assert_eq!(cursor.current()?.unwrap(), path1); + + // After next, current should update + cursor.next()?; + assert_eq!(cursor.current()?.unwrap(), path2); + + Ok(()) +} + +/// Test current with no prior operations +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_current_no_prior_operations( + storage: S, +) -> Result<(), OpProofsStorageError> { + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // Current should be None when no operations performed + assert!(cursor.current()?.is_none()); + + Ok(()) +} + +// ============================================================================= +// 4. Block Number Filtering +// ============================================================================= + +/// Test same path with different blocks +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_same_path_different_blocks( + storage: S, +) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2]); + let branch1 = create_test_branch(); + let branch2 = create_test_branch_variant(); + + // Store same path at different blocks + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch1))])?; + init_provider.store_account_branches(vec![(path, Some(branch2))])?; + init_provider.commit()?; + + // Cursor with max_block_number=75 should see only block 50 data + let mut cursor75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + let result75 = cursor75.seek_exact(path)?.unwrap(); + assert_eq!(result75.0, path); + + // Cursor with max_block_number=150 should see block 100 data (latest) + let mut cursor150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + let result150 = cursor150.seek_exact(path)?.unwrap(); + assert_eq!(result150.0, path); + + Ok(()) +} + +/// Test deleted branch nodes +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_deleted_branch_nodes(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2]); + let branch = create_test_branch(); + let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); + + // Store branch node, then delete it (store None) + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + // Cursor before deletion should see the node + let mut cursor75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + assert!(cursor75.seek_exact(path)?.is_some()); + + let mut block_state_diff_trie_updates = TrieUpdates::default(); + block_state_diff_trie_updates.removed_nodes.insert(path); + let block_state_diff = BlockStateDiff { + sorted_trie_updates: block_state_diff_trie_updates.into_sorted(), + sorted_post_state: HashedPostStateSorted::default(), + }; + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref, block_state_diff)?; + provider_rw.commit()?; + + // Cursor after deletion should not see the node + let mut cursor150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + assert!(cursor150.seek_exact(path)?.is_none()); + + Ok(()) +} + +// ============================================================================= +// 5. Hashed Address Filtering +// ============================================================================= + +/// Test account-specific cursor +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_account_specific_cursor(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2]); + let addr1 = B256::repeat_byte(0x01); + let addr2 = B256::repeat_byte(0x02); + let branch = create_test_branch(); + + // Store same path for different accounts (using storage branches) + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_storage_branches(addr1, vec![(path, Some(branch.clone()))])?; + init_provider.store_storage_branches(addr2, vec![(path, Some(branch))])?; + init_provider.commit()?; + + // Cursor for addr1 should only see addr1 data + let mut cursor1 = + storage.provider_ro().expect("provider ro").storage_trie_cursor(addr1, 100)?; + let result1 = cursor1.seek_exact(path)?.unwrap(); + assert_eq!(result1.0, path); + + // Cursor for addr2 should only see addr2 data + let mut cursor2 = + storage.provider_ro().expect("provider ro").storage_trie_cursor(addr2, 100)?; + let result2 = cursor2.seek_exact(path)?.unwrap(); + assert_eq!(result2.0, path); + + // Cursor for addr1 should not see addr2 data when iterating + let mut cursor1_iter = + storage.provider_ro().expect("provider ro").storage_trie_cursor(addr1, 100)?; + let mut found_count = 0; + while cursor1_iter.next()?.is_some() { + found_count += 1; + } + assert_eq!(found_count, 1); // Should only see one entry (for addr1) + + Ok(()) +} + +/// Test state trie cursor +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_state_trie_cursor(storage: S) -> Result<(), OpProofsStorageError> { + let path = nibbles_from(vec![1, 2]); + let addr = B256::repeat_byte(0x01); + let branch = create_test_branch(); + + // Store data for account trie and state trie + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_storage_branches(addr, vec![(path, Some(branch.clone()))])?; + init_provider.store_account_branches(vec![(path, Some(branch))])?; + init_provider.commit()?; + + // State trie cursor (None address) should only see state trie data + let mut state_cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let result = state_cursor.seek_exact(path)?.unwrap(); + assert_eq!(result.0, path); + + // Verify state cursor doesn't see account data when iterating + let mut state_cursor_iter = + storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let mut found_count = 0; + while state_cursor_iter.next()?.is_some() { + found_count += 1; + } + + assert_eq!(found_count, 1); // Should only see state trie entry + + Ok(()) +} + +/// Test mixed account and state data +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_mixed_account_state_data(storage: S) -> Result<(), OpProofsStorageError> { + let path1 = nibbles_from(vec![1]); + let path2 = nibbles_from(vec![2]); + let addr = B256::repeat_byte(0x01); + let branch = create_test_branch(); + + // Store mixed account and state trie data + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_storage_branches(addr, vec![(path1, Some(branch.clone()))])?; + init_provider.store_account_branches(vec![(path2, Some(branch))])?; + init_provider.commit()?; + + // Account cursor should only see account data + let mut account_cursor = + storage.provider_ro().expect("provider ro").storage_trie_cursor(addr, 100)?; + let mut account_paths = Vec::new(); + while let Some((path, _)) = account_cursor.next()? { + account_paths.push(path); + } + assert_eq!(account_paths.len(), 1); + assert_eq!(account_paths[0], path1); + + // State cursor should only see state data + let mut state_cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let mut state_paths = Vec::new(); + while let Some((path, _)) = state_cursor.next()? { + state_paths.push(path); + } + assert_eq!(state_paths.len(), 1); + assert_eq!(state_paths[0], path2); + + Ok(()) +} + +// ============================================================================= +// 6. Path Ordering Tests +// ============================================================================= + +/// Test lexicographic ordering +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_lexicographic_ordering(storage: S) -> Result<(), OpProofsStorageError> { + let paths = vec![ + nibbles_from(vec![3, 1]), + nibbles_from(vec![1, 2]), + nibbles_from(vec![2]), + nibbles_from(vec![1]), + ]; + let branch = create_test_branch(); + + // Store paths in sorted order (init provider requires sorted input, like a real trie walk) + let mut sorted_paths = paths; + sorted_paths.sort(); + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches( + sorted_paths.into_iter().map(|p| (p, Some(branch.clone()))).collect(), + )?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let mut found_paths = Vec::new(); + while let Some((path, _)) = cursor.next()? { + found_paths.push(path); + } + + // Should be returned in lexicographic order: [1], [1,2], [2], [3,1] + let expected_order = vec![ + nibbles_from(vec![1]), + nibbles_from(vec![1, 2]), + nibbles_from(vec![2]), + nibbles_from(vec![3, 1]), + ]; + + assert_eq!(found_paths, expected_order); + + Ok(()) +} + +/// Test path prefix scenarios +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_path_prefix_scenarios(storage: S) -> Result<(), OpProofsStorageError> { + let paths = vec![ + nibbles_from(vec![1]), // Prefix of next + nibbles_from(vec![1, 2]), // Extends first + nibbles_from(vec![1, 2, 3]), // Extends second + ]; + let branch = create_test_branch(); + + let init_provider = storage.initialization_provider().expect("provider ro"); + for path in &paths { + init_provider.store_account_branches(vec![(*path, Some(branch.clone()))])?; + } + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + + // Seek to prefix should find exact match + let result = cursor.seek_exact(paths[0])?.unwrap(); + assert_eq!(result.0, paths[0]); + + // Next should go to next path, not skip prefixed paths + let result = cursor.next()?.unwrap(); + assert_eq!(result.0, paths[1]); + + let result = cursor.next()?.unwrap(); + assert_eq!(result.0, paths[2]); + + Ok(()) +} + +/// Test complex nibble combinations +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_complex_nibble_combinations( + storage: S, +) -> Result<(), OpProofsStorageError> { + // Test various nibble patterns including edge values + let paths = vec![ + nibbles_from(vec![0]), + nibbles_from(vec![0, 15]), + nibbles_from(vec![15]), + nibbles_from(vec![15, 0]), + nibbles_from(vec![7, 8, 9]), + ]; + let branch = create_test_branch(); + + // Store paths in sorted order (init provider requires sorted input, like a real trie walk) + let mut sorted_paths = paths; + sorted_paths.sort(); + let init_provider = storage.initialization_provider().expect("provider ro"); + init_provider.store_account_branches( + sorted_paths.into_iter().map(|p| (p, Some(branch.clone()))).collect(), + )?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + let mut found_paths = Vec::new(); + while let Some((path, _)) = cursor.next()? { + found_paths.push(path); + } + + // All paths should be found and in correct order + assert_eq!(found_paths.len(), 5); + + // Verify specific ordering for edge cases + assert_eq!(found_paths[0], nibbles_from(vec![0])); + assert_eq!(found_paths[1], nibbles_from(vec![0, 15])); + assert_eq!(found_paths[4], nibbles_from(vec![15, 0])); + + Ok(()) +} + +// ============================================================================= +// 7. Leaf Node Tests (Hashed Accounts and Storage) +// ============================================================================= + +/// Test store and retrieve single account +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_store_and_retrieve_single_account( + storage: S, +) -> Result<(), OpProofsStorageError> { + let account_key = B256::repeat_byte(0x01); + let account = create_test_account(); + + // Store account + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_accounts(vec![(account_key, Some(account))])?; + init_provider.commit()?; + + // Retrieve via cursor + let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; + let result = cursor.seek(account_key)?.unwrap(); + + assert_eq!(result.0, account_key); + assert_eq!(result.1.nonce, account.nonce); + assert_eq!(result.1.balance, account.balance); + assert_eq!(result.1.bytecode_hash, account.bytecode_hash); + + Ok(()) +} + +/// Test account cursor navigation +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_account_cursor_navigation( + storage: S, +) -> Result<(), OpProofsStorageError> { + let accounts = [ + (B256::repeat_byte(0x01), create_test_account()), + (B256::repeat_byte(0x03), create_test_account()), + (B256::repeat_byte(0x05), create_test_account()), + ]; + + // Store accounts + let accounts_to_store: Vec<_> = accounts.iter().map(|(k, v)| (*k, Some(*v))).collect(); + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_accounts(accounts_to_store)?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; + + // Test seeking to exact key + let result = cursor.seek(accounts[1].0)?.unwrap(); + assert_eq!(result.0, accounts[1].0); + + // Test seeking to key that doesn't exist (should return next greater) + let seek_key = B256::repeat_byte(0x02); + let result = cursor.seek(seek_key)?.unwrap(); + assert_eq!(result.0, accounts[1].0); // Should find 0x03 + + // Test next() navigation + let result = cursor.next()?.unwrap(); + assert_eq!(result.0, accounts[2].0); // Should find 0x05 + + // Test next() at end + assert!(cursor.next()?.is_none()); + + Ok(()) +} + +/// Test account block versioning +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_account_block_versioning(storage: S) -> Result<(), OpProofsStorageError> { + let account_key = B256::repeat_byte(0x01); + let account_v1 = create_test_account_with_values(1, 100, 0xBB); + let account_v2 = create_test_account_with_values(2, 200, 0xDD); + + // Store account at different blocks + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_accounts(vec![(account_key, Some(account_v1))])?; + init_provider.commit()?; + + // Cursor with max_block_number=75 should see v1 + let mut cursor75 = storage.provider_ro().expect("provider ro").account_hashed_cursor(75)?; + let result75 = cursor75.seek(account_key)?.unwrap(); + assert_eq!(result75.1.nonce, account_v1.nonce); + assert_eq!(result75.1.balance, account_v1.balance); + + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_accounts(vec![(account_key, Some(account_v2))])?; + init_provider.commit()?; + + // After update, Cursor with max_block_number=150 should see v2 + let mut cursor150 = storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; + let result150 = cursor150.seek(account_key)?.unwrap(); + assert_eq!(result150.1.nonce, account_v2.nonce); + assert_eq!(result150.1.balance, account_v2.balance); + + Ok(()) +} + +/// Test store and retrieve storage +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] + +fn test_store_and_retrieve_storage( + storage: S, +) -> Result<(), OpProofsStorageError> { + let hashed_address = B256::repeat_byte(0x01); + let storage_slots = vec![ + (B256::repeat_byte(0x10), U256::from(100)), + (B256::repeat_byte(0x20), U256::from(200)), + (B256::repeat_byte(0x30), U256::from(300)), + ]; + + // Store storage slots + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(hashed_address, storage_slots.clone())?; + init_provider.commit()?; + + // Retrieve via cursor + let mut cursor = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; + + // Test seeking to each slot + for (key, expected_value) in &storage_slots { + let result = cursor.seek(*key)?.unwrap(); + assert_eq!(result.0, *key); + assert_eq!(result.1, *expected_value); + } + + Ok(()) +} + +/// Test storage cursor navigation +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_storage_cursor_navigation( + storage: S, +) -> Result<(), OpProofsStorageError> { + let hashed_address = B256::repeat_byte(0x01); + let storage_slots = vec![ + (B256::repeat_byte(0x10), U256::from(100)), + (B256::repeat_byte(0x30), U256::from(300)), + (B256::repeat_byte(0x50), U256::from(500)), + ]; + + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(hashed_address, storage_slots.clone())?; + init_provider.commit()?; + + let mut cursor = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; + + // Start from beginning with next() + let mut found_slots = Vec::new(); + while let Some((key, value)) = cursor.next()? { + found_slots.push((key, value)); + } + + assert_eq!(found_slots.len(), 3); + assert_eq!(found_slots[0], storage_slots[0]); + assert_eq!(found_slots[1], storage_slots[1]); + assert_eq!(found_slots[2], storage_slots[2]); + + Ok(()) +} + +/// Test storage account isolation +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_storage_account_isolation( + storage: S, +) -> Result<(), OpProofsStorageError> { + let address1 = B256::repeat_byte(0x01); + let address2 = B256::repeat_byte(0x02); + let storage_key = B256::repeat_byte(0x10); + + // Store same storage key for different accounts + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(address1, vec![(storage_key, U256::from(100))])?; + init_provider.store_hashed_storages(address2, vec![(storage_key, U256::from(200))])?; + init_provider.commit()?; + + // Verify each account sees only its own storage + let mut cursor1 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(address1, 100)?; + let result1 = cursor1.seek(storage_key)?.unwrap(); + assert_eq!(result1.1, U256::from(100)); + + let mut cursor2 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(address2, 100)?; + let result2 = cursor2.seek(storage_key)?.unwrap(); + assert_eq!(result2.1, U256::from(200)); + + // Verify cursor1 doesn't see address2's storage + let mut cursor1_iter = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(address1, 100)?; + let mut count = 0; + while cursor1_iter.next()?.is_some() { + count += 1; + } + assert_eq!(count, 1); // Should only see one entry + + Ok(()) +} + +/// Test storage block versioning +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_storage_block_versioning(storage: S) -> Result<(), OpProofsStorageError> { + let hashed_address = B256::repeat_byte(0x01); + let storage_key = B256::repeat_byte(0x10); + + // Set up initial state with value 100 (init_provider writes current-state only) + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(hashed_address, vec![(storage_key, U256::from(100))])?; + init_provider.commit()?; + + // Apply a block-level update at block 100 that changes the value to 200. + // This records changeset (old=100) + history bitmap + updates current state. + let mut block_state_diff_post_state = HashedPostState::default(); + let mut hashed_storage = HashedStorage::default(); + hashed_storage.storage.insert(storage_key, U256::from(200)); + block_state_diff_post_state.storages.insert(hashed_address, hashed_storage); + + let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); + let block_state_diff = BlockStateDiff { + sorted_trie_updates: TrieUpdatesSorted::default(), + sorted_post_state: block_state_diff_post_state.into_sorted(), + }; + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref, block_state_diff)?; + provider_rw.commit()?; + + // Cursor with max_block_number=75 should see old value (before block 100 update) + let mut cursor75 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; + let result75 = cursor75.seek(storage_key)?.unwrap(); + assert_eq!(result75.1, U256::from(100)); + + // Cursor with max_block_number=150 should see new value (after block 100 update) + let mut cursor150 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 150)?; + let result150 = cursor150.seek(storage_key)?.unwrap(); + assert_eq!(result150.1, U256::from(200)); + + Ok(()) +} + +/// Test storage zero value deletion +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_storage_zero_value_deletion( + storage: S, +) -> Result<(), OpProofsStorageError> { + let hashed_address = B256::repeat_byte(0x01); + let storage_key = B256::repeat_byte(0x10); + + // Store non-zero value + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(hashed_address, vec![(storage_key, U256::from(100))])?; + init_provider.commit()?; + + // Cursor before deletion should see the value + let mut cursor75 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; + let result75 = cursor75.seek(storage_key)?.unwrap(); + assert_eq!(result75.1, U256::from(100)); + + // "Delete" by storing zero value at block 100 + let mut block_state_diff_post_state = HashedPostState::default(); + let mut hashed_storage = HashedStorage::default(); + hashed_storage.storage.insert(storage_key, U256::ZERO); + block_state_diff_post_state.storages.insert(hashed_address, hashed_storage); + + let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); + let block_state_diff = BlockStateDiff { + sorted_trie_updates: TrieUpdatesSorted::default(), + sorted_post_state: block_state_diff_post_state.into_sorted(), + }; + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref, block_state_diff)?; + provider_rw.commit()?; + + // Cursor after deletion should NOT see the entry (zero values are skipped) + let mut cursor150 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 150)?; + let result150 = cursor150.seek(storage_key)?; + assert!(result150.is_none(), "Zero values should be skipped/deleted"); + + Ok(()) +} + +/// Test that zero values are skipped during iteration +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_storage_cursor_skips_zero_values( + storage: S, +) -> Result<(), OpProofsStorageError> { + let hashed_address = B256::repeat_byte(0x01); + + // Create a mix of non-zero and zero value storage slots + let storage_slots = vec![ + (B256::repeat_byte(0x10), U256::from(100)), // Non-zero + (B256::repeat_byte(0x20), U256::ZERO), // Zero value - should be skipped + (B256::repeat_byte(0x30), U256::from(300)), // Non-zero + (B256::repeat_byte(0x40), U256::ZERO), // Zero value - should be skipped + (B256::repeat_byte(0x50), U256::from(500)), // Non-zero + ]; + + // Store all slots + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(hashed_address, storage_slots)?; + init_provider.commit()?; + + // Create cursor and iterate through all entries + let mut cursor = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; + let mut found_slots = Vec::new(); + while let Some((key, value)) = cursor.next()? { + found_slots.push((key, value)); + } + + // Should only find 3 non-zero values + assert_eq!(found_slots.len(), 3, "Zero values should be skipped during iteration"); + + // Verify the non-zero values are the ones we stored + assert_eq!(found_slots[0], (B256::repeat_byte(0x10), U256::from(100))); + assert_eq!(found_slots[1], (B256::repeat_byte(0x30), U256::from(300))); + assert_eq!(found_slots[2], (B256::repeat_byte(0x50), U256::from(500))); + + // Verify seeking to a zero-value slot returns None or skips to next non-zero + let mut seek_cursor = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; + let seek_result = seek_cursor.seek(B256::repeat_byte(0x20))?; + + // Should either return None or skip to the next non-zero value (0x30) + if let Some((key, value)) = seek_result { + assert_eq!(key, B256::repeat_byte(0x30), "Should skip zero value and find next non-zero"); + assert_eq!(value, U256::from(300)); + } + + Ok(()) +} + +/// Test empty cursors +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_empty_cursors(storage: S) -> Result<(), OpProofsStorageError> { + // Test empty account cursor + let mut account_cursor = + storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; + assert!(account_cursor.seek(B256::repeat_byte(0x01))?.is_none()); + assert!(account_cursor.next()?.is_none()); + + // Test empty storage cursor + let mut storage_cursor = storage + .provider_ro() + .expect("provider ro") + .storage_hashed_cursor(B256::repeat_byte(0x01), 100)?; + assert!(storage_cursor.seek(B256::repeat_byte(0x10))?.is_none()); + assert!(storage_cursor.next()?.is_none()); + + Ok(()) +} + +/// Test cursor boundary conditions +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_cursor_boundary_conditions( + storage: S, +) -> Result<(), OpProofsStorageError> { + let account_key = B256::repeat_byte(0x80); // Middle value + let account = create_test_account(); + + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_accounts(vec![(account_key, Some(account))])?; + init_provider.commit()?; + + let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; + + // Seek to minimum key should find our account + let result = cursor.seek(B256::ZERO)?.unwrap(); + assert_eq!(result.0, account_key); + + // Seek to maximum key should find nothing + assert!(cursor.seek(B256::repeat_byte(0xFF))?.is_none()); + + // Seek to key just before our account should find our account + let just_before = B256::repeat_byte(0x7F); + let result = cursor.seek(just_before)?.unwrap(); + assert_eq!(result.0, account_key); + + Ok(()) +} + +/// Test large batch operations +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_large_batch_operations(storage: S) -> Result<(), OpProofsStorageError> { + // Create large batch of accounts + let mut accounts = Vec::new(); + for i in 0..100 { + let key = B256::from([i as u8; 32]); + let account = create_test_account_with_values(i, i * 1000, (i + 1) as u8); + accounts.push((key, Some(account))); + } + + // Store in batch + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_accounts(accounts.clone())?; + init_provider.commit()?; + + // Verify all accounts can be retrieved + let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; + let mut found_count = 0; + while cursor.next()?.is_some() { + found_count += 1; + } + assert_eq!(found_count, 100); + + // Test specific account retrieval + let test_key = B256::from([42u8; 32]); + let result = cursor.seek(test_key)?.unwrap(); + assert_eq!(result.0, test_key); + assert_eq!(result.1.nonce, 42); + + Ok(()) +} + +/// Test wiped storage in [`HashedPostState`] +/// +/// When `store_trie_updates` receives a [`HashedPostState`] with wiped=true for a storage entry, +/// it should iterate all existing values for that address and create deletion entries for them. +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_store_trie_updates_with_wiped_storage( + storage: S, +) -> Result<(), OpProofsStorageError> { + use reth_trie::HashedStorage; + + let hashed_address = B256::repeat_byte(0x01); + let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); + + // First, store some storage values at block 50 + let storage_slots = vec![ + (B256::repeat_byte(0x10), U256::from(100)), + (B256::repeat_byte(0x20), U256::from(200)), + (B256::repeat_byte(0x30), U256::from(300)), + (B256::repeat_byte(0x40), U256::from(400)), + ]; + + let init_provider = storage.initialization_provider().expect("provider rw"); + init_provider.store_hashed_storages(hashed_address, storage_slots.clone())?; + init_provider.commit()?; + + // Verify all values are present at block 75 + let mut cursor75 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; + let mut found_slots = Vec::new(); + while let Some((key, value)) = cursor75.next()? { + found_slots.push((key, value)); + } + assert_eq!(found_slots.len(), 4, "All storage slots should be present before wipe"); + assert_eq!(found_slots[0], (B256::repeat_byte(0x10), U256::from(100))); + assert_eq!(found_slots[1], (B256::repeat_byte(0x20), U256::from(200))); + assert_eq!(found_slots[2], (B256::repeat_byte(0x30), U256::from(300))); + assert_eq!(found_slots[3], (B256::repeat_byte(0x40), U256::from(400))); + + // Now create a HashedPostState with wiped=true for this address at block 100 + let mut post_state = HashedPostState::default(); + let wiped_storage = HashedStorage::new(true); // wiped=true, empty storage map + post_state.storages.insert(hashed_address, wiped_storage); + + let block_state_diff = BlockStateDiff { + sorted_trie_updates: TrieUpdatesSorted::default(), + sorted_post_state: post_state.into_sorted(), + }; + + // Store the wiped state + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref, block_state_diff)?; + provider_rw.commit()?; + + // After wiping, cursor at block 150 should see NO storage values + let mut cursor150 = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 150)?; + let mut found_slots_after_wipe = Vec::new(); + while let Some((key, value)) = cursor150.next()? { + found_slots_after_wipe.push((key, value)); + } + + assert_eq!( + found_slots_after_wipe.len(), + 0, + "All storage slots should be deleted after wipe. Found: {:?}", + found_slots_after_wipe + ); + + // Verify individual seeks also return None + for (slot, _) in &storage_slots { + let mut seek_cursor = storage + .provider_ro() + .expect("provider ro") + .storage_hashed_cursor(hashed_address, 150)?; + let result = seek_cursor.seek(*slot)?; + assert!( + result.is_none() || result.unwrap().0 != *slot, + "Storage slot {:?} should be deleted after wipe", + slot + ); + } + + // Verify cursor at block 75 (before wipe) still sees all values + let mut cursor75_after = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; + let mut found_slots_before_wipe = Vec::new(); + while let Some((key, value)) = cursor75_after.next()? { + found_slots_before_wipe.push((key, value)); + } + assert_eq!( + found_slots_before_wipe.len(), + 4, + "All storage slots should still be present when querying before wipe block" + ); + + Ok(()) +} + +/// Test that `store_trie_updates` properly stores branch nodes, leaf nodes, and removals +/// +/// This test verifies that all data stored via `store_trie_updates` can be read back +/// through the cursor APIs. +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_store_trie_updates_comprehensive( + storage: S, +) -> Result<(), OpProofsStorageError> { + use reth_trie::{HashedStorage, updates::StorageTrieUpdates}; + + let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); + + // Create comprehensive trie updates with branches, leaves, and removals + let mut trie_updates = TrieUpdates::default(); + + // Add account branch nodes + let account_path1 = nibbles_from(vec![1, 2, 3]); + let account_path2 = nibbles_from(vec![4, 5, 6]); + let account_branch1 = create_test_branch(); + let account_branch2 = create_test_branch_variant(); + + trie_updates.account_nodes.insert(account_path1, account_branch1); + trie_updates.account_nodes.insert(account_path2, account_branch2); + + // Add removed account nodes + let removed_account_path = nibbles_from(vec![7, 8, 9]); + trie_updates.removed_nodes.insert(removed_account_path); + + // Add storage branch nodes for an address + let hashed_address = B256::repeat_byte(0x42); + let storage_path1 = nibbles_from(vec![1, 1]); + let storage_path2 = nibbles_from(vec![2, 2]); + let storage_branch = create_test_branch(); + + let mut storage_trie = StorageTrieUpdates::default(); + storage_trie.storage_nodes.insert(storage_path1, storage_branch.clone()); + storage_trie.storage_nodes.insert(storage_path2, storage_branch); + + // Add removed storage node + let removed_storage_path = nibbles_from(vec![3, 3]); + storage_trie.removed_nodes.insert(removed_storage_path); + + trie_updates.insert_storage_updates(hashed_address, storage_trie); + + // Create post state with accounts and storage + let mut post_state = HashedPostState::default(); + + // Add accounts + let account1_addr = B256::repeat_byte(0x10); + let account2_addr = B256::repeat_byte(0x20); + let account1 = create_test_account_with_values(1, 1000, 0xAA); + let account2 = create_test_account_with_values(2, 2000, 0xBB); + + post_state.accounts.insert(account1_addr, Some(account1)); + post_state.accounts.insert(account2_addr, Some(account2)); + + // Add deleted account + let deleted_account_addr = B256::repeat_byte(0x30); + post_state.accounts.insert(deleted_account_addr, None); + + // Add storage for an address + let storage_addr = B256::repeat_byte(0x50); + let mut hashed_storage = HashedStorage::new(false); + hashed_storage.storage.insert(B256::repeat_byte(0x01), U256::from(111)); + hashed_storage.storage.insert(B256::repeat_byte(0x02), U256::from(222)); + hashed_storage.storage.insert(B256::repeat_byte(0x03), U256::ZERO); // Deleted storage + post_state.storages.insert(storage_addr, hashed_storage); + + let block_state_diff = BlockStateDiff { + sorted_trie_updates: trie_updates.into_sorted(), + sorted_post_state: post_state.into_sorted(), + }; + + // Store the updates + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref, block_state_diff)?; + provider_rw.commit()?; + + // ========== Verify Account Branch Nodes ========== + let mut account_trie_cursor = storage + .provider_ro() + .expect("provider ro") + .account_trie_cursor(block_ref.block.number + 10)?; + + // Should find the added branches + let result1 = account_trie_cursor.seek_exact(account_path1)?; + assert!(result1.is_some(), "Account branch node 1 should be found"); + assert_eq!(result1.unwrap().0, account_path1); + + let result2 = account_trie_cursor.seek_exact(account_path2)?; + assert!(result2.is_some(), "Account branch node 2 should be found"); + assert_eq!(result2.unwrap().0, account_path2); + + // Removed node should not be found + let removed_result = account_trie_cursor.seek_exact(removed_account_path)?; + assert!(removed_result.is_none(), "Removed account node should not be found"); + + // ========== Verify Storage Branch Nodes ========== + let mut storage_trie_cursor = storage + .provider_ro() + .expect("provider ro") + .storage_trie_cursor(hashed_address, block_ref.block.number + 10)?; + + let storage_result1 = storage_trie_cursor.seek_exact(storage_path1)?; + assert!(storage_result1.is_some(), "Storage branch node 1 should be found"); + + let storage_result2 = storage_trie_cursor.seek_exact(storage_path2)?; + assert!(storage_result2.is_some(), "Storage branch node 2 should be found"); + + // Removed storage node should not be found + let removed_storage_result = storage_trie_cursor.seek_exact(removed_storage_path)?; + assert!(removed_storage_result.is_none(), "Removed storage node should not be found"); + + // ========== Verify Account Leaves ========== + let mut account_cursor = storage + .provider_ro() + .expect("provider ro") + .account_hashed_cursor(block_ref.block.number + 10)?; + + let acc1_result = account_cursor.seek(account1_addr)?; + assert!(acc1_result.is_some(), "Account 1 should be found"); + assert_eq!(acc1_result.unwrap().0, account1_addr); + assert_eq!(acc1_result.unwrap().1.nonce, 1); + assert_eq!(acc1_result.unwrap().1.balance, U256::from(1000)); + + let acc2_result = account_cursor.seek(account2_addr)?; + assert!(acc2_result.is_some(), "Account 2 should be found"); + assert_eq!(acc2_result.unwrap().1.nonce, 2); + + // Deleted account should not be found + let deleted_acc_result = account_cursor.seek(deleted_account_addr)?; + assert!( + deleted_acc_result.is_none() || deleted_acc_result.unwrap().0 != deleted_account_addr, + "Deleted account should not be found" + ); + + // ========== Verify Storage Leaves ========== + let mut storage_cursor = storage + .provider_ro() + .expect("provider ro") + .storage_hashed_cursor(storage_addr, block_ref.block.number + 10)?; + + let slot1_result = storage_cursor.seek(B256::repeat_byte(0x01))?; + assert!(slot1_result.is_some(), "Storage slot 1 should be found"); + assert_eq!(slot1_result.unwrap().1, U256::from(111)); + + let slot2_result = storage_cursor.seek(B256::repeat_byte(0x02))?; + assert!(slot2_result.is_some(), "Storage slot 2 should be found"); + assert_eq!(slot2_result.unwrap().1, U256::from(222)); + + // Zero-valued storage should not be found (deleted) + let slot3_result = storage_cursor.seek(B256::repeat_byte(0x03))?; + assert!( + slot3_result.is_none() || slot3_result.unwrap().0 != B256::repeat_byte(0x03), + "Zero-valued storage slot should not be found" + ); + + // ========== Verify fetch_trie_updates can retrieve the data ========== + let provider_rw = storage.provider_rw().expect("provider ro"); + let fetched_diff = provider_rw.fetch_trie_updates(block_ref.block.number)?; + provider_rw.commit()?; + + // Check that trie updates are stored + assert_eq!( + fetched_diff.sorted_trie_updates.account_nodes_ref().len(), + 3, + "Should have 3 account nodes, including removed" + ); + assert_eq!( + fetched_diff.sorted_trie_updates.storage_tries_ref().len(), + 1, + "Should have 1 storage trie" + ); + + // Check that post state is stored + assert_eq!( + fetched_diff.sorted_post_state.accounts.len(), + 3, + "Should have 3 accounts (including deleted)" + ); + assert_eq!(fetched_diff.sorted_post_state.storages.len(), 1, "Should have 1 storage entry"); + + Ok(()) +} + +/// Test that `replace_updates` properly applies hashed/trie storage updates to the DB +/// +/// This test verifies the bug fix where `replace_updates` was only storing `trie_updates` +/// and `post_states` directly without populating the internal data structures +/// (`hashed_accounts`, `hashed_storages`, `account_branches`, `storage_branches`). +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_replace_updates_applies_all_updates( + storage: S, +) -> Result<(), OpProofsStorageError> { + use reth_trie::{HashedStorage, updates::StorageTrieUpdates}; + + let block_ref_50 = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); + + // ========== Setup: Store initial state at blocks 50, 100, 101 ========== + let initial_account_addr = B256::repeat_byte(0x10); + let initial_account = create_test_account_with_values(1, 1000, 0xAA); + + let initial_storage_addr = B256::repeat_byte(0x20); + let initial_storage_slot = B256::repeat_byte(0x01); + let initial_storage_value = U256::from(100); + + let initial_branch_path = nibbles_from(vec![1, 2, 3]); + let initial_branch = create_test_branch(); + + // Store initial data at block 50 + let mut initial_trie_updates_50 = TrieUpdates::default(); + initial_trie_updates_50.account_nodes.insert(initial_branch_path, initial_branch.clone()); + + let mut initial_post_state_50 = HashedPostState::default(); + initial_post_state_50.accounts.insert(initial_account_addr, Some(initial_account)); + + let initial_diff_50 = BlockStateDiff { + sorted_trie_updates: initial_trie_updates_50.into_sorted(), + sorted_post_state: initial_post_state_50.into_sorted(), + }; + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_50, initial_diff_50)?; + provider_rw.commit()?; + + // Store data at block 100 (common block) + let mut initial_trie_updates_100 = TrieUpdates::default(); + let common_branch_path = nibbles_from(vec![4, 5, 6]); + initial_trie_updates_100.account_nodes.insert(common_branch_path, initial_branch.clone()); + + let mut initial_post_state_100 = HashedPostState::default(); + let mut initial_storage_100 = HashedStorage::new(false); + initial_storage_100.storage.insert(initial_storage_slot, initial_storage_value); + initial_post_state_100.storages.insert(initial_storage_addr, initial_storage_100); + + let initial_diff_100 = BlockStateDiff { + sorted_trie_updates: initial_trie_updates_100.into_sorted(), + sorted_post_state: initial_post_state_100.into_sorted(), + }; + + let block_ref_100 = + BlockWithParent::new(block_ref_50.block.hash, NumHash::new(100, B256::repeat_byte(0x97))); + + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_100, initial_diff_100)?; + provider_rw.commit()?; + + // Store data at block 101 (will be replaced) + let mut initial_trie_updates_101 = TrieUpdates::default(); + let old_branch_path = nibbles_from(vec![7, 8, 9]); + initial_trie_updates_101.account_nodes.insert(old_branch_path, initial_branch); + + let mut initial_post_state_101 = HashedPostState::default(); + let old_account_addr = B256::repeat_byte(0x30); + let old_account = create_test_account_with_values(99, 9999, 0xFF); + initial_post_state_101.accounts.insert(old_account_addr, Some(old_account)); + + let initial_diff_101 = BlockStateDiff { + sorted_trie_updates: initial_trie_updates_101.into_sorted(), + sorted_post_state: initial_post_state_101.into_sorted(), + }; + let block_ref_101 = + BlockWithParent::new(block_ref_100.block.hash, NumHash::new(101, B256::repeat_byte(0x98))); + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_101, initial_diff_101)?; + provider_rw.commit()?; + + let block_ref_102 = + BlockWithParent::new(block_ref_101.block.hash, NumHash::new(102, B256::repeat_byte(0x99))); + + // ========== Verify initial state exists ========== + // Verify block 50 data exists + let mut cursor_initial = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + assert!( + cursor_initial.seek_exact(initial_branch_path)?.is_some(), + "Initial branch should exist before replace" + ); + + // Verify block 101 old data exists + let mut cursor_old = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + assert!( + cursor_old.seek_exact(old_branch_path)?.is_some(), + "Old branch at block 101 should exist before replace" + ); + + let mut account_cursor_old = + storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; + assert!( + account_cursor_old.seek(old_account_addr)?.is_some(), + "Old account at block 101 should exist before replace" + ); + + // ========== Call replace_updates to replace blocks after 100 ========== + let mut blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)> = Vec::default(); + + // New data for block 101 + let new_account_addr = B256::repeat_byte(0x40); + let new_account = create_test_account_with_values(5, 5000, 0xCC); + + let new_storage_addr = B256::repeat_byte(0x50); + let new_storage_slot = B256::repeat_byte(0x02); + let new_storage_value = U256::from(999); + + let new_branch_path = nibbles_from(vec![10, 11, 12]); + let new_branch = create_test_branch_variant(); + + let storage_branch_path = nibbles_from(vec![5, 5]); + let storage_hashed_addr = B256::repeat_byte(0x60); + + let mut new_trie_updates = TrieUpdates::default(); + new_trie_updates.account_nodes.insert(new_branch_path, new_branch.clone()); + + // Add storage trie updates + let mut storage_trie = StorageTrieUpdates::default(); + storage_trie.storage_nodes.insert(storage_branch_path, new_branch.clone()); + new_trie_updates.insert_storage_updates(storage_hashed_addr, storage_trie); + + let mut new_post_state = HashedPostState::default(); + new_post_state.accounts.insert(new_account_addr, Some(new_account)); + + let mut new_storage = HashedStorage::new(false); + new_storage.storage.insert(new_storage_slot, new_storage_value); + new_post_state.storages.insert(new_storage_addr, new_storage); + + blocks_to_add.push(( + block_ref_101, + BlockStateDiff { + sorted_trie_updates: new_trie_updates.into_sorted(), + sorted_post_state: new_post_state.into_sorted(), + }, + )); + + // New data for block 102 + let block_102_account_addr = B256::repeat_byte(0x70); + let block_102_account = create_test_account_with_values(10, 10000, 0xDD); + + let mut trie_updates_102 = TrieUpdates::default(); + let block_102_branch_path = nibbles_from(vec![15, 14, 13]); + trie_updates_102.account_nodes.insert(block_102_branch_path, new_branch); + + let mut post_state_102 = HashedPostState::default(); + post_state_102.accounts.insert(block_102_account_addr, Some(block_102_account)); + + blocks_to_add.push(( + block_ref_102, + BlockStateDiff { + sorted_trie_updates: trie_updates_102.into_sorted(), + sorted_post_state: post_state_102.into_sorted(), + }, + )); + + // Execute replace_updates + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.replace_updates(BlockNumHash::new(100, block_ref_100.block.hash), blocks_to_add)?; + provider_rw.commit()?; + // ========== Verify that data up to block 100 still exists ========== + let mut cursor_50 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + assert!( + cursor_50.seek_exact(initial_branch_path)?.is_some(), + "Block 50 branch should still exist after replace" + ); + + let mut cursor_100 = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; + assert!( + cursor_100.seek_exact(common_branch_path)?.is_some(), + "Block 100 branch should still exist after replace" + ); + + let mut storage_cursor_100 = storage + .provider_ro() + .expect("provider ro") + .storage_hashed_cursor(initial_storage_addr, 100)?; + let result_100 = storage_cursor_100.seek(initial_storage_slot)?; + assert!(result_100.is_some(), "Block 100 storage should still exist after replace"); + assert_eq!( + result_100.unwrap().1, + initial_storage_value, + "Block 100 storage value should be unchanged" + ); + + // ========== Verify that old data after block 100 is gone ========== + let mut cursor_old_gone = + storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + assert!( + cursor_old_gone.seek_exact(old_branch_path)?.is_none(), + "Old branch at block 101 should be removed after replace" + ); + + let mut account_cursor_old_gone = + storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; + let old_acc_result = account_cursor_old_gone.seek(old_account_addr)?; + assert!( + old_acc_result.is_none() || old_acc_result.unwrap().0 != old_account_addr, + "Old account at block 101 should be removed after replace" + ); + + // ========== Verify new data is properly accessible via cursors ========== + + // Verify new account branch nodes + let mut trie_cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + let branch_result = trie_cursor.seek_exact(new_branch_path)?; + assert!(branch_result.is_some(), "New account branch should be accessible via cursor"); + assert_eq!(branch_result.unwrap().0, new_branch_path); + + // Verify new storage branch nodes + let mut storage_trie_cursor = storage + .provider_ro() + .expect("provider ro") + .storage_trie_cursor(storage_hashed_addr, 150)?; + let storage_branch_result = storage_trie_cursor.seek_exact(storage_branch_path)?; + assert!(storage_branch_result.is_some(), "New storage branch should be accessible via cursor"); + assert_eq!(storage_branch_result.unwrap().0, storage_branch_path); + + // Verify new hashed accounts + let mut account_cursor = + storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; + let account_result = account_cursor.seek(new_account_addr)?; + assert!(account_result.is_some(), "New account should be accessible via cursor"); + assert_eq!(account_result.as_ref().unwrap().0, new_account_addr); + assert_eq!(account_result.as_ref().unwrap().1.nonce, new_account.nonce); + assert_eq!(account_result.as_ref().unwrap().1.balance, new_account.balance); + assert_eq!(account_result.as_ref().unwrap().1.bytecode_hash, new_account.bytecode_hash); + + // Verify new hashed storages + let mut storage_cursor = + storage.provider_ro().expect("provider ro").storage_hashed_cursor(new_storage_addr, 150)?; + let storage_result = storage_cursor.seek(new_storage_slot)?; + assert!(storage_result.is_some(), "New storage should be accessible via cursor"); + assert_eq!(storage_result.as_ref().unwrap().0, new_storage_slot); + assert_eq!(storage_result.as_ref().unwrap().1, new_storage_value); + + // Verify block 102 data + let mut trie_cursor_102 = + storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + let branch_result_102 = trie_cursor_102.seek_exact(block_102_branch_path)?; + assert!(branch_result_102.is_some(), "Block 102 branch should be accessible"); + assert_eq!(branch_result_102.unwrap().0, block_102_branch_path); + + let mut account_cursor_102 = + storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; + let account_result_102 = account_cursor_102.seek(block_102_account_addr)?; + assert!(account_result_102.is_some(), "Block 102 account should be accessible"); + assert_eq!(account_result_102.as_ref().unwrap().0, block_102_account_addr); + assert_eq!(account_result_102.as_ref().unwrap().1.nonce, block_102_account.nonce); + + // Verify fetch_trie_updates returns the new data + let provider_rw = storage.provider_rw().expect("provider ro"); + let fetched_101 = provider_rw.fetch_trie_updates(101)?; + provider_rw.commit()?; + assert_eq!( + fetched_101.sorted_trie_updates.account_nodes_ref().len(), + 1, + "Should have 1 account branch node at block 101" + ); + assert!( + fetched_101 + .sorted_trie_updates + .account_nodes_ref() + .iter() + .any(|(addr, _)| *addr == new_branch_path), + "New branch path should be in trie_updates" + ); + assert_eq!( + fetched_101.sorted_post_state.accounts.len(), + 1, + "Should have 1 account at block 101" + ); + assert!( + fetched_101.sorted_post_state.accounts.iter().any(|(addr, _)| *addr == new_account_addr), + "New account should be in post_state" + ); + + Ok(()) +} + +/// Test that pure deletions (nodes only in `removed_nodes`) are properly stored +/// +/// This test verifies that when a node appears only in `removed_nodes` (not in updates), +/// it is properly stored as a deletion and subsequent queries return None for that path. +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_pure_deletions_stored_correctly( + storage: S, +) -> Result<(), OpProofsStorageError> { + use reth_trie::updates::StorageTrieUpdates; + + // ========== Setup: Store initial branch nodes at block 50 ========== + let account_path1 = nibbles_from(vec![1, 2, 3]); + let account_path2 = nibbles_from(vec![4, 5, 6]); + let storage_path1 = nibbles_from(vec![7, 8, 9]); + let storage_path2 = nibbles_from(vec![10, 11, 12]); + let storage_address = B256::repeat_byte(0x42); + + let initial_branch = create_test_branch(); + + let mut initial_trie_updates = TrieUpdates::default(); + initial_trie_updates.account_nodes.insert(account_path1, initial_branch.clone()); + initial_trie_updates.account_nodes.insert(account_path2, initial_branch.clone()); + + let mut storage_trie = StorageTrieUpdates::default(); + storage_trie.storage_nodes.insert(storage_path1, initial_branch.clone()); + storage_trie.storage_nodes.insert(storage_path2, initial_branch); + initial_trie_updates.insert_storage_updates(storage_address, storage_trie); + + let initial_diff = BlockStateDiff { + sorted_trie_updates: initial_trie_updates.into_sorted(), + sorted_post_state: HashedPostStateSorted::default(), + }; + + let block_ref_50 = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); + + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_50, initial_diff)?; + provider_rw.commit()?; + + // Verify initial state exists at block 75 + let mut cursor_75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + assert!( + cursor_75.seek_exact(account_path1)?.is_some(), + "Initial account branch 1 should exist at block 75" + ); + assert!( + cursor_75.seek_exact(account_path2)?.is_some(), + "Initial account branch 2 should exist at block 75" + ); + + let mut storage_cursor_75 = + storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 75)?; + assert!( + storage_cursor_75.seek_exact(storage_path1)?.is_some(), + "Initial storage branch 1 should exist at block 75" + ); + assert!( + storage_cursor_75.seek_exact(storage_path2)?.is_some(), + "Initial storage branch 2 should exist at block 75" + ); + + // ========== At block 100: Mark paths as deleted (ONLY in removed_nodes) ========== + let mut deletion_trie_updates = TrieUpdates::default(); + + // Add to removed_nodes ONLY (no updates) + deletion_trie_updates.removed_nodes.insert(account_path1); + + // Do the same for storage branch + let mut deletion_storage_trie = StorageTrieUpdates::default(); + deletion_storage_trie.removed_nodes.insert(storage_path1); + deletion_trie_updates.insert_storage_updates(storage_address, deletion_storage_trie); + + let deletion_diff = BlockStateDiff { + sorted_trie_updates: deletion_trie_updates.into_sorted(), + sorted_post_state: HashedPostStateSorted::default(), + }; + + let block_ref_100 = + BlockWithParent::new(B256::repeat_byte(0x96), NumHash::new(100, B256::repeat_byte(0x97))); + + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_100, deletion_diff)?; + provider_rw.commit()?; + + // ========== Verify that deleted nodes return None at block 150 ========== + + // Deleted account branch should not be found + let mut cursor_150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + let account_result = cursor_150.seek_exact(account_path1)?; + assert!(account_result.is_none(), "Deleted account branch should return None at block 150"); + + // Non-deleted account branch should still exist + let account_result2 = cursor_150.seek_exact(account_path2)?; + assert!( + account_result2.is_some(), + "Non-deleted account branch should still exist at block 150" + ); + + // Deleted storage branch should not be found + let mut storage_cursor_150 = + storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 150)?; + let storage_result = storage_cursor_150.seek_exact(storage_path1)?; + assert!(storage_result.is_none(), "Deleted storage branch should return None at block 150"); + + // Non-deleted storage branch should still exist + let storage_result2 = storage_cursor_150.seek_exact(storage_path2)?; + assert!( + storage_result2.is_some(), + "Non-deleted storage branch should still exist at block 150" + ); + + // ========== Verify that the nodes still exist at block 75 (before deletion) ========== + let mut cursor_75_after = + storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + assert!( + cursor_75_after.seek_exact(account_path1)?.is_some(), + "Deleted node should still exist at block 75 (before deletion)" + ); + + let mut storage_cursor_75_after = + storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 75)?; + assert!( + storage_cursor_75_after.seek_exact(storage_path1)?.is_some(), + "Deleted storage node should still exist at block 75 (before deletion)" + ); + + // ========== Verify iteration skips deleted nodes ========== + let mut cursor_iter = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + let mut found_paths = Vec::new(); + while let Some((path, _)) = cursor_iter.next()? { + found_paths.push(path); + } + + assert!(!found_paths.contains(&account_path1), "Iteration should skip deleted node"); + assert!(found_paths.contains(&account_path2), "Iteration should include non-deleted node"); + + Ok(()) +} + +/// Test that updates take precedence over removals when both are present +/// +/// This test verifies that when a path appears in both `removed_nodes` and `account_nodes`, +/// the update from `account_nodes` takes precedence. This is critical for correctness +/// when processing trie updates that both remove and update the same node. +#[test_case(InMemoryProofsStorage::new(); "InMemory")] +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_updates_take_precedence_over_removals( + storage: S, +) -> Result<(), OpProofsStorageError> { + use reth_trie::updates::StorageTrieUpdates; + + // ========== Setup: Store initial branch nodes at block 50 ========== + let account_path = nibbles_from(vec![1, 2, 3]); + let storage_path = nibbles_from(vec![4, 5, 6]); + let storage_address = B256::repeat_byte(0x42); + + let initial_branch = create_test_branch(); + + let mut initial_trie_updates = TrieUpdates::default(); + initial_trie_updates.account_nodes.insert(account_path, initial_branch.clone()); + + let mut storage_trie = StorageTrieUpdates::default(); + storage_trie.storage_nodes.insert(storage_path, initial_branch.clone()); + initial_trie_updates.insert_storage_updates(storage_address, storage_trie); + + let initial_diff = BlockStateDiff { + sorted_trie_updates: initial_trie_updates.into_sorted(), + sorted_post_state: HashedPostStateSorted::default(), + }; + + let block_ref_50 = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); + + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_50, initial_diff)?; + provider_rw.commit()?; + + // Verify initial state exists at block 75 + let mut cursor_75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + assert!( + cursor_75.seek_exact(account_path)?.is_some(), + "Initial account branch should exist at block 75" + ); + + let mut storage_cursor_75 = + storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 75)?; + assert!( + storage_cursor_75.seek_exact(storage_path)?.is_some(), + "Initial storage branch should exist at block 75" + ); + + // ========== At block 100: Add paths to BOTH removed_nodes AND account_nodes ========== + // This simulates a scenario where a node is both removed and updated + // The update should take precedence + let updated_branch = create_test_branch_variant(); + + let mut conflicting_trie_updates = TrieUpdates::default(); + + // Add to removed_nodes + conflicting_trie_updates.removed_nodes.insert(account_path); + + // Also add to account_nodes (this should take precedence) + conflicting_trie_updates.account_nodes.insert(account_path, updated_branch.clone()); + + // Do the same for storage branch + let mut conflicting_storage_trie = StorageTrieUpdates::default(); + conflicting_storage_trie.removed_nodes.insert(storage_path); + conflicting_storage_trie.storage_nodes.insert(storage_path, updated_branch.clone()); + conflicting_trie_updates.insert_storage_updates(storage_address, conflicting_storage_trie); + + let conflicting_diff = BlockStateDiff { + sorted_trie_updates: conflicting_trie_updates.into_sorted(), + sorted_post_state: HashedPostStateSorted::default(), + }; + + let block_ref_100 = + BlockWithParent::new(B256::repeat_byte(0x96), NumHash::new(100, B256::repeat_byte(0x97))); + + let provider_rw = storage.provider_rw().expect("provider rw"); + provider_rw.store_trie_updates(block_ref_100, conflicting_diff)?; + provider_rw.commit()?; + + // ========== Verify that updates took precedence at block 150 ========== + + // Account branch should exist (not deleted) with the updated value + let mut cursor_150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; + let account_result = cursor_150.seek_exact(account_path)?; + assert!( + account_result.is_some(), + "Account branch should exist at block 150 (update should take precedence over removal)" + ); + let (found_path, found_branch) = account_result.unwrap(); + assert_eq!(found_path, account_path); + // Verify it's the updated branch, not the initial one + assert_eq!( + found_branch.state_mask, updated_branch.state_mask, + "Account branch should be the updated version, not the initial one" + ); + + // Storage branch should exist (not deleted) with the updated value + let mut storage_cursor_150 = + storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 150)?; + let storage_result = storage_cursor_150.seek_exact(storage_path)?; + assert!( + storage_result.is_some(), + "Storage branch should exist at block 150 (update should take precedence over removal)" + ); + let (found_storage_path, found_storage_branch) = storage_result.unwrap(); + assert_eq!(found_storage_path, storage_path); + // Verify it's the updated branch + assert_eq!( + found_storage_branch.state_mask, updated_branch.state_mask, + "Storage branch should be the updated version, not the initial one" + ); + + // ========== Verify that the old version still exists at block 75 ========== + let mut cursor_75_after = + storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; + let result_75 = cursor_75_after.seek_exact(account_path)?; + assert!(result_75.is_some(), "Initial version should still exist at block 75"); + let (_, branch_75) = result_75.unwrap(); + assert_eq!( + branch_75.state_mask, initial_branch.state_mask, + "Block 75 should see the initial branch, not the updated one" + ); + + Ok(()) +} diff --git a/vendor/reth-optimism-trie/tests/live.rs b/vendor/reth-optimism-trie/tests/live.rs new file mode 100644 index 0000000..3d3ec99 --- /dev/null +++ b/vendor/reth-optimism-trie/tests/live.rs @@ -0,0 +1,633 @@ +//! End-to-end test of the live trie collector. + +use alloy_consensus::{BlockHeader, Header, TxEip2930, constants::ETH_TO_WEI}; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, TxKind, U256, keccak256}; +use derive_more::Constructor; +use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, MAINNET, MIN_TRANSACTION_GAS}; +use reth_db::Database; +use reth_db_common::init::init_genesis; +use reth_ethereum_primitives::{Block, BlockBody, Receipt, Transaction, TransactionSigned}; +use reth_evm::{ConfigureEvm, execute::Executor}; +use reth_evm_ethereum::EthEvmConfig; +use reth_node_api::{NodePrimitives, NodeTypesWithDB}; +use reth_optimism_trie::{ + MdbxProofsStorage, MdbxProofsStorageV2, OpProofStoragePruner, OpProofsStorage, OpProofsStore, + RethTrieStorageLayout, + engine::{EngineError, EngineHandle}, + initialize::InitializationJob, +}; +use reth_primitives_traits::{Block as _, RecoveredBlock}; +use reth_provider::{ + BlockWriter as _, ExecutionOutcome, HashedPostStateProvider, LatestStateProviderRef, + ProviderFactory, StateRootProvider, StorageSettingsCache, + providers::{BlockchainProvider, ProviderNodeTypes}, + test_utils::create_test_provider_factory_with_chain_spec, +}; +use reth_revm::database::StateProviderDatabase; +use secp256k1::{Keypair, Secp256k1, rand::rng}; +use serial_test::serial; +use std::sync::Arc; +use tempfile::TempDir; +use test_case::test_case; + +fn create_mdbx_proofs_storage() -> Arc { + let path = TempDir::new().unwrap().keep(); + Arc::new(MdbxProofsStorage::new(&path).unwrap()) +} + +fn create_mdbx_proofs_storage_v2() -> Arc { + let path = TempDir::new().unwrap().keep(); + Arc::new(MdbxProofsStorageV2::new(&path).unwrap()) +} + +/// Converts a secp256k1 public key to an Ethereum address. +fn public_key_to_address(pubkey: secp256k1::PublicKey) -> Address { + let hash = keccak256(&pubkey.serialize_uncompressed()[1..]); + Address::from_slice(&hash[12..]) +} + +/// Signs a transaction with the given keypair. +fn sign_tx_with_key_pair(key_pair: Keypair, tx: Transaction) -> TransactionSigned { + use alloy_consensus::SignableTransaction; + use reth_primitives_traits::crypto::secp256k1::sign_message; + let secret = B256::from_slice(&key_pair.secret_bytes()); + let sig = sign_message(secret, tx.signature_hash()).unwrap(); + tx.into_signed(sig).into() +} + +/// Specification for a transaction within a block +#[derive(Debug, Clone)] +struct TxSpec { + /// Recipient address for the transaction + to: Address, + /// Value to transfer (in wei) + value: U256, + /// Nonce for the transaction (will be automatically assigned if None) + nonce: Option, +} + +impl TxSpec { + /// Create a simple transfer transaction + const fn transfer(to: Address, value: U256) -> Self { + Self { to, value, nonce: None } + } +} + +/// Specification for a block in the test chain +#[derive(Debug, Clone, Constructor)] +struct BlockSpec { + /// Transactions to include in this block + txs: Vec, +} + +/// Configuration for a test scenario +#[derive(Debug, Constructor)] +struct TestScenario { + /// Blocks to execute before running the initialization job + blocks_before_initialization: Vec, + /// Blocks to execute after initialization using the live collector + blocks_after_initialization: Vec, +} + +/// Helper to create a chain spec with a genesis account funded +fn chain_spec_with_address(address: Address) -> Arc { + Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(Genesis { + alloc: [( + address, + GenesisAccount { balance: U256::from(10 * ETH_TO_WEI), ..Default::default() }, + )] + .into(), + ..MAINNET.genesis.clone() + }) + .paris_activated() + .build(), + ) +} + +/// Creates a block from a spec, executing transactions with the given keypair +fn create_block_from_spec( + spec: &BlockSpec, + block_number: u64, + parent_hash: B256, + chain_spec: &Arc, + key_pair: Keypair, + nonce_counter: &mut u64, +) -> RecoveredBlock { + let transactions: Vec = spec + .txs + .iter() + .map(|tx_spec| { + let nonce = tx_spec.nonce.unwrap_or_else(|| { + let current = *nonce_counter; + *nonce_counter += 1; + current + }); + + sign_tx_with_key_pair( + key_pair, + TxEip2930 { + chain_id: chain_spec.chain.id(), + nonce, + gas_limit: MIN_TRANSACTION_GAS, + gas_price: 1_500_000_000, + to: TxKind::Call(tx_spec.to), + value: tx_spec.value, + ..Default::default() + } + .into(), + ) + }) + .collect(); + + let gas_total = transactions.len() as u64 * MIN_TRANSACTION_GAS; + + Block { + header: Header { + parent_hash, + receipts_root: alloy_primitives::b256!( + "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" + ), + difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), + number: block_number, + gas_limit: gas_total.max(MIN_TRANSACTION_GAS), + gas_used: gas_total, + state_root: B256::ZERO, // Will be calculated by executor + ..Default::default() + }, + body: BlockBody { transactions, ..Default::default() }, + } + .try_into_recovered() + .unwrap() +} + +/// Executes a block and returns the updated block with correct state root +fn execute_block( + block: &mut RecoveredBlock, + provider_factory: &ProviderFactory, + chain_spec: &Arc, +) -> eyre::Result> +where + N: ProviderNodeTypes< + Primitives: NodePrimitives, + > + NodeTypesWithDB, +{ + let provider = provider_factory.provider()?; + let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); + let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); + let block_executor = evm_config.batch_executor(db); + + let execution_result = block_executor.execute(block)?; + + let hashed_state = + LatestStateProviderRef::new(&provider).hashed_post_state(&execution_result.state); + let state_root = LatestStateProviderRef::new(&provider).state_root(hashed_state)?; + + block.set_state_root(state_root); + + Ok(execution_result) +} + +/// Commits a block and its execution output to the database +fn commit_block_to_database( + block: &RecoveredBlock, + execution_output: &reth_evm::execute::BlockExecutionOutput, + provider_factory: &ProviderFactory, +) -> eyre::Result<()> +where + N: ProviderNodeTypes< + Primitives: NodePrimitives, + > + NodeTypesWithDB, +{ + let execution_outcome = ExecutionOutcome { + bundle: execution_output.state.clone(), + receipts: vec![execution_output.receipts.clone()], + first_block: block.number(), + requests: vec![execution_output.requests.clone()], + }; + + // Calculate hashed state from execution result + let state_provider = provider_factory.provider()?; + let hashed_state = HashedPostStateProvider::hashed_post_state( + &LatestStateProviderRef::new(&state_provider), + &execution_output.state, + ); + + let provider_rw = provider_factory.provider_rw()?; + provider_rw.append_blocks_with_state( + vec![block.clone()], + &execution_outcome, + hashed_state.into_sorted(), + )?; + provider_rw.commit()?; + + Ok(()) +} + +/// Runs a test scenario with the given configuration +fn run_test_scenario( + scenario: TestScenario, + provider_factory: ProviderFactory, + chain_spec: Arc, + key_pair: Keypair, + raw_storage: S, +) -> eyre::Result<()> +where + N: ProviderNodeTypes< + Primitives: NodePrimitives, + > + NodeTypesWithDB, + S: OpProofsStore + Send + Sync + Clone + std::fmt::Debug + 'static, +{ + let storage: OpProofsStorage = raw_storage.into(); + let genesis_hash = chain_spec.genesis_hash(); + let mut nonce_counter = 0u64; + let mut last_block_hash = genesis_hash; + let mut last_block_number = 0u64; + + // Execute blocks before initialization + for (idx, block_spec) in scenario.blocks_before_initialization.iter().enumerate() { + let block_number = idx as u64 + 1; + let mut block = create_block_from_spec( + block_spec, + block_number, + last_block_hash, + &chain_spec, + key_pair, + &mut nonce_counter, + ); + + let execution_output = execute_block(&mut block, &provider_factory, &chain_spec)?; + commit_block_to_database(&block, &execution_output, &provider_factory)?; + + last_block_hash = block.hash(); + last_block_number = block_number; + } + + { + let trie_layout = if provider_factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let provider = provider_factory.db_ref(); + let tx = provider.tx()?; + let initialization_job = InitializationJob::new(storage.clone(), tx, trie_layout); + initialization_job.run(last_block_number, last_block_hash)?; + } + + // Execute blocks after initialization using live collector. + // A single collector is shared across all blocks so the in-memory buffer accumulates + // state between iterations (the new async-persistence architecture requires this). + let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); + let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); + let engine_handle = EngineHandle::spawn(evm_config, blockchain_db, storage, pruner); + + for (idx, block_spec) in scenario.blocks_after_initialization.iter().enumerate() { + let block_number = last_block_number + idx as u64 + 1; + let mut block = create_block_from_spec( + block_spec, + block_number, + last_block_hash, + &chain_spec, + key_pair, + &mut nonce_counter, + ); + + // Execute the block to get the correct state root + let execution_output = execute_block(&mut block, &provider_factory, &chain_spec)?; + + // Use the live collector to execute and store trie updates + engine_handle.execute_block(&block)?; + + // Commit the block to the database so subsequent blocks can build on it + commit_block_to_database(&block, &execution_output, &provider_factory)?; + + last_block_hash = block.hash(); + } + + // Drain any pending in-memory blocks to disk before returning. + engine_handle.flush(); + + Ok(()) +} + +/// End-to-end test of a single live collector iteration. +/// (1) Creates a chain with some state +/// (2) Stores the genesis state into storage via initialization +/// (3) Executes a block and calculates the state root using the stored state +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_execute_and_store_block_updates(storage: S) -> Result<(), eyre::Error> +where + S: OpProofsStore + Clone + Send + Sync + std::fmt::Debug + 'static, +{ + // Create a keypair for signing transactions + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rng()); + let sender = public_key_to_address(key_pair.public_key()); + + // Create chain spec with the sender address funded in genesis + let chain_spec = chain_spec_with_address(sender); + + // Create test database and provider factory + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + + // Insert genesis state into the database + init_genesis(&provider_factory).unwrap(); + + // Define the test scenario: + // - No blocks before initialization + // - Initialization to genesis (block 0) + // - Execute one block with a single transaction after initialization + let recipient = Address::repeat_byte(0x42); + let scenario = TestScenario::new( + vec![], + vec![BlockSpec::new(vec![TxSpec::transfer(recipient, U256::from(1))])], + ); + + run_test_scenario(scenario, provider_factory, chain_spec, key_pair, storage) +} + +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_execute_and_store_block_updates_missing_parent_block( + storage: S, +) -> Result<(), eyre::Error> +where + S: OpProofsStore + Clone + Send + Sync + std::fmt::Debug + 'static, +{ + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rng()); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + // No blocks before initialization; initialization only inserts genesis. + let scenario = TestScenario::new(vec![], vec![]); + + // Run initialization (block 0 only) + run_test_scenario( + scenario, + provider_factory.clone(), + chain_spec.clone(), + key_pair, + storage.clone(), + )?; + + // Create a block that is sequential but has a wrong parent hash. + let incorrect_block_number = 1; + let incorrect_parent_hash = B256::repeat_byte(0x11); + + let mut nonce_counter = 0; + let incorrect_block = create_block_from_spec( + &BlockSpec::new(vec![]), + incorrect_block_number, + incorrect_parent_hash, + &chain_spec, + key_pair, + &mut nonce_counter, + ); + + let blockchain_db = BlockchainProvider::new(provider_factory).unwrap(); + let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); + let engine_handle = EngineHandle::spawn( + EthEvmConfig::ethereum(chain_spec.clone()), + blockchain_db, + storage, + pruner, + ); + + // EXPECT: ParentHashMismatch (block is at correct number but wrong parent hash) + let err = engine_handle.execute_block(&incorrect_block).unwrap_err(); + + assert!(matches!(err, EngineError::ParentHashMismatch { .. })); + Ok(()) +} + +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_execute_and_store_block_updates_state_root_mismatch( + storage: Arc, +) -> Result<(), eyre::Error> +where + S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, +{ + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rng()); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + // Run normal scenario: no blocks before initialization, one block after. + let recipient = Address::repeat_byte(0x42); + let scenario = TestScenario::new( + vec![], + vec![BlockSpec::new(vec![TxSpec::transfer(recipient, U256::from(1))])], + ); + + run_test_scenario( + scenario, + provider_factory.clone(), + chain_spec.clone(), + key_pair, + storage.clone(), + )?; + + // Generate a second block normally + let blockchain_db = BlockchainProvider::new(provider_factory.clone()).unwrap(); + let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); + let engine_handle = EngineHandle::spawn( + EthEvmConfig::ethereum(chain_spec.clone()), + blockchain_db, + storage, + pruner, + ); + + // Create the next block — sequential after genesis so the parent hash check passes + // and execution runs, allowing us to verify the state root mismatch path. + let mut nonce_counter = 0; + let last_block_hash = chain_spec.genesis_hash(); + let next_number = 1; + + let mut block = create_block_from_spec( + &BlockSpec::new(vec![]), + next_number, + last_block_hash, + &chain_spec, + key_pair, + &mut nonce_counter, + ); + + // Execute it to compute a correct state root + let _ = execute_block(&mut block, &provider_factory, &chain_spec).unwrap(); + + // Change the state root to induce the error + block.header_mut().state_root = B256::repeat_byte(0xAA); + + // EXPECT: StateRootMismatch + let err = engine_handle.execute_block(&block).unwrap_err(); + + assert!(matches!(err, EngineError::StateRootMismatch { .. })); + Ok(()) +} + +/// Negative test for the body-pruned detection in the engine's execute-block path. +/// The check sits in `tasks::execute_block::run` so it covers both the sync path +/// (`advance_sync` calls into this task) and any direct `EngineHandle::execute_block` +/// caller; this test exercises the latter. +/// +/// Reth returns `Some(block)` with an empty body when the transaction data has been pruned but +/// body indices are still present. Executing that block would produce zero state changes and +/// surface a misleading [`EngineError::StateRootMismatch`] instead of the real cause. The engine +/// detects this and returns [`EngineError::BlockBodyPruned`] instead. +/// +/// We exercise this by constructing a block whose header advertises a non-empty +/// `transactions_root` (i.e. originally contained transactions) but whose body has been +/// stripped — exactly what `recovered_block` would return for a body-pruned block. +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_execute_and_store_block_updates_body_pruned(storage: Arc) -> Result<(), eyre::Error> +where + S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, +{ + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rng()); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + // Initialize storage at genesis so the engine's tip = genesis. + run_test_scenario( + TestScenario::new(vec![], vec![]), + provider_factory.clone(), + chain_spec.clone(), + key_pair, + storage.clone(), + )?; + + let blockchain_db = BlockchainProvider::new(provider_factory.clone()).unwrap(); + let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); + let engine_handle = EngineHandle::spawn( + EthEvmConfig::ethereum(chain_spec.clone()), + blockchain_db, + storage, + pruner, + ); + + // Build block 1 normally and execute it so the header carries the real post-state + // values for that block. + let recipient = Address::repeat_byte(0x42); + let mut nonce_counter = 0u64; + let mut block_with_txs = create_block_from_spec( + &BlockSpec::new(vec![TxSpec::transfer(recipient, U256::from(1))]), + 1, + chain_spec.genesis_hash(), + &chain_spec, + key_pair, + &mut nonce_counter, + ); + let _ = execute_block(&mut block_with_txs, &provider_factory, &chain_spec)?; + + // Synthesize a body-pruned block: keep the executed header but rewrite its + // `transactions_root` to a non-canonical-empty value (mimicking a real header for a + // block that originally had transactions) and strip the body. + let mut header = block_with_txs.header().clone(); + header.transactions_root = B256::repeat_byte(0xAB); + let pruned = Block { header, body: BlockBody::default() }.try_into_recovered().unwrap(); + + // EXPECT: BlockBodyPruned at block 1 + let err = engine_handle.execute_block(&pruned).unwrap_err(); + assert!( + matches!(err, EngineError::BlockBodyPruned(1)), + "expected BlockBodyPruned(1), got {err:?}" + ); + Ok(()) +} + +/// Test with multiple blocks before and after initialization +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_multiple_blocks_before_and_after_initialization( + storage: Arc, +) -> Result<(), eyre::Error> +where + S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, +{ + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rng()); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + // Define the test scenario: + // - Execute 3 blocks before initialization (will be committed to db) + // - Initialization to block 3 + // - Execute 2 more blocks using the live collector + let recipient1 = Address::repeat_byte(0x42); + let recipient2 = Address::repeat_byte(0x43); + let recipient3 = Address::repeat_byte(0x44); + + let scenario = TestScenario::new( + vec![ + BlockSpec::new(vec![TxSpec::transfer(recipient1, U256::from(1))]), + BlockSpec::new(vec![TxSpec::transfer(recipient2, U256::from(2))]), + BlockSpec::new(vec![TxSpec::transfer(recipient3, U256::from(3))]), + ], + vec![ + BlockSpec::new(vec![TxSpec::transfer(recipient1, U256::from(4))]), + BlockSpec::new(vec![TxSpec::transfer(recipient2, U256::from(5))]), + ], + ); + + run_test_scenario(scenario, provider_factory, chain_spec, key_pair, storage) +} + +/// Test with blocks containing multiple transactions +#[test_case(create_mdbx_proofs_storage(); "Mdbx")] +#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] +#[serial] +fn test_blocks_with_multiple_transactions(storage: Arc) -> Result<(), eyre::Error> +where + S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, +{ + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rng()); + let sender = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec_with_address(sender); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&provider_factory).unwrap(); + + let recipient1 = Address::repeat_byte(0x42); + let recipient2 = Address::repeat_byte(0x43); + let recipient3 = Address::repeat_byte(0x44); + + // Block with 3 transactions + let scenario = TestScenario::new( + vec![], + vec![BlockSpec::new(vec![ + TxSpec::transfer(recipient1, U256::from(1)), + TxSpec::transfer(recipient2, U256::from(2)), + TxSpec::transfer(recipient3, U256::from(3)), + ])], + ); + + run_test_scenario(scenario, provider_factory, chain_spec, key_pair, storage) +} From 264ea09cf2644b8033238f685766ee5d8cd3d106 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 Jul 2026 22:36:50 +0800 Subject: [PATCH 03/28] fix: migrate primitives/evm/consensus to reth v2.4.0 APIs - PayloadTypes: block_to_payload bal param + From for TaikoExecutionData (BAL discarded; Taiko schedules no Amsterdam fork) - zk-gas metered loop mirrors revm 41 run_plain (step Err protocol, GasTable threading); EvmFactory::Error bound -> DBErrorMarker - ConsensusError::Other now carries Arc: add TaikoConsensusMessage + other_consensus_error helper - FullConsensus::validate_block_post_execution BAL-hash param Co-Authored-By: Claude Fable 5 --- crates/consensus/src/validation/anchor.rs | 18 +++++++------ crates/consensus/src/validation/mod.rs | 33 ++++++++++++++++++++--- crates/evm/src/evm.rs | 1 + crates/evm/src/factory.rs | 4 +-- crates/evm/src/zk_gas/runtime.rs | 26 ++++++++++++++---- crates/primitives/src/engine/mod.rs | 15 +++++++++++ 6 files changed, 78 insertions(+), 19 deletions(-) diff --git a/crates/consensus/src/validation/anchor.rs b/crates/consensus/src/validation/anchor.rs index bfb53dc..0cd56b1 100644 --- a/crates/consensus/src/validation/anchor.rs +++ b/crates/consensus/src/validation/anchor.rs @@ -8,6 +8,8 @@ use reth_primitives_traits::{Block, BlockBody, RecoveredBlock, SignedTransaction use alethia_reth_chainspec::{hardfork::TaikoHardforks, spec::TaikoChainSpec}; use alethia_reth_evm::alloy::TAIKO_GOLDEN_TOUCH_ADDRESS; +use super::other_consensus_error; + pub use crate::anchor_constants::{ ANCHOR_V1_SELECTOR, ANCHOR_V1_V2_GAS_LIMIT, ANCHOR_V2_SELECTOR, ANCHOR_V3_SELECTOR, ANCHOR_V3_V4_GAS_LIMIT, ANCHOR_V4_SELECTOR, @@ -42,7 +44,7 @@ pub fn validate_anchor_transaction( // Ensure the value is zero. if anchor_transaction.value() != U256::ZERO { - return Err(ConsensusError::Other("Anchor transaction value must be zero".into())); + return Err(other_consensus_error("Anchor transaction value must be zero")); } // Ensure the gas limit is correct. @@ -52,7 +54,7 @@ pub fn validate_anchor_transaction( ANCHOR_V1_V2_GAS_LIMIT }; if anchor_transaction.gas_limit() != gas_limit { - return Err(ConsensusError::Other(format!( + return Err(other_consensus_error(format!( "Anchor transaction gas limit must be {gas_limit}, got {}", anchor_transaction.gas_limit() ))); @@ -61,21 +63,21 @@ pub fn validate_anchor_transaction( // Ensure the tip is equal to zero. let anchor_transaction_tip = anchor_transaction.effective_tip_per_gas(ctx.base_fee_per_gas).ok_or_else(|| { - ConsensusError::Other("Anchor transaction tip must be set to zero".into()) + other_consensus_error("Anchor transaction tip must be set to zero") })?; if anchor_transaction_tip != 0 { - return Err(ConsensusError::Other(format!( + return Err(other_consensus_error(format!( "Anchor transaction tip must be zero, got {anchor_transaction_tip}" ))); } // Ensure the sender is the treasury address. let sender = anchor_transaction.try_recover().map_err(|err| { - ConsensusError::Other(format!("Anchor transaction sender must be recoverable: {err}")) + other_consensus_error(format!("Anchor transaction sender must be recoverable: {err}")) })?; if sender != Address::from(TAIKO_GOLDEN_TOUCH_ADDRESS) { - return Err(ConsensusError::Other(format!( + return Err(other_consensus_error(format!( "Anchor transaction sender must be the treasury address, got {sender}" ))); } @@ -103,7 +105,7 @@ where timestamp: block.header().timestamp(), block_number: block.number(), base_fee_per_gas: block.header().base_fee_per_gas().ok_or_else(|| { - ConsensusError::Other("Block base fee per gas must be set".into()) + other_consensus_error("Block base fee per gas must be set") })?, }, ) @@ -115,7 +117,7 @@ pub(super) fn validate_input_selector( expected_selector: &[u8; 4], ) -> Result<(), ConsensusError> { if !input.starts_with(expected_selector) { - return Err(ConsensusError::Other(format!( + return Err(other_consensus_error(format!( "Anchor transaction input data does not match the expected selector: {expected_selector:?}" ))); } diff --git a/crates/consensus/src/validation/mod.rs b/crates/consensus/src/validation/mod.rs index 97f20a6..a006312 100644 --- a/crates/consensus/src/validation/mod.rs +++ b/crates/consensus/src/validation/mod.rs @@ -36,6 +36,24 @@ pub use anchor::{ #[cfg(test)] mod tests; +/// Free-form message carried by Taiko-specific [`ConsensusError::Other`] violations. +#[derive(Debug)] +struct TaikoConsensusMessage(String); + +impl core::fmt::Display for TaikoConsensusMessage { + /// Writes the violation message verbatim. + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.0) + } +} + +impl core::error::Error for TaikoConsensusMessage {} + +/// Builds a [`ConsensusError::Other`] from a Taiko validation message. +pub(crate) fn other_consensus_error(message: impl Into) -> ConsensusError { + ConsensusError::Other(Arc::new(TaikoConsensusMessage(message.into()))) +} + /// Minimal block reader interface used by Taiko consensus. pub trait TaikoBlockReader: Send + Sync + Debug { /// Returns the timestamp of the block referenced by the given hash, if present. @@ -73,8 +91,15 @@ where block: &RecoveredBlock, result: &BlockExecutionResult, receipt_root_bloom: Option, + block_access_list_hash: Option, ) -> Result<(), ConsensusError> { - validate_block_post_execution(block, &self.chain_spec, result, receipt_root_bloom)?; + validate_block_post_execution( + block, + &self.chain_spec, + result, + receipt_root_bloom, + block_access_list_hash, + )?; validate_zk_gas_post_execution(block, self.chain_spec.as_ref(), &result.receipts)?; validate_anchor_transaction_in_block::<::Block>( block, @@ -160,7 +185,7 @@ where if self.chain_spec.is_shasta_active(header.timestamp()) && header.extra_data().len() != SHASTA_EXTRA_DATA_LEN { - return Err(ConsensusError::Other(format!( + return Err(other_consensus_error(format!( "invalid Shasta extra-data length: have {}, want {SHASTA_EXTRA_DATA_LEN}", header.extra_data().len() ))); @@ -259,7 +284,7 @@ where return Ok(()); } - Err(ConsensusError::Other(format!( + Err(other_consensus_error(format!( "Unzen block body extends past zk gas truncation point: body has {body_transaction_count} transactions but execution committed {committed_receipt_count}" ))) } @@ -307,7 +332,7 @@ fn validate_no_blob_transactions( transactions: &[Tx], ) -> Result<(), ConsensusError> { if transactions.iter().any(|tx| !is_allowed_tx_type(tx)) { - return Err(ConsensusError::Other("Blob transactions are not allowed".into())); + return Err(other_consensus_error("Blob transactions are not allowed")); } Ok(()) } diff --git a/crates/evm/src/evm.rs b/crates/evm/src/evm.rs index 916b6e4..86e333f 100644 --- a/crates/evm/src/evm.rs +++ b/crates/evm/src/evm.rs @@ -201,6 +201,7 @@ where context, &mut frame.interpreter, instructions.instruction_table(), + instructions.gas_table(), meter, ); diff --git a/crates/evm/src/factory.rs b/crates/evm/src/factory.rs index d20e141..903ec35 100644 --- a/crates/evm/src/factory.rs +++ b/crates/evm/src/factory.rs @@ -3,7 +3,7 @@ use reth_evm::precompiles::PrecompilesMap; use reth_revm::{ Context, Inspector, MainBuilder, MainContext, context::{ - BlockEnv, TxEnv, + BlockEnv, DBErrorMarker, TxEnv, result::{EVMError, HaltReason}, }, inspector::NoOpInspector, @@ -29,7 +29,7 @@ impl EvmFactory for TaikoEvmFactory { /// Transaction environment. type Tx = TxEnv; /// EVM error. - type Error = EVMError; + type Error = EVMError; /// Halt reason. type HaltReason = HaltReason; /// The EVM context for inspectors. diff --git a/crates/evm/src/zk_gas/runtime.rs b/crates/evm/src/zk_gas/runtime.rs index e8d4a92..44a38cd 100644 --- a/crates/evm/src/zk_gas/runtime.rs +++ b/crates/evm/src/zk_gas/runtime.rs @@ -3,7 +3,7 @@ use reth_revm::{ context::{ContextError, ContextTr}, interpreter::{ - Interpreter, InterpreterAction, + GasTable, Interpreter, InterpreterAction, instructions::InstructionTable, interpreter::EthInterpreter, interpreter_types::{Jumps, LoopControl}, @@ -16,21 +16,27 @@ use super::{ }; /// Runs the interpreter loop while charging zk gas directly in the production path. +/// +/// Mirrors revm's `Interpreter::run_plain` control flow: every step signals loop exit through +/// its `Err(InstructionResult)`, and the halt is materialized afterwards only when the step +/// left no pending action. Zk gas is charged for every executed step — including the halting +/// one — matching the interpreter gas it consumed. #[inline] pub(crate) fn run_metered_plain( context: &mut CTX, interpreter: &mut Interpreter, instruction_table: &InstructionTable, + gas_table: &GasTable, meter: &mut ZkGasMeter<'static>, ) -> InterpreterAction { - while interpreter.bytecode.is_not_end() { + let halt = loop { let opcode = interpreter.bytecode.opcode(); let gas_before = interpreter.gas.remaining(); - interpreter.step(instruction_table, context); + let step_result = interpreter.step(instruction_table, gas_table, context); let charge = if is_spawn_opcode(opcode) && - matches!(&interpreter.bytecode.action, Some(InterpreterAction::NewFrame(_))) + matches!(interpreter.bytecode.action(), Some(InterpreterAction::NewFrame(_))) { meter.charge_spawn_opcode(opcode) } else { @@ -40,8 +46,18 @@ pub(crate) fn run_metered_plain( if let Err(ZkGasOutcome::LimitExceeded) = charge { set_custom_error(context); interpreter.halt_fatal(); - break; + break None; } + + if let Err(result) = step_result { + break Some(result); + } + }; + + if let Some(result) = halt + && interpreter.bytecode.action().is_none() + { + interpreter.halt(result); } interpreter.take_next_action() diff --git a/crates/primitives/src/engine/mod.rs b/crates/primitives/src/engine/mod.rs index c0d51eb..4590746 100644 --- a/crates/primitives/src/engine/mod.rs +++ b/crates/primitives/src/engine/mod.rs @@ -1,4 +1,5 @@ //! Engine API type adapters for Taiko execution payloads. +use alloy_primitives::Bytes; use alloy_rpc_types_engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadV1, @@ -7,6 +8,7 @@ use reth_engine_primitives::EngineTypes; use reth_ethereum_engine_primitives::EthBuiltPayload; use reth_payload_primitives::{BuiltPayload, PayloadTypes}; use reth_primitives_traits::{NodePrimitives, SealedBlock}; +use std::sync::Arc; use self::types::{TaikoExecutionData, TaikoExecutionDataSidecar}; use crate::payload::attributes::TaikoPayloadAttributes; @@ -28,10 +30,14 @@ impl PayloadTypes for TaikoEngineTypes { type PayloadAttributes = TaikoPayloadAttributes; /// Converts a block into an execution payload. + /// + /// The Amsterdam block access list is discarded: Taiko networks schedule no Amsterdam + /// fork and [`TaikoExecutionData`] carries no BAL field. fn block_to_payload( block: SealedBlock< <::Primitives as NodePrimitives>::Block, >, + _bal: Option, ) -> Self::ExecutionData { let tx_hash = block.transactions_root; let withdrawals_hash = block.withdrawals_root; @@ -51,6 +57,15 @@ impl PayloadTypes for TaikoEngineTypes { } } +impl From for TaikoExecutionData { + /// Converts a built payload into Taiko execution data, discarding any Amsterdam block + /// access list (Taiko networks schedule no Amsterdam fork). + fn from(value: EthBuiltPayload) -> Self { + let block = Arc::unwrap_or_clone(value.into_block_arc()).into_sealed_block(); + TaikoEngineTypes::block_to_payload(block, None) + } +} + impl EngineTypes for TaikoEngineTypes { /// Execution Payload V1 envelope type. type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1; From 85830e18687bf441a57214730689c028efc4215f Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 Jul 2026 23:02:12 +0800 Subject: [PATCH 04/28] fix: migrate block/payload/rpc/node/cli to reth v2.4.0 APIs - block: infallible commit_transaction (zk-gas budget pre-checked in execute_transaction_without_commit), state hooks now delivered via the State database, BlockExecutorFactory Executor GAT + TxExecutionResult, GasOutput accessors, mark_invalid by-value, BuildPendingEnv overrides param - payload: BuildArguments state_root_handle, DB-level state hooks, EthBuiltPayload over RecoveredBlock - rpc: BalProvider bounds on the Taiko engine API, tree-validator StateTrieOverlayManager param, proof-window accessor - node: adapt proof-history to vendored trie develop API (opt_block shim, pruner builder, ComputedTrieData); retain LiveTrieCollector in vendor (upstream moved live collection into its engine service) - cli: NodeConfig jit field passthrough, TracingGuards Co-Authored-By: Claude Fable 5 --- Cargo.lock | 1 + crates/block/src/config.rs | 8 +- crates/block/src/executor.rs | 60 ++--- crates/block/src/factory.rs | 20 +- crates/block/src/tx_selection/mod.rs | 18 +- crates/cli/src/command.rs | 2 + crates/cli/src/lib.rs | 4 +- crates/evm/src/alloy.rs | 10 + crates/evm/src/zk_gas/meter.rs | 9 + crates/node/src/proof_history/mod.rs | 14 +- crates/node/src/proof_history/sidecar.rs | 42 ++-- crates/node/src/proof_history/storage_init.rs | 4 +- crates/payload/src/builder/execution.rs | 6 +- crates/payload/src/builder/mod.rs | 24 +- crates/rpc/Cargo.toml | 1 + crates/rpc/src/engine/api.rs | 12 +- crates/rpc/src/engine/validator.rs | 3 + crates/rpc/src/proof_state.rs | 10 +- vendor/reth-optimism-trie/src/lib.rs | 4 + vendor/reth-optimism-trie/src/live.rs | 220 ++++++++++++++++++ 20 files changed, 376 insertions(+), 96 deletions(-) create mode 100644 vendor/reth-optimism-trie/src/live.rs diff --git a/Cargo.lock b/Cargo.lock index 88071fe..8122179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,7 @@ dependencies = [ "jsonrpsee-types", "op-alloy-flz", "reth", + "reth-chain-state", "reth-db", "reth-db-api", "reth-engine-primitives", diff --git a/crates/block/src/config.rs b/crates/block/src/config.rs index 3ced3c7..a8e8315 100644 --- a/crates/block/src/config.rs +++ b/crates/block/src/config.rs @@ -424,7 +424,13 @@ where #[cfg(feature = "net")] impl BuildPendingEnv

for TaikoNextBlockEnvAttributes { /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for pending block. - fn build_pending_env(parent: &SealedHeader
) -> Self { + /// + /// Block overrides are ignored: the only override the upstream implementation consults is + /// the beacon root, and Taiko's next-block attributes carry no parent beacon block root. + fn build_pending_env( + parent: &SealedHeader
, + _block_overrides: Option<&alloy_rpc_types_eth::BlockOverrides>, + ) -> Self { Self { timestamp: parent.timestamp.saturating_add(12), suggested_fee_recipient: parent.beneficiary, diff --git a/crates/block/src/executor.rs b/crates/block/src/executor.rs index 03aa7e4..7ad37de 100644 --- a/crates/block/src/executor.rs +++ b/crates/block/src/executor.rs @@ -10,10 +10,10 @@ use alloy_evm::{ }; use alloy_primitives::{Address, Bytes, Log, U256, Uint}; use reth_evm::{ - Evm, OnStateHook, + Evm, block::{ BlockExecutionError, BlockExecutor, BlockValidationError, CommitChanges, ExecutableTx, - InternalBlockExecutionError, StateChangeSource, StateDB, SystemCaller, + InternalBlockExecutionError, StateDB, SystemCaller, }, eth::receipt_builder::ReceiptBuilderCtx, }; @@ -179,21 +179,24 @@ where } /// Commits the current transaction's zk gas and publishes the updated block total. - fn commit_current_transaction_zk_gas(&mut self) -> Result<(), BlockExecutionError> + /// + /// # Panics + /// + /// If committing would exceed the block zk gas budget. [`BlockExecutor::commit_transaction`] + /// is infallible in alloy-evm 0.37, so `execute_transaction_without_commit` pre-checks the + /// budget while the failure can still be reported; reaching the exceeded branch here means + /// the executor was driven with a result that skipped that check. + fn commit_current_transaction_zk_gas(&mut self) where Evm: TaikoZkGasEvm, { match self.evm.commit_transaction_zk_gas() { - Ok(Some(zk_gas)) => { - self.ctx.set_finalized_block_zk_gas(zk_gas); - Ok(()) - } - Ok(None) => Ok(()), - Err(ZkGasOutcome::LimitExceeded) => { - self.zk_gas_exhausted = true; - self.reset_current_transaction_zk_gas(); - Err(Self::zk_gas_limit_error()) - } + Ok(Some(zk_gas)) => self.ctx.set_finalized_block_zk_gas(zk_gas), + Ok(None) => {} + Err(ZkGasOutcome::LimitExceeded) => unreachable!( + "zk gas commit exceeded the block budget; \ + execute_transaction_without_commit must pre-check the budget" + ), } } @@ -297,6 +300,7 @@ where > + TaikoZkGasEvm, Spec: TaikoExecutorSpec + Clone, R: ReceiptBuilder>, + ::TxType: Send + 'static, { /// Input transaction type. type Transaction = R::Transaction; @@ -365,7 +369,7 @@ where return Ok(None); } - self.commit_transaction(output).map(Some) + Ok(Some(self.commit_transaction(output))) } /// Executes a transaction and returns the resulting state diff without persisting it; the @@ -419,6 +423,15 @@ where } }; + // The trait's `commit_transaction` is infallible in alloy-evm 0.37, so the block zk gas + // budget must be checked here, where truncation can still be reported. The in-flight + // total is final at this point — nothing meters between execution and commit. + if self.evm.transaction_zk_gas_commit_would_exceed() { + self.zk_gas_exhausted = true; + self.reset_current_transaction_zk_gas(); + return Err(Self::zk_gas_limit_error()); + } + Ok(EthTxResult { result, blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(), @@ -428,16 +441,14 @@ where /// Commits a previously executed transaction: updates receipts, gas accounting, and writes the /// buffered state changes to the database. - fn commit_transaction( - &mut self, - output: Self::Result, - ) -> Result { + /// + /// State hooks fire from the `State` database on commit (reth v2.4.0 moved hook delivery off + /// the block executor), so no explicit hook call is needed here. + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { let EthTxResult { result: ResultAndState { result, state }, tx_type, .. } = output; - self.system_caller.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); - let gas_used = result.tx_gas_used(); - self.commit_current_transaction_zk_gas()?; + self.commit_current_transaction_zk_gas(); // append gas used self.gas_used += gas_used; @@ -454,7 +465,7 @@ where // Commit the state changes. self.evm.db_mut().commit(state); - Ok(GasOutput::new(gas_used)) + GasOutput::new(gas_used) } /// Applies any necessary changes after executing the block's transactions, completes execution @@ -473,11 +484,6 @@ where )) } - /// Sets a hook to be called after each state change during execution. - fn set_state_hook(&mut self, hook: Option>) { - self.system_caller.with_state_hook(hook); - } - /// Exposes mutable reference to EVM. fn evm_mut(&mut self) -> &mut Self::Evm { &mut self.evm diff --git a/crates/block/src/factory.rs b/crates/block/src/factory.rs index ca313ca..6d98dc6 100644 --- a/crates/block/src/factory.rs +++ b/crates/block/src/factory.rs @@ -6,11 +6,11 @@ use std::{ }, }; -use alloy_consensus::Header; +use alloy_consensus::{Header, TransactionEnvelope}; use alloy_evm::{ EvmFactory, - block::{BlockExecutorFactory, BlockExecutorFor, StateDB}, - eth::receipt_builder::ReceiptBuilder, + block::{BlockExecutorFactory, StateDB}, + eth::{EthTxResult, receipt_builder::ReceiptBuilder}, }; use alloy_primitives::{B256, Bytes, U256}; use alloy_rpc_types_eth::Withdrawals; @@ -116,6 +116,14 @@ where type Transaction = ::Transaction; /// Receipt type produced by the executor. type Receipt = ::Receipt; + /// Result produced by executors for each transaction. + type TxExecutionResult = EthTxResult< + ::HaltReason, + <::Transaction as TransactionEnvelope>::TxType, + >; + /// Executor type created by this factory. + type Executor<'a, DB: StateDB, I: Inspector<::Context>> = + TaikoBlockExecutor<'a, ::Evm, Spec, &'a RethReceiptBuilder>; /// Reference to EVM factory used by the executor. fn evm_factory(&self) -> &Self::EvmFactory { @@ -127,10 +135,10 @@ where &'a self, evm: ::Evm, ctx: Self::ExecutionCtx<'a>, - ) -> impl BlockExecutorFor<'a, Self, DB, I> + ) -> Self::Executor<'a, DB, I> where - DB: StateDB + 'a, - I: Inspector<::Context> + 'a, + DB: StateDB, + I: Inspector<::Context>, { TaikoBlockExecutor::new(evm, ctx, self.spec.clone(), &self.receipt_builder) } diff --git a/crates/block/src/tx_selection/mod.rs b/crates/block/src/tx_selection/mod.rs index 6b9a51f..51e1653 100644 --- a/crates/block/src/tx_selection/mod.rs +++ b/crates/block/src/tx_selection/mod.rs @@ -146,7 +146,7 @@ where // 2. Filter by locals (if configured) if !config.locals.is_empty() && !config.locals.contains(&pool_tx.sender()) { // Mark as underpriced to skip this transaction and its dependents - best_txs.mark_invalid(&pool_tx, &InvalidPoolTransactionError::Underpriced); + best_txs.mark_invalid(&pool_tx, InvalidPoolTransactionError::Underpriced); continue; } @@ -154,7 +154,7 @@ where let tip = pool_tx.effective_tip_per_gas(config.base_fee); if tip.is_none_or(|t| t < config.min_tip as u128) { trace!(target: "tx_selection", ?pool_tx, "skipping transaction with insufficient tip"); - best_txs.mark_invalid(&pool_tx, &InvalidPoolTransactionError::Underpriced); + best_txs.mark_invalid(&pool_tx, InvalidPoolTransactionError::Underpriced); continue; } @@ -163,7 +163,7 @@ where if !is_allowed_tx_type(tx.inner()) { best_txs.mark_invalid( &pool_tx, - &InvalidPoolTransactionError::Consensus( + InvalidPoolTransactionError::Consensus( InvalidTransactionError::TxTypeNotSupported, ), ); @@ -175,7 +175,7 @@ where if pool_tx.gas_limit() > config.gas_limit_per_list { best_txs.mark_invalid( &pool_tx, - &InvalidPoolTransactionError::ExceedsGasLimit( + InvalidPoolTransactionError::ExceedsGasLimit( pool_tx.gas_limit(), config.gas_limit_per_list, ), @@ -183,7 +183,7 @@ where continue; } if da_size > config.max_da_bytes_per_list { - best_txs.mark_invalid(&pool_tx, &da_limit_error(da_size, config.max_da_bytes_per_list)); + best_txs.mark_invalid(&pool_tx, da_limit_error(da_size, config.max_da_bytes_per_list)); continue; } @@ -203,7 +203,7 @@ where if lists.len() >= config.max_lists { let err = limit_exceeded_error(pool_tx.gas_limit(), exceeds_gas, exceeds_da, config); - best_txs.mark_invalid(&pool_tx, &err); + best_txs.mark_invalid(&pool_tx, err); continue; } // Start a new list @@ -225,14 +225,14 @@ where if exceeds_gas || exceeds_da.is_some() { let err = limit_exceeded_error(pool_tx.gas_limit(), exceeds_gas, exceeds_da, config); - best_txs.mark_invalid(&pool_tx, &err); + best_txs.mark_invalid(&pool_tx, err); continue; } } // 7. Execute transaction let gas_used = match builder.execute_transaction(tx.clone()) { - Ok(gas_used) => gas_used, + Ok(gas_output) => gas_output.tx_gas_used(), Err(err) if is_zk_gas_limit_exceeded(&err) => { trace!(target: "tx_selection", ?tx, "stopping selection after zk gas exhaustion"); break; @@ -249,7 +249,7 @@ where trace!(target: "tx_selection", %error, ?tx, "skipping invalid transaction and its descendants"); best_txs.mark_invalid( &pool_tx, - &InvalidPoolTransactionError::Consensus( + InvalidPoolTransactionError::Consensus( InvalidTransactionError::TxTypeNotSupported, ), ); diff --git a/crates/cli/src/command.rs b/crates/cli/src/command.rs index 3eb692c..e6909db 100644 --- a/crates/cli/src/command.rs +++ b/crates/cli/src/command.rs @@ -120,6 +120,7 @@ where era, static_files, storage, + jit, } = *self.0; // set up node config @@ -141,6 +142,7 @@ where era, static_files, storage, + jit, }; // Apply Taiko-specific devnet Unzen timestamp override if specified. diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 905a24b..2de95cc 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -16,7 +16,7 @@ use reth_db::DatabaseEnv; use reth_ethereum_forks::Hardforks; use reth_node_api::{NodePrimitives, NodeTypes}; use reth_node_builder::{NodeBuilder, WithLaunchContext}; -use reth_tracing::FileWorkerGuard; +use reth_tracing::TracingGuards; use tracing::info; use alethia_reth_block::config::TaikoEvmConfig; @@ -267,7 +267,7 @@ impl< /// /// If file logging is enabled, this function returns a guard that must be kept alive to ensure /// that all logs are flushed to disk. - pub fn init_tracing(&self) -> eyre::Result> { + pub fn init_tracing(&self) -> eyre::Result { let guard = self.inner.logs.init_tracing()?; Ok(guard) } diff --git a/crates/evm/src/alloy.rs b/crates/evm/src/alloy.rs index b594be5..eb95ae2 100644 --- a/crates/evm/src/alloy.rs +++ b/crates/evm/src/alloy.rs @@ -93,6 +93,11 @@ pub trait TaikoZkGasEvm { /// Commits the current transaction's zk gas into the block total and returns the new total. fn commit_transaction_zk_gas(&mut self) -> Result, ZkGasOutcome>; + /// Returns `true` when committing the current transaction's zk gas would exceed the block + /// budget, i.e. when [`Self::commit_transaction_zk_gas`] would fail. Always `false` when no + /// meter is installed (pre-Unzen specs). + fn transaction_zk_gas_commit_would_exceed(&self) -> bool; + /// Returns the finalized block zk gas that has already been committed. fn block_zk_gas_used(&self) -> Option; @@ -124,6 +129,11 @@ where Ok(Some(meter.block_zk_gas_used())) } + /// Returns whether committing the current transaction's zk gas would exceed the budget. + fn transaction_zk_gas_commit_would_exceed(&self) -> bool { + self.meter().is_some_and(|m| m.commit_would_exceed_block_limit()) + } + /// Returns the finalized block zk gas that has already been committed. fn block_zk_gas_used(&self) -> Option { self.meter().map(|m| m.block_zk_gas_used()) diff --git a/crates/evm/src/zk_gas/meter.rs b/crates/evm/src/zk_gas/meter.rs index cc91428..7e46e26 100644 --- a/crates/evm/src/zk_gas/meter.rs +++ b/crates/evm/src/zk_gas/meter.rs @@ -54,6 +54,15 @@ impl<'a> ZkGasMeter<'a> { Ok(()) } + /// Returns `true` when [`Self::commit_transaction`] would fail: committing the in-flight + /// transaction zk gas would exceed the block budget or overflow `u64` arithmetic. + pub const fn commit_would_exceed_block_limit(&self) -> bool { + match self.block_zk_gas_used.checked_add(self.tx_zk_gas_used) { + Some(next_block) => next_block > self.schedule.block_limit, + None => true, + } + } + /// Returns the finalized zk gas from fully committed transactions. pub const fn block_zk_gas_used(&self) -> u64 { self.block_zk_gas_used diff --git a/crates/node/src/proof_history/mod.rs b/crates/node/src/proof_history/mod.rs index 46a5018..5fda2e3 100644 --- a/crates/node/src/proof_history/mod.rs +++ b/crates/node/src/proof_history/mod.rs @@ -32,7 +32,7 @@ use reth_node_builder::{ NodeAdapter, NodeBuilderWithComponents, NodeComponentsBuilder, WithLaunchContext, rpc::{RethRpcAddOns, RpcContext}, }; -use reth_optimism_trie::{OpProofsStorage, db::MdbxProofsStorage}; +use reth_optimism_trie::{OpProofsStorage, OpProofsStorageError, db::MdbxProofsStorage}; use reth_rpc_builder::RethRpcModule; use reth_rpc_eth_api::helpers::FullEthApi; use reth_storage_api::{ @@ -45,6 +45,18 @@ use tracing::info; /// Shared storage type used by proof-history indexing and debug RPC overrides. pub type ProofHistoryStorage = OpProofsStorage>; +/// Adapts the storage bound accessors' `NoBlocksFound` error back to the `Option` shape the +/// reconciliation logic in this module was written against. +pub(crate) fn opt_block( + result: Result, +) -> Result, OpProofsStorageError> { + match result { + Ok(numhash) => Ok(Some((numhash.number, numhash.hash))), + Err(OpProofsStorageError::NoBlocksFound) => Ok(None), + Err(err) => Err(err), + } +} + /// Storage and reconciliation-readiness handles shared with the proof-history RPC overrides. pub type ProofHistoryRpcHandles = (ProofHistoryStorage, ProofHistoryReadiness); diff --git a/crates/node/src/proof_history/sidecar.rs b/crates/node/src/proof_history/sidecar.rs index 818bd65..db2ae8a 100644 --- a/crates/node/src/proof_history/sidecar.rs +++ b/crates/node/src/proof_history/sidecar.rs @@ -2,6 +2,7 @@ use super::{ config::ProofHistoryConfig, + opt_block, storage_init::{ DelayedProofHistoryStart, ProofHistoryInitializationAction, delayed_proof_history_start, finalized_block_number, initialize_historical_proof_history_storage, @@ -405,8 +406,8 @@ where /// Computes the reconciliation action for the current proof-history storage bounds. fn startup_action(&self) -> eyre::Result { let provider_ro = self.storage.provider_ro()?; - let earliest = provider_ro.get_earliest_block_number()?; - let latest = provider_ro.get_latest_block_number()?; + let earliest = opt_block(provider_ro.get_earliest_block())?; + let latest = opt_block(provider_ro.get_latest_block())?; let canonical_best = self.provider.best_block_number()?; let canonical_earliest_hash = earliest.map(|(number, _)| self.provider.block_hash(number)).transpose()?.flatten(); @@ -427,10 +428,7 @@ where /// Unwinds proof-history storage so its latest retained block is the canonical earliest block. async fn unwind_to_earliest(&self, earliest: BlockNumHash) -> eyre::Result<()> { - let latest = self - .storage - .provider_ro()? - .get_latest_block_number()? + let latest = opt_block(self.storage.provider_ro()?.get_latest_block())? .ok_or_else(|| eyre!("no latest proof-history block to unwind"))? .0; if latest <= earliest.number { @@ -600,12 +598,10 @@ where /// Verifies the proof-history database is initialized and safe to prune automatically. fn ensure_initialized(&self) -> eyre::Result<()> { let provider_ro = self.storage.provider_ro()?; - let earliest_block_number = provider_ro - .get_earliest_block_number()? + let earliest_block_number = opt_block(provider_ro.get_earliest_block())? .ok_or_else(|| eyre!("proof-history storage is not initialized"))? .0; - let latest_block_number = provider_ro - .get_latest_block_number()? + let latest_block_number = opt_block(provider_ro.get_latest_block())? .ok_or_else(|| eyre!("proof-history storage is not initialized"))? .0; @@ -626,12 +622,10 @@ where /// Spawns the periodic proof-history pruning task. fn spawn_pruner_task(&self) { - let pruner = Arc::new(OpProofStoragePruner::new( - self.storage.clone(), - self.provider.clone(), - self.config.window, - PROOF_HISTORY_PRUNE_BATCH_SIZE, - )); + let pruner = Arc::new( + OpProofStoragePruner::new(self.storage.clone(), self.provider.clone(), self.config.window) + .with_batch_size(PROOF_HISTORY_PRUNE_BATCH_SIZE), + ); let prune_interval = self.config.prune_interval; let retention_window = self.config.window; let write_lock = self.write_lock.clone(); @@ -733,9 +727,9 @@ where loop { let write_guard = write_lock.lock().await; - let latest = match storage.provider_ro().and_then(|p| p.get_latest_block_number()) { - Ok(Some((number, _))) => number, - Ok(None) => { + let latest = match storage.provider_ro().and_then(|p| p.get_latest_block()) { + Ok(numhash) => numhash.number, + Err(OpProofsStorageError::NoBlocksFound) => { error!(target: "reth::taiko::proof_history", "proof-history sync loop found no stored blocks; stopping sync loop"); return; } @@ -912,11 +906,9 @@ where ) -> eyre::Result<()> { let _write_guard = self.write_lock.lock().await; let provider_ro = self.storage.provider_ro()?; - let earliest_stored = provider_ro - .get_earliest_block_number()? + let earliest_stored = opt_block(provider_ro.get_earliest_block())? .ok_or_else(|| eyre!("no earliest proof-history block stored"))?; - let latest_stored = provider_ro - .get_latest_block_number()? + let latest_stored = opt_block(provider_ro.get_latest_block())? .ok_or_else(|| eyre!("no latest proof-history block stored"))? .0; let earliest_stored = BlockNumHash::new(earliest_stored.0, earliest_stored.1); @@ -979,7 +971,7 @@ where let Some(block) = chain.blocks().get(&block_number) && let Some(trie_data) = chain.trie_data_at(block_number) { - let SortedTrieData { hashed_state, trie_updates } = trie_data.get(); + let SortedTrieData { hashed_state, trie_updates } = &trie_data.get().sorted; collector.store_block_updates( block.block_with_parent(), (**trie_updates).clone(), @@ -1041,7 +1033,7 @@ where } return Ok(()); }; - let SortedTrieData { hashed_state, trie_updates } = trie_data.get(); + let SortedTrieData { hashed_state, trie_updates } = &trie_data.get().sorted; block_updates.push(( block.block_with_parent(), trie_updates.clone(), diff --git a/crates/node/src/proof_history/storage_init.rs b/crates/node/src/proof_history/storage_init.rs index 09136db..25841b7 100644 --- a/crates/node/src/proof_history/storage_init.rs +++ b/crates/node/src/proof_history/storage_init.rs @@ -28,8 +28,8 @@ where Storage: OpProofsStore, { let provider_ro = storage.provider_ro()?; - Ok(provider_ro.get_earliest_block_number()?.is_none() || - provider_ro.get_latest_block_number()?.is_none()) + Ok(super::opt_block(provider_ro.get_earliest_block())?.is_none() || + super::opt_block(provider_ro.get_latest_block())?.is_none()) } /// File stored beside the proof-history MDBX database to validate historical init resume targets. diff --git a/crates/payload/src/builder/execution.rs b/crates/payload/src/builder/execution.rs index 934a01e..3e5069f 100644 --- a/crates/payload/src/builder/execution.rs +++ b/crates/payload/src/builder/execution.rs @@ -82,7 +82,7 @@ pub(super) fn execute_provided_transactions( let recovered_tx: Recovered<::SignedTx> = tx.clone(); let gas_used = match builder.execute_transaction(recovered_tx) { - Ok(gas_used) => gas_used, + Ok(gas_output) => gas_output.tx_gas_used(), Err(err) if is_zk_gas_limit_exceeded(&err) => { debug!( target: "payload_builder", @@ -150,9 +150,9 @@ where // Execute the anchor transaction as the first transaction in the block // NOTE: anchor transaction does not contribute to the total DA size limit calculation. match builder.execute_transaction(ctx.anchor_tx.clone()) { - Ok(gas_used) => { + Ok(gas_output) => { // Note: Anchor transaction has zero priority fee (tip), so no fees to add - debug!(target: "payload_builder", id=%ctx.payload_id, gas_used, "anchor transaction executed successfully"); + debug!(target: "payload_builder", id=%ctx.payload_id, gas_used = gas_output.tx_gas_used(), "anchor transaction executed successfully"); } Err(err) if is_zk_gas_limit_exceeded(&err) => { debug!( diff --git a/crates/payload/src/builder/mod.rs b/crates/payload/src/builder/mod.rs index 7a08518..5baa70b 100644 --- a/crates/payload/src/builder/mod.rs +++ b/crates/payload/src/builder/mod.rs @@ -6,7 +6,7 @@ use reth_basic_payload_builder::{ use reth_ethereum::EthPrimitives; use reth_evm::{ ConfigureEvm, - execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor}, + execute::{BlockBuilder, BlockBuilderOutcome}, }; use reth_evm_ethereum::RethReceiptBuilder; use reth_execution_cache::{CachedStateMetrics, CachedStateMetricsSource, CachedStateProvider}; @@ -173,20 +173,20 @@ where let BuildArguments { mut cached_reads, execution_cache, - trie_handle, + mut state_root_handle, config, cancel, best_payload: _, } = args; let attributes = normalize_payload_config(&config)?; - let PayloadConfig { parent_header, attributes: _, payload_id } = config; + let PayloadConfig { parent_header, attributes: _, payload_id, .. } = config; let mut state_provider = client.state_by_block_hash(parent_header.hash())?; if let Some(execution_cache) = execution_cache { state_provider = Box::new(CachedStateProvider::new( state_provider, execution_cache.cache().clone(), - CachedStateMetrics::zeroed(CachedStateMetricsSource::Builder), + Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Builder)), )); } let state = StateProviderDatabase::new(state_provider.as_ref()); @@ -210,8 +210,11 @@ where ) .map_err(PayloadBuilderError::other)?; - if let Some(ref handle) = trie_handle { - builder.executor_mut().set_state_hook(Some(Box::new(handle.state_hook()))); + // If we have a state-root task, wire a state hook that streams per-tx state diffs. Hooks + // are delivered through the `State` database in reth v2.4.0. + if let Some(task) = state_root_handle.as_mut() { + reth_evm::Evm::db_mut(builder.evm_mut()) + .set_state_hook(Some(Box::new(task.take_state_hook()))); } builder.apply_pre_execution_changes().map_err(PayloadBuilderError::other)?; @@ -252,10 +255,10 @@ where }; let BlockBuilderOutcome { execution_result: _, block, .. } = if let Some(mut handle) = - trie_handle + state_root_handle { // Drop the state hook so the trie task sees the final state updates and can finalize. - builder.executor_mut().set_state_hook(None); + reth_evm::Evm::db_mut(builder.evm_mut()).set_state_hook(None); // The sparse trie computes alongside transaction execution, so this usually just waits // for the last root/trie update. If that pipeline fails, fall back to synchronous state @@ -277,10 +280,9 @@ where builder.finish(state_provider.as_ref(), None)? }; - let sealed_block = Arc::new(block.into_sealed_block()); - debug!(target: "payload_builder", id=%payload_id, sealed_block_header = ?sealed_block.sealed_header(), "sealed built block"); + debug!(target: "payload_builder", id=%payload_id, sealed_block_header = ?block.sealed_header(), "sealed built block"); - Ok(BuildOutcome::Freeze(EthBuiltPayload::new(sealed_block, total_fees, None, None))) + Ok(BuildOutcome::Freeze(EthBuiltPayload::new(Arc::new(block), total_fees, None, None))) } #[cfg(test)] diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 50daab0..328f9a0 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -38,6 +38,7 @@ jsonrpsee-core = { workspace = true } jsonrpsee-types = { workspace = true } op-alloy-flz = { workspace = true } reth = { workspace = true } +reth-chain-state = { workspace = true } reth-db = { workspace = true } reth-db-api = { workspace = true } reth-engine-primitives = { workspace = true } diff --git a/crates/rpc/src/engine/api.rs b/crates/rpc/src/engine/api.rs index 1717e6c..38c6afd 100644 --- a/crates/rpc/src/engine/api.rs +++ b/crates/rpc/src/engine/api.rs @@ -24,7 +24,8 @@ use reth_ethereum_engine_primitives::EthBuiltPayload; use reth_node_api::{EngineTypes, PayloadBuilderError, PayloadTypes}; use reth_payload_primitives::PayloadKind; use reth_provider::{ - BlockReader, DBProvider, DatabaseProviderFactory, HeaderProvider, StateProviderFactory, + BalProvider, BlockReader, DBProvider, DatabaseProviderFactory, HeaderProvider, + StateProviderFactory, }; use reth_rpc::EngineApi; use reth_rpc_engine_api::{EngineApiError, EngineCapabilities}; @@ -93,7 +94,8 @@ impl TaikoEngineApi where Provider: - HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory + 'static, + HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory + + BalProvider + 'static, PayloadT: PayloadTypes, Pool: TransactionPool + 'static, ChainSpec: EthereumHardforks + Send + Sync + 'static, @@ -117,7 +119,8 @@ impl TaikoEngineApi where Provider: - HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory + 'static, + HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory + + BalProvider + 'static, EngineT: EngineTypes< ExecutionData = TaikoExecutionData, PayloadAttributes = TaikoPayloadAttributes, @@ -196,7 +199,8 @@ impl TaikoEngineApiServer where Provider: - HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory + 'static, + HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory + + BalProvider + 'static, EngineT: EngineTypes< ExecutionData = TaikoExecutionData, PayloadAttributes = TaikoPayloadAttributes, diff --git a/crates/rpc/src/engine/validator.rs b/crates/rpc/src/engine/validator.rs index 3e92e26..6c03e16 100644 --- a/crates/rpc/src/engine/validator.rs +++ b/crates/rpc/src/engine/validator.rs @@ -12,6 +12,7 @@ use alloy_primitives::B256; use alloy_rpc_types_engine::{ExecutionPayloadV1, PayloadError}; use alloy_rpc_types_eth::Withdrawals; use reth::{chainspec::EthChainSpec, primitives::RecoveredBlock}; +use reth_chain_state::StateTrieOverlayManager; use reth_engine_primitives::EngineApiValidator; use reth_engine_tree::tree::{TreeConfig, payload_validator::BasicEngineValidator}; use reth_ethereum::{Block, EthPrimitives}; @@ -82,6 +83,7 @@ where ctx: &AddOnsContext<'_, N>, tree_config: TreeConfig, changeset_cache: ChangesetCache, + state_trie_overlays: StateTrieOverlayManager<::Primitives>, ) -> eyre::Result { let validator = >::build(self, ctx).await?; let data_dir = ctx.config.datadir.clone().resolve_datadir(ctx.config.chain.chain()); @@ -94,6 +96,7 @@ where tree_config, invalid_block_hook, changeset_cache, + state_trie_overlays, ctx.node.task_executor().clone(), )) } diff --git a/crates/rpc/src/proof_state.rs b/crates/rpc/src/proof_state.rs index db07930..5564ce4 100644 --- a/crates/rpc/src/proof_state.rs +++ b/crates/rpc/src/proof_state.rs @@ -183,11 +183,11 @@ where ) -> ProviderResult> { let provider_ro = self.storage.provider_ro().map_err(ProviderError::from)?; - let latest = provider_ro.get_latest_block_number().map_err(ProviderError::from)?; - let earliest = provider_ro.get_earliest_block_number().map_err(ProviderError::from)?; - let bounds = latest - .zip(earliest) - .map(|((latest_number, _), (earliest_number, _))| (earliest_number, latest_number)); + let bounds = match provider_ro.get_proof_window() { + Ok(window) => Some((window.earliest.number, window.latest.number)), + Err(reth_optimism_trie::OpProofsStorageError::NoBlocksFound) => None, + Err(err) => return Err(ProviderError::from(err)), + }; let reconciled = self.readiness.is_ready(); if !proof_history_can_serve(reconciled, block_number, bounds) { diff --git a/vendor/reth-optimism-trie/src/lib.rs b/vendor/reth-optimism-trie/src/lib.rs index 90b3e0b..00753df 100644 --- a/vendor/reth-optimism-trie/src/lib.rs +++ b/vendor/reth-optimism-trie/src/lib.rs @@ -33,6 +33,10 @@ pub use backfill::{BackfillError, BackfillJob, DEFAULT_BACKFILL_BATCH_SIZE}; pub mod snapshot; pub use snapshot::{SnapshotError, SnapshotInitJob, SnapshotInitOutcome}; +/// Taiko: retained from upstream `bcf489ea` (upstream moved live collection into `engine`); +/// see the module docs for details. +pub mod live; + pub mod in_memory; pub use in_memory::{ InMemoryAccountCursor, InMemoryProofsStorage, InMemoryStorageCursor, InMemoryTrieCursor, diff --git a/vendor/reth-optimism-trie/src/live.rs b/vendor/reth-optimism-trie/src/live.rs new file mode 100644 index 0000000..51e64fb --- /dev/null +++ b/vendor/reth-optimism-trie/src/live.rs @@ -0,0 +1,220 @@ +//! Live trie collector for external proofs storage. +//! +//! Taiko provenance note: upstream replaced this module with the `engine` service between the +//! previously pinned revision (`bcf489ea`) and the vendored revision (`9b802fdb`). Alethia's +//! proof-history sidecar drives collection itself, so the collector is retained here, ported to +//! the current storage API (`get_proof_window`). + +use crate::{ + BlockStateDiff, OpProofsStorage, OpProofsStorageError, OpProofsStore, + api::{OpProofsProviderRO, OpProofsProviderRw, OperationDurations}, + provider::OpProofsStateProviderRef, +}; +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use derive_more::Constructor; +use reth_evm::{ConfigureEvm, execute::Executor}; +use reth_primitives_traits::{AlloyBlockHeader, BlockTy, RecoveredBlock}; +use reth_provider::{ + DatabaseProviderFactory, HashedPostStateProvider, StateProviderFactory, StateReader, + StateRootProvider, +}; +use reth_revm::database::StateProviderDatabase; +use reth_trie_common::{HashedPostStateSorted, updates::TrieUpdatesSorted}; +use std::{sync::Arc, time::Instant}; +use tracing::info; + +/// Live trie collector for external proofs storage. +#[derive(Debug, Constructor)] +pub struct LiveTrieCollector<'tx, Evm, Provider, PreimageStore> +where + Evm: ConfigureEvm, + Provider: StateReader + DatabaseProviderFactory + StateProviderFactory, +{ + evm_config: Evm, + provider: Provider, + storage: &'tx OpProofsStorage, +} + +impl<'tx, Evm, Provider, Store> LiveTrieCollector<'tx, Evm, Provider, Store> +where + Evm: ConfigureEvm, + Provider: StateReader + DatabaseProviderFactory + StateProviderFactory, + Store: 'tx + OpProofsStore + Clone + 'static, +{ + /// Execute a block and store the updates in the storage. + pub fn execute_and_store_block_updates( + &self, + block: &RecoveredBlock>, + ) -> Result<(), OpProofsStorageError> { + let mut operation_durations = OperationDurations::default(); + + let start = Instant::now(); + // ensure that we have the state of the parent block + let provider_ro = self.storage.provider_ro()?; + // Errors with `NoBlocksFound` when the proof window is empty. + let window = provider_ro.get_proof_window()?; + let (earliest, latest) = (window.earliest.number, window.latest.number); + + let parent_block_number = block.number() - 1; + if parent_block_number < earliest { + return Err(OpProofsStorageError::UnknownParent); + } + + if parent_block_number > latest { + return Err(OpProofsStorageError::MissingParentBlock { + block_number: block.number(), + parent_block_number, + latest_block_number: latest, + }); + } + + let block_ref = + BlockWithParent::new(block.parent_hash(), NumHash::new(block.number(), block.hash())); + + // TODO: should we check block hash here? + + let state_provider = OpProofsStateProviderRef::new( + self.provider.state_by_block_hash(block.parent_hash())?, + self.storage.provider_ro()?, + parent_block_number, + ); + + let db = StateProviderDatabase::new(&state_provider); + let block_executor = self.evm_config.batch_executor(db); + + let execution_result = block_executor.execute(&(*block).clone())?; + + operation_durations.execution_duration_seconds = start.elapsed(); + + let hashed_state = state_provider.hashed_post_state(&execution_result.state); + let (state_root, trie_updates) = + state_provider.state_root_with_updates(hashed_state.clone())?; + + operation_durations.state_root_duration_seconds = + start.elapsed() - operation_durations.execution_duration_seconds; + + if state_root != block.state_root() { + return Err(OpProofsStorageError::StateRootMismatch { + block_number: block.number(), + current_state_hash: state_root, + expected_state_hash: block.state_root(), + }); + } + + let provider_rw = self.storage.provider_rw()?; + let update_result = provider_rw.store_trie_updates( + block_ref, + BlockStateDiff { + sorted_trie_updates: trie_updates.into_sorted(), + sorted_post_state: hashed_state.into_sorted(), + }, + )?; + provider_rw.commit()?; + + operation_durations.total_duration_seconds = start.elapsed(); + operation_durations.write_duration_seconds = operation_durations.total_duration_seconds - + operation_durations.state_root_duration_seconds - + operation_durations.execution_duration_seconds; + + + info!( + block_number = block.number(), + ?operation_durations, + ?update_result, + "Block executed and trie updates stored successfully", + ); + + Ok(()) + } + + /// Store trie updates for a given block. + pub fn store_block_updates( + &self, + block: BlockWithParent, + sorted_trie_updates: TrieUpdatesSorted, + sorted_post_state: HashedPostStateSorted, + ) -> Result<(), OpProofsStorageError> { + let start = Instant::now(); + let mut operation_durations = OperationDurations::default(); + + let provider_rw = self.storage.provider_rw()?; + let storage_result = provider_rw + .store_trie_updates(block, BlockStateDiff { sorted_trie_updates, sorted_post_state })?; + provider_rw.commit()?; + + let write_duration = start.elapsed(); + operation_durations.total_duration_seconds = write_duration; + operation_durations.write_duration_seconds = write_duration; + + + info!( + block_number = block.block.number, + ?operation_durations, + ?storage_result, + "Trie updates stored successfully", + ); + + Ok(()) + } + + /// Handles chain reorganizations by replacing block updates after a common ancestor. + /// + /// This method removes all block updates after the latest common ancestor (the block before + /// the first block in `new_blocks`) and replaces them with the updates from the provided new + /// chain. + /// + /// # Arguments + /// + /// * `new_blocks` - A vector of references to `RecoveredBlock` instances representing the new + /// blocks to be added to the trie storage. + pub fn unwind_and_store_block_updates( + &self, + block_updates: Vec<(BlockWithParent, Arc, Arc)>, + ) -> Result<(), OpProofsStorageError> { + if block_updates.is_empty() { + return Ok(()); + } + + let start = Instant::now(); + let mut operation_durations = OperationDurations::default(); + let first = &block_updates[0].0; + let latest_common_block = + BlockNumHash::new(first.block.number.saturating_sub(1), first.parent); + let mut block_trie_updates: Vec<(BlockWithParent, BlockStateDiff)> = + Vec::with_capacity(block_updates.len()); + + for (block, trie_updates, hashed_state) in &block_updates { + block_trie_updates.push(( + *block, + BlockStateDiff { + sorted_trie_updates: (**trie_updates).clone(), + sorted_post_state: (**hashed_state).clone(), + }, + )); + } + + let provider_rw = self.storage.provider_rw()?; + provider_rw.replace_updates(latest_common_block, block_trie_updates)?; + provider_rw.commit()?; + let write_duration = start.elapsed(); + operation_durations.total_duration_seconds = write_duration; + operation_durations.write_duration_seconds = write_duration; + + + info!( + start_block_number = block_updates.first().map(|(b, _, _)| b.block.number), + end_block_number = block_updates.last().map(|(b, _, _)| b.block.number), + ?operation_durations, + "Trie updates rewound and stored successfully", + ); + Ok(()) + } + + /// Remove account, storage and trie updates from historical storage for all blocks from + /// the specified block (inclusive). + pub fn unwind_history(&self, to: BlockWithParent) -> Result<(), OpProofsStorageError> { + let provider_rw = self.storage.provider_rw()?; + provider_rw.unwind_history(to)?; + provider_rw.commit() + } +} From 9d6880da667fe15c056771545af4706681815021 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 Jul 2026 23:14:49 +0800 Subject: [PATCH 05/28] fix: migrate tests and prover paths to reth v2.4.0 APIs All-features workspace now compiles and the full suite passes (706 tests, including the vendored reth-optimism-trie suite). Co-Authored-By: Claude Fable 5 --- crates/block/src/assembler.rs | 2 ++ crates/block/src/derived_block.rs | 1 + crates/block/src/executor.rs | 2 ++ crates/block/src/testutil.rs | 4 ++-- crates/consensus/src/validation/tests.rs | 2 +- crates/evm/src/alloy.rs | 2 +- crates/node/src/proof_history/storage_init.rs | 14 ++++++++++---- crates/payload/src/builder/execution.rs | 2 +- crates/payload/src/builder/mod.rs | 1 + crates/primitives/src/payload/builder.rs | 1 + crates/rpc/src/engine/api.rs | 6 +++--- 11 files changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/block/src/assembler.rs b/crates/block/src/assembler.rs index d354d06..8a91ab7 100644 --- a/crates/block/src/assembler.rs +++ b/crates/block/src/assembler.rs @@ -159,6 +159,7 @@ mod test { &bundle_state, &state_provider, B256::ZERO, + None, )) .expect("Unzen block should assemble"); @@ -206,6 +207,7 @@ mod test { &bundle_state, &state_provider, B256::ZERO, + None, )) .expect("Unzen block should assemble"); diff --git a/crates/block/src/derived_block.rs b/crates/block/src/derived_block.rs index 7ebdbb1..1578d3c 100644 --- a/crates/block/src/derived_block.rs +++ b/crates/block/src/derived_block.rs @@ -129,6 +129,7 @@ pub fn assemble_filtered_block( &bundle_state, &state_provider, state_root, + None, ))?; Ok(RecoveredBlock::new_unhashed(block, senders)) diff --git a/crates/block/src/executor.rs b/crates/block/src/executor.rs index 7ad37de..5753942 100644 --- a/crates/block/src/executor.rs +++ b/crates/block/src/executor.rs @@ -225,6 +225,7 @@ where Transaction: Transaction + Encodable2718 + Clone, Receipt: TxReceipt, >, + ::TxType: Send + 'static, { /// Executes a prover candidate block and returns the transactions that were actually committed. pub fn execute_block_with_committed_transactions<'tx>( @@ -272,6 +273,7 @@ where > + TaikoZkGasEvm, Spec: TaikoExecutorSpec + Clone, R: ReceiptBuilder>, + ::TxType: Send + 'static, { /// Executes `tx` and returns `Ok(true)` if it committed, `Ok(false)` if it was filtered as a /// recoverable non-anchor failure, or `Err` for fatal / anchor errors. diff --git a/crates/block/src/testutil.rs b/crates/block/src/testutil.rs index 1ca6beb..507a096 100644 --- a/crates/block/src/testutil.rs +++ b/crates/block/src/testutil.rs @@ -184,9 +184,9 @@ where &mut self, tx: impl ExecutorTx, f: impl FnOnce(&::Result) -> reth_evm::block::CommitChanges, - ) -> Result, BlockExecutionError> { + ) -> Result, BlockExecutionError> { let tx = tx.into_parts(); - Ok(self.executor.execute_transaction_with_commit_condition(tx, f)?.map(|g| g.tx_gas_used())) + self.executor.execute_transaction_with_commit_condition(tx, f) } fn finish( diff --git a/crates/consensus/src/validation/tests.rs b/crates/consensus/src/validation/tests.rs index 69d55b4..a032b31 100644 --- a/crates/consensus/src/validation/tests.rs +++ b/crates/consensus/src/validation/tests.rs @@ -254,7 +254,7 @@ fn unzen_post_execution_rejects_body_past_truncation_point() { let err = >::validate_block_post_execution( - &consensus, &recovered, &result, None, + &consensus, &recovered, &result, None, None, ) .expect_err("Unzen blocks must reject bodies that extend past the truncation point"); assert!(matches!(err, ConsensusError::Other(_))); diff --git a/crates/evm/src/alloy.rs b/crates/evm/src/alloy.rs index eb95ae2..17e7f89 100644 --- a/crates/evm/src/alloy.rs +++ b/crates/evm/src/alloy.rs @@ -464,7 +464,7 @@ mod tests { ); let next_tx_id = journal.transaction_id; - assert_eq!(next_tx_id, 1, "synthetic pre-execution load should advance tx id"); + assert_eq!(next_tx_id.get(), 1, "synthetic pre-execution load should advance tx id"); let golden_touch_account = witness_state .get(&golden_touch) diff --git a/crates/node/src/proof_history/storage_init.rs b/crates/node/src/proof_history/storage_init.rs index 25841b7..56123a2 100644 --- a/crates/node/src/proof_history/storage_init.rs +++ b/crates/node/src/proof_history/storage_init.rs @@ -431,7 +431,10 @@ mod tests { use super::*; use alloy_consensus::Header; use reth_ethereum_primitives::EthPrimitives; - use reth_optimism_trie::{InMemoryProofsStorage, OpProofsStorage, api::OpProofsProviderRw}; + use reth_optimism_trie::{ + InMemoryProofsStorage, OpProofsStorage, + api::OpProofsInitProvider, + }; use reth_provider::test_utils::MockEthProvider; #[test] @@ -441,9 +444,12 @@ mod tests { assert!(proof_history_storage_needs_initialization(&storage).unwrap()); - let provider_rw = storage.provider_rw().expect("provider_rw"); - provider_rw.set_earliest_block_number(0, B256::ZERO).expect("set earliest block"); - provider_rw.commit().expect("commit"); + let initializer = storage.initialization_provider().expect("initialization provider"); + initializer + .set_initial_state_anchor(alloy_eips::BlockNumHash::new(0, B256::ZERO)) + .expect("set initial state anchor"); + initializer.commit_initial_state().expect("commit initial state"); + initializer.commit().expect("commit"); assert!(!proof_history_storage_needs_initialization(&storage).unwrap()); } diff --git a/crates/payload/src/builder/execution.rs b/crates/payload/src/builder/execution.rs index 3e5069f..7be5ccf 100644 --- a/crates/payload/src/builder/execution.rs +++ b/crates/payload/src/builder/execution.rs @@ -290,7 +290,7 @@ mod tests { &mut self, tx: impl ExecutorTx, f: impl FnOnce(&::Result) -> CommitChanges, - ) -> Result, BlockExecutionError> { + ) -> Result, BlockExecutionError> { if self.fail_next_execution { self.fail_next_execution = false; return Err(BlockExecutionError::other(ZkGasLimitExceeded)); diff --git a/crates/payload/src/builder/mod.rs b/crates/payload/src/builder/mod.rs index 5baa70b..72c6ebc 100644 --- a/crates/payload/src/builder/mod.rs +++ b/crates/payload/src/builder/mod.rs @@ -315,6 +315,7 @@ mod tests { withdrawals: Some(Vec::new()), parent_beacon_block_root: Some(B256::repeat_byte(0x33)), slot_number: None, + target_gas_limit: None, }, base_fee_per_gas, block_metadata: TaikoBlockMetadata { diff --git a/crates/primitives/src/payload/builder.rs b/crates/primitives/src/payload/builder.rs index f0320e4..4247ec6 100644 --- a/crates/primitives/src/payload/builder.rs +++ b/crates/primitives/src/payload/builder.rs @@ -288,6 +288,7 @@ mod test { withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), slot_number: None, + target_gas_limit: None, } } diff --git a/crates/rpc/src/engine/api.rs b/crates/rpc/src/engine/api.rs index 38c6afd..0dcdf8f 100644 --- a/crates/rpc/src/engine/api.rs +++ b/crates/rpc/src/engine/api.rs @@ -322,7 +322,6 @@ mod tests { use alloy_eips::merge::BEACON_NONCE; use alloy_hardforks::ForkCondition; use alloy_primitives::{Address, B256, Bytes, U256}; - use reth_primitives_traits::Block as _; use std::sync::Arc; #[test] @@ -378,9 +377,10 @@ mod tests { fn sample_built_payload(difficulty: U256, fees: U256, timestamp: u64) -> EthBuiltPayload { let block = sample_unzen_block(difficulty, timestamp); - let sealed_block = Arc::new(block.seal_slow()); + let recovered_block = + Arc::new(reth_primitives_traits::RecoveredBlock::new_unhashed(block, Vec::new())); - EthBuiltPayload::new(sealed_block, fees, None, None) + EthBuiltPayload::new(recovered_block, fees, None, None) } fn sample_unzen_block(difficulty: U256, timestamp: u64) -> reth_ethereum::Block { From 516b708da381c84458524e36caf64bf60b86fe8a Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 Jul 2026 23:47:40 +0800 Subject: [PATCH 06/28] feat(evm): support revmc JIT execution on reth v2.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire upstream revmc (pinned at 56ed0063, reth v2.4.0's locked revision fast-forwarded to include the revmc#395 dynamic-gas failure-order fix) into the Taiko EVM, using reth v2.4.0's native JIT surfaces. Proving isolation — compiled code can only run when every gate passes: - build: workspace jit features (bin default; --no-default-features builds LLVM-free) - runtime: --jit / upstream reth_jit RPC (via ConfigureEvm::jit_backend) - local opt-in: with_jit_support() from reth's engine tree and the Taiko payload builder; RPC execution keeps the base config (interpreter) - fork allowlist: spec_supports_jit is an exhaustive match — Unzen stays interpreter-only (zk-gas needs per-opcode hooks), new forks fail compilation until classified; schedule_for() re-checked as guard Inspected execution always selects the disabled backend, and TaikoEvmWrapper::transact_raw drives JitEvm with TaikoEvmHandler so anchor/fee-share semantics are preserved. Adds JIT-vs-interpreter differential tests and config isolation tests (714 tests pass with the jit feature); ports LLVM 22 CI/Docker infra. Co-Authored-By: Claude Fable 5 --- .github/scripts/install_llvm.sh | 16 ++ .github/scripts/install_llvm_ubuntu.sh | 31 +++ .github/workflows/ci.yml | 36 +++- Cargo.lock | 252 +++++++++++++++++++++++- Cargo.toml | 7 + Dockerfile | 14 +- README.md | 39 ++++ bin/alethia-reth/Cargo.toml | 4 + bin/alethia-reth/src/main.rs | 14 ++ crates/block/Cargo.toml | 1 + crates/block/src/config.rs | 148 +++++++++++++- crates/block/src/executor.rs | 10 +- crates/block/src/factory.rs | 2 +- crates/block/src/tx_selection/mod.rs | 4 +- crates/evm/Cargo.toml | 2 + crates/evm/src/alloy.rs | 148 ++++++++++---- crates/evm/src/execution.rs | 42 +++- crates/evm/src/factory.rs | 151 +++++++++++++- crates/evm/src/jit.rs | 129 ++++++++++++ crates/evm/src/lib.rs | 2 + crates/evm/src/zk_gas/tests.rs | 128 ++++++++++-- crates/node/Cargo.toml | 2 + crates/node/src/components.rs | 60 +++++- crates/node/src/lib.rs | 2 + crates/payload/src/builder/execution.rs | 4 +- crates/payload/src/builder/mod.rs | 4 + justfile | 5 +- 27 files changed, 1181 insertions(+), 76 deletions(-) create mode 100755 .github/scripts/install_llvm.sh create mode 100755 .github/scripts/install_llvm_ubuntu.sh create mode 100644 crates/evm/src/jit.rs diff --git a/.github/scripts/install_llvm.sh b/.github/scripts/install_llvm.sh new file mode 100755 index 0000000..16f9036 --- /dev/null +++ b/.github/scripts/install_llvm.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eo pipefail + +os=${1:?usage: install_llvm.sh [version]} +version=${2:-22} +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +case "$os" in + ubuntu) + sudo "$script_dir/install_llvm_ubuntu.sh" "$version" + ;; + *) + echo "unsupported OS: $os" >&2 + exit 1 + ;; +esac diff --git a/.github/scripts/install_llvm_ubuntu.sh b/.github/scripts/install_llvm_ubuntu.sh new file mode 100755 index 0000000..36a31f3 --- /dev/null +++ b/.github/scripts/install_llvm_ubuntu.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -eo pipefail + +version=${1:-22} +bins=(clang llvm-config lld ld.lld FileCheck) + +# The official installer needs this package on Debian bookworm but not on newer distributions. +apt-get update -qq +apt-get install -y --no-install-recommends \ + lsb-release wget gnupg ca-certificates +apt-get install -y --no-install-recommends software-properties-common 2>/dev/null || true + +llvm_installer=$(mktemp) +wget -qO "$llvm_installer" https://apt.llvm.org/llvm.sh +# Pin the upstream installer so CI and Docker builds fail loudly when it changes; review the new +# script before updating this hash. +echo "9474ecd78b52aba6e923976b1e9773f5613027cc7e237b9956986cb536e02a36 $llvm_installer" | sha256sum -c - +chmod +x "$llvm_installer" +"$llvm_installer" "$version" all +rm -f "$llvm_installer" + +for bin in "${bins[@]}"; do + if ! command -v "$bin-$version" &>/dev/null; then + echo "Warning: $bin-$version not found" >&2 + continue + fi + ln -fs "$(command -v "$bin-$version")" "/usr/bin/$bin" +done + +echo "LLVM $version installed:" +llvm-config --version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 028b187..43c5f55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 + - run: .github/scripts/install_llvm.sh ubuntu - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.94.0 + toolchain: 1.95.0 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -40,7 +41,7 @@ jobs: - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.94.0 + toolchain: 1.95.0 components: rustfmt - uses: extractions/setup-just@v2 with: @@ -54,9 +55,10 @@ jobs: steps: - uses: actions/checkout@v5 - uses: rui314/setup-mold@v1 + - run: .github/scripts/install_llvm.sh ubuntu - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.94.0 + toolchain: 1.95.0 components: clippy - uses: Swatinem/rust-cache@v2 with: @@ -65,3 +67,31 @@ jobs: with: just-version: 1.5.0 - run: just clippy + + check-no-jit: + name: check (no default features) + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + - uses: rui314/setup-mold@v1 + # LLVM is deliberately not installed: this proves the interpreter-only build stays free + # of the LLVM toolchain dependency. + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.95.0 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo check --locked -p alethia-reth-bin --no-default-features + + llvm-arm64: + name: llvm install (linux/arm64) + runs-on: ubuntu-24.04-arm + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + # Guards the Docker build on ARM64 hosts: apt.llvm.org omits arm64 packages for some + # Debian releases (bookworm has no arm64 LLVM 22), which only surfaces when installing + # on an ARM64 machine. Runs the same installer inside the Docker base distribution. + - run: docker run --rm -v "$PWD/.github/scripts:/scripts:ro" debian:trixie /scripts/install_llvm_ubuntu.sh diff --git a/Cargo.lock b/Cargo.lock index 8122179..7434def 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,7 @@ dependencies = [ "reth-evm", "reth-revm", "revm-database-interface", + "revmc", "serde", "tracing", ] @@ -237,6 +238,7 @@ dependencies = [ "reth-execution-types", "reth-node-api", "reth-node-builder", + "reth-node-core", "reth-node-ethereum", "reth-optimism-trie", "reth-primitives-traits", @@ -2474,7 +2476,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.9", ] [[package]] @@ -4633,6 +4635,30 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inkwell" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7decbc9dfa45a4a827a6ff7b822c113b1285678a937e84213417d4ca8a095782" +dependencies = [ + "bitflags", + "inkwell_internals", + "libc", + "llvm-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "inkwell_internals" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cfe97ee860815a90ed17e09639513269e39420a7440f3f4c996f238c514cf8d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "inotify" version = "0.11.4" @@ -5180,6 +5206,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -5296,6 +5332,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "llvm-sys" +version = "221.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2abcc34a3b190f03c2a61b555f218f529589ff13657bdd2ff8ac3e85f2abe6bb" +dependencies = [ + "anyhow", + "cc", + "lazy_static", + "libc", + "regex-lite", + "semver 1.0.28", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -5893,6 +5943,15 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -6020,6 +6079,15 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "oxc_index" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191884bee6c3744909a51acc7d78d4ae370d817b25875b10642f632327b6296e" +dependencies = [ + "nonmax", +] + [[package]] name = "p256" version = "0.13.2" @@ -6130,6 +6198,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -6954,6 +7028,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.11" @@ -9711,6 +9791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33493cf6d996e9242db4f2002caef48ec9aa8c4f3fff3b18aed10ef3942eec7" dependencies = [ "bitvec", + "paste", "phf 0.13.1", "revm-primitives", "serde", @@ -9901,6 +9982,150 @@ dependencies = [ "serde", ] +[[package]] +name = "revmc" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-primitives", + "revmc-backend", + "revmc-build", + "revmc-builtins", + "revmc-codegen", + "revmc-context", + "revmc-llvm", + "revmc-runtime", +] + +[[package]] +name = "revmc-backend" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "eyre", + "ruint", +] + +[[package]] +name = "revmc-build" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" + +[[package]] +name = "revmc-builtins" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "paste", + "revm-bytecode", + "revm-context-interface", + "revm-interpreter", + "revm-primitives", + "revmc-backend", + "revmc-context", + "tracing", +] + +[[package]] +name = "revmc-codegen" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "alloy-primitives", + "bitflags", + "bitvec", + "derive_more", + "either", + "indexmap 2.14.0", + "oxc_index", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-primitives", + "revm-state", + "revmc-backend", + "revmc-build", + "revmc-builtins", + "revmc-context", + "revmc-llvm", + "smallvec", + "tempfile", + "tracing", +] + +[[package]] +name = "revmc-context" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "revm-context", + "revm-context-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-primitives", + "revm-state", + "ruint", +] + +[[package]] +name = "revmc-llvm" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "alloy-primitives", + "cc", + "inkwell", + "object", + "revmc-backend", + "tracing", +] + +[[package]] +name = "revmc-runtime" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +dependencies = [ + "alloy-primitives", + "crossbeam-channel", + "crossbeam-queue", + "dashmap", + "derive_more", + "eyre", + "libc", + "libloading 0.9.0", + "quanta", + "rayon", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-primitives", + "revm-state", + "revmc-backend", + "revmc-builtins", + "revmc-codegen", + "revmc-context", + "revmc-llvm", + "tempfile", + "tracing", + "wait-timeout", + "wincode", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -12197,6 +12422,31 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wincode" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d967db7705dc29120bb6e8ce5b5a2e27734ed5976d1c904e95bd238d1c3c5a" +dependencies = [ + "pastey", + "proc-macro2", + "quote", + "thiserror 2.0.18", + "wincode-derive", +] + +[[package]] +name = "wincode-derive" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15ab90b719560d0fda79c74550ad1c948d17b118765942838055ebaf34d67071" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "windows" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index f9f1ec2..f9c04e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,6 +154,13 @@ reth-optimism-trie = { path = "vendor/reth-optimism-trie", default-features = fa # revm revm-database-interface = { version = "41.0.0", default-features = false } +# revmc JIT: pinned to the fast-forward of reth v2.4.0's locked revision (7e3536d) that adds +# revmc#395 (dynamic-gas failure ordering). revmc#391 (non-blocking runtime controls) is +# already included. Bump alongside the reth pin. +revmc = { git = "https://github.com/paradigmxyz/revmc", rev = "56ed00631c4542507f7198bdc9d53dd6471a2ef8", default-features = false, features = [ + "llvm-prefer-static", +] } + # serialization serde = { version = "1.0", default-features = false } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } diff --git a/Dockerfile b/Dockerfile index e029ab1..a1754e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,23 @@ -FROM rust:1.94.0-bookworm AS build +# Trixie rather than bookworm: apt.llvm.org publishes arm64 LLVM 22 packages only for trixie +# (the bookworm arm64 index carries just docs and WASM cross libs), and the runtime stage must +# share the build stage's glibc. +FROM rust:1.95.0-trixie AS build WORKDIR /app -RUN apt-get update && \ +COPY .github/scripts/install_llvm_ubuntu.sh /tmp/install_llvm.sh + +RUN /tmp/install_llvm.sh && rm /tmp/install_llvm.sh && \ + apt-get update && \ apt-get -y upgrade && \ apt-get install -y git libclang-dev pkg-config curl build-essential && \ rm -rf /var/lib/apt/lists/* COPY ./ . -RUN cargo build --release +RUN cargo build --release --locked -FROM debian:bookworm-slim +FROM debian:trixie-slim RUN apt-get update && \ apt-get install -y jq curl ca-certificates && \ diff --git a/README.md b/README.md index a890769..5c6608f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,20 @@ cargo build --release The main binary will be located at `target/release/alethia-reth`. +The default build includes revmc JIT support and requires Rust 1.95 plus LLVM 22. On Ubuntu or +Debian, install LLVM with the same helper used by CI and Docker: + +```bash +.github/scripts/install_llvm.sh ubuntu +``` + +On macOS, `brew install llvm` and put its `bin` directory on `PATH` while building. To build +without the LLVM toolchain dependency, disable default features: + +```bash +cargo build --release -p alethia-reth-bin --no-default-features +``` + ### 3. Run Checks and Tests To ensure everything is set up correctly, run the checks and tests: @@ -47,6 +61,31 @@ To see available command-line options and subcommands, run: _(Note: Replace `[OPTIONS]` with the necessary configuration flags for your setup. Refer to the `--help` output for details.)_ +### revmc JIT + +Start the node with upstream-compatible JIT flags: + +```bash +./target/release/alethia-reth node --jit [OPTIONS] +``` + +The main tuning flags are `--jit.hot-threshold`, `--jit.worker-count`, +`--jit.code-cache-bytes`, and `--jit.idle-evict-duration`. When the `reth` RPC namespace is +enabled, the upstream `reth_jit` method accepts `enable`, `disable`, `pause`, `unpause`, or +`clear` at runtime. + +Taiko's Unzen execution uses consensus-critical zk-gas metering that requires per-opcode +interpreter hooks. JIT dispatch therefore falls back to the interpreter for Unzen blocks and +for ordinary RPC call, trace, simulation, estimation, and pending-block execution. Canonical +Engine execution, payload building, and block replay opt in to the shared revmc backend on +pre-Unzen forks. Compiled code bakes in upstream mainnet gas and opcode semantics, so hardforks +are JIT-eligible only through an explicit allowlist: new forks stay interpreter-only until they +are deliberately marked JIT-safe in `TaikoEvmFactory`. + +revmc is pinned in `Cargo.toml` to a fast-forward of the revision reth v2.4.0 locks, adding the +upstream dynamic-gas failure-order fix (revmc#395) on top of the non-blocking runtime-control +fix (revmc#391). + ## Docker ### 1. Build the Docker Image diff --git a/bin/alethia-reth/Cargo.toml b/bin/alethia-reth/Cargo.toml index f5a43b9..55a8103 100644 --- a/bin/alethia-reth/Cargo.toml +++ b/bin/alethia-reth/Cargo.toml @@ -9,6 +9,10 @@ license.workspace = true name = "alethia-reth" path = "src/main.rs" +[features] +default = ["jit"] +jit = ["alethia-reth-node/jit"] + [dependencies] alethia-reth-cli = { path = "../../crates/cli" } alethia-reth-node = { path = "../../crates/node" } diff --git a/bin/alethia-reth/src/main.rs b/bin/alethia-reth/src/main.rs index a1f0b96..4458698 100644 --- a/bin/alethia-reth/src/main.rs +++ b/bin/alethia-reth/src/main.rs @@ -21,6 +21,20 @@ use tracing::info; static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); fn main() { + // When revmc re-executes this binary as its out-of-process compile helper, serve that role + // and exit before any node setup runs. + #[cfg(feature = "jit")] + { + match alethia_reth_node::maybe_run_jit_helper() { + Ok(std::ops::ControlFlow::Break(())) => return, + Ok(std::ops::ControlFlow::Continue(())) => {} + Err(err) => { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } + } + } + // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. if std::env::var_os("RUST_BACKTRACE").is_none() { unsafe { std::env::set_var("RUST_BACKTRACE", "1") }; diff --git a/crates/block/Cargo.toml b/crates/block/Cargo.toml index c540f46..51d0ac1 100644 --- a/crates/block/Cargo.toml +++ b/crates/block/Cargo.toml @@ -10,6 +10,7 @@ path = "src/lib.rs" [features] default = ["net"] +jit = ["alethia-reth-evm/jit"] prover = ["dep:alethia-reth-primitives", "dep:reth-storage-api", "dep:reth-trie-common"] test-utils = ["dep:reth-storage-api", "dep:reth-trie-common"] net = [ diff --git a/crates/block/src/config.rs b/crates/block/src/config.rs index a8e8315..dd96539 100644 --- a/crates/block/src/config.rs +++ b/crates/block/src/config.rs @@ -83,14 +83,12 @@ pub struct TaikoEvmConfig { pub executor_factory: TaikoBlockExecutorFactory, /// Block assembler used to construct finalized block objects. pub block_assembler: TaikoBlockAssembler, - /// EVM factory used to instantiate Taiko EVM instances. - pub evm_factory: TaikoEvmFactory, } impl TaikoEvmConfig { /// Creates a new Taiko EVM configuration with the given chain spec and extra context. pub fn new(chain_spec: Arc) -> Self { - Self::new_with_evm_factory(chain_spec, TaikoEvmFactory) + Self::new_with_evm_factory(chain_spec, TaikoEvmFactory::default()) } /// Creates a new Taiko EVM configuration with the given chain spec and EVM factory. @@ -105,7 +103,6 @@ impl TaikoEvmConfig { chain_spec, evm_factory, ), - evm_factory, } } @@ -113,6 +110,11 @@ impl TaikoEvmConfig { pub const fn chain_spec(&self) -> &Arc { self.executor_factory.spec() } + + /// Returns the EVM factory backing this configuration. + pub const fn evm_factory(&self) -> &TaikoEvmFactory { + self.executor_factory.evm_factory() + } } /// Returns the zero blob-gas environment used for Cancun-or-later RPC execution. @@ -159,6 +161,27 @@ impl ConfigureEvm for TaikoEvmConfig { &self.block_assembler } + /// Returns a config whose EVM factory selects (or deselects) the shared JIT backend for + /// subsequently created EVMs. + /// + /// This is the local-support gate of the JIT model: reth's engine tree and the Taiko + /// payload builder opt in explicitly, while RPC execution keeps the base config and stays + /// interpreter-only. Unzen (zk-gas) execution remains interpreter-only regardless via the + /// fork allowlist in [`TaikoEvmFactory`]. + #[cfg(feature = "jit")] + fn with_jit_support_enabled(self, enabled: bool) -> Self { + let evm_factory = + self.executor_factory.evm_factory().clone().with_jit_support_enabled(enabled); + Self::new_with_evm_factory(self.chain_spec().clone(), evm_factory) + } + + /// Returns runtime controls for the shared revmc backend, enabling the upstream `reth_jit` + /// RPC action against this configuration. + #[cfg(feature = "jit")] + fn jit_backend(&self) -> Option<&dyn reth_evm::JitBackend> { + Some(self.executor_factory.evm_factory()) + } + /// Creates a new [`EvmEnv`] for the given header. fn evm_env(&self, header: &Header) -> Result, Self::Error> { let spec = taiko_revm_spec(&self.chain_spec().inner, header); @@ -449,6 +472,123 @@ mod tests { use alloy_hardforks::ForkCondition; use std::sync::Arc; + #[cfg(feature = "jit")] + #[test] + fn jit_support_is_opt_in_per_config() { + let config = TaikoEvmConfig::new(TAIKO_DEVNET.clone()); + + // The base config — handed to RPC execution — never selects the shared backend. + assert!(!config.evm_factory().jit_support_enabled()); + + // The engine tree and the payload builder opt in through the `ConfigureEvm` hook. + let engine_config = config.clone().with_jit_support(); + assert!(engine_config.evm_factory().jit_support_enabled()); + assert!(Arc::ptr_eq(config.chain_spec(), engine_config.chain_spec())); + + // Opting out again restores the interpreter-only selection. + let rpc_config = engine_config.with_jit_support_enabled(false); + assert!(!rpc_config.evm_factory().jit_support_enabled()); + } + + #[cfg(feature = "jit")] + #[test] + fn base_config_is_interpreter_only_while_jit_enabled_config_uses_backend() { + use alethia_reth_evm::{ + factory::TaikoEvmFactory, + jit::{JitBackend, JitMode, RuntimeConfig}, + }; + use alloy_evm::Evm as _; + use reth_revm::{ + context::TxEnv, + db::InMemoryDB, + primitives::TxKind, + state::{AccountInfo, Bytecode}, + }; + use revm_database_interface::{ + BENCH_CALLER, BENCH_CALLER_BALANCE, BENCH_TARGET, BENCH_TARGET_BALANCE, + }; + + fn contract_db() -> InMemoryDB { + // PUSH1 1 PUSH1 2 ADD STOP — minimal arithmetic contract. + let bytecode = Bytecode::new_raw([0x60, 0x01, 0x60, 0x02, 0x01, 0x00].into()); + let mut db = InMemoryDB::default(); + db.insert_account_info( + BENCH_TARGET, + AccountInfo { + nonce: 1, + balance: BENCH_TARGET_BALANCE, + code_hash: bytecode.hash_slow(), + code: Some(bytecode), + ..Default::default() + }, + ); + db.insert_account_info( + BENCH_CALLER, + AccountInfo { nonce: 0, balance: BENCH_CALLER_BALANCE, ..Default::default() }, + ); + db + } + + fn call_tx() -> TxEnv { + TxEnv::builder() + .caller(BENCH_CALLER) + .kind(TxKind::Call(BENCH_TARGET)) + // Skip the chain-id check instead of mirroring the devnet chain id. + .chain_id(None) + .gas_limit(100_000) + .build() + .unwrap() + } + + // Pre-Unzen chain state so the factory's fork allowlist permits JIT dispatch. + let mut chain_spec = (*TAIKO_DEVNET).as_ref().clone(); + chain_spec.inner.hardforks.insert(TaikoHardfork::Shasta, ForkCondition::Timestamp(0)); + chain_spec.inner.hardforks.insert(TaikoHardfork::Unzen, ForkCondition::Timestamp(u64::MAX)); + + let backend = JitBackend::new(RuntimeConfig { + enabled: true, + blocking: true, + jit_mode: JitMode::InProcess, + ..RuntimeConfig::default() + }) + .expect("blocking JIT backend should start"); + let config = TaikoEvmConfig::new_with_evm_factory( + Arc::new(chain_spec), + TaikoEvmFactory::new(backend.clone()), + ); + let header = Header { + number: 1, + timestamp: 1, + gas_limit: 30_000_000, + base_fee_per_gas: Some(0), + ..Header::default() + }; + + // RPC call/estimate/simulate and pending-block execution receive the base config, which + // must never consult the shared backend. + let env = config.evm_env(&header).expect("pre-Unzen RPC env should build"); + let mut rpc_evm = config.evm_with_env(contract_db(), env); + rpc_evm.transact(call_tx()).expect("RPC-style execution should succeed"); + let rpc_stats = backend.stats(); + assert_eq!( + (rpc_stats.lookup_hits, rpc_stats.lookup_misses, rpc_stats.compilations_succeeded), + (0, 0, 0), + "base-config EVM construction must stay interpreter-only", + ); + + // Engine-tree canonical execution and payload building opt in via `with_jit_support`, + // so their executions dispatch to the shared backend. + let engine_config = config.with_jit_support(); + let env = engine_config.evm_env(&header).expect("pre-Unzen Engine env should build"); + let mut engine_evm = engine_config.evm_with_env(contract_db(), env); + engine_evm.transact(call_tx()).expect("Engine-style execution should succeed"); + let engine_stats = backend.stats(); + assert!( + engine_stats.compilations_succeeded >= 1 && engine_stats.lookup_hits >= 1, + "Engine execution should dispatch to the shared JIT backend: {engine_stats:?}", + ); + } + #[test] fn unzen_takes_precedence_over_shasta() { let mut chain_spec = (*TAIKO_DEVNET).as_ref().clone(); diff --git a/crates/block/src/executor.rs b/crates/block/src/executor.rs index 5753942..f49dedc 100644 --- a/crates/block/src/executor.rs +++ b/crates/block/src/executor.rs @@ -616,7 +616,7 @@ mod test { .with_database(db_with_contracts(&[(BENCH_CALLER, 0)])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); assert_eq!(evm.block_zk_gas_used(), Some(0)); let ctx = unzen_execution_ctx(); let mut executor = TaikoBlockExecutor::new( @@ -655,7 +655,7 @@ mod test { .with_database(db_with_contracts(&[(BENCH_CALLER, 0)])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let ctx = unzen_execution_ctx(); let mut executor = TaikoBlockExecutor::new(evm, ctx.clone(), chain_spec, RethReceiptBuilder::default()); @@ -679,7 +679,7 @@ mod test { .with_database(db_with_contracts(&[(BENCH_CALLER, 0)])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let ctx = unzen_execution_ctx(); let executor = TaikoBlockExecutor::new(evm, ctx.clone(), chain_spec, RethReceiptBuilder::default()); @@ -703,7 +703,7 @@ mod test { .with_database(db_with_contracts(&[(BENCH_CALLER, 0)])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let mut ctx = unzen_execution_ctx(); ctx.expected_difficulty = Some(U256::ZERO); let mut executor = @@ -767,7 +767,7 @@ mod test { }, ) .expect("next block env should build"); - let evm = config.evm_factory.create_evm(db, evm_env); + let evm = config.evm_factory().create_evm(db, evm_env); TaikoBlockExecutor::new( evm, diff --git a/crates/block/src/factory.rs b/crates/block/src/factory.rs index 6d98dc6..524379b 100644 --- a/crates/block/src/factory.rs +++ b/crates/block/src/factory.rs @@ -154,7 +154,7 @@ mod tests { fn test_taiko_block_executor_factory_creation() { let receipt_builder = RethReceiptBuilder::default(); let spec = Arc::new(TaikoChainSpec::default()); - let evm_factory = TaikoEvmFactory; + let evm_factory = TaikoEvmFactory::default(); TaikoBlockExecutorFactory::new(receipt_builder, spec.clone(), evm_factory); } diff --git a/crates/block/src/tx_selection/mod.rs b/crates/block/src/tx_selection/mod.rs index 51e1653..3c826d2 100644 --- a/crates/block/src/tx_selection/mod.rs +++ b/crates/block/src/tx_selection/mod.rs @@ -303,7 +303,7 @@ mod tests { let chain_spec = Arc::new(unzen_chain_spec()); let mut state = State::builder().with_database(db_with_contracts(&[])).with_bundle_update().build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let executor = TaikoBlockExecutor::new( evm, unzen_execution_ctx(), @@ -348,7 +348,7 @@ mod tests { ])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let executor = TaikoBlockExecutor::new( evm, unzen_execution_ctx(), diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 1a70c8d..b4a67d5 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -10,6 +10,7 @@ path = "src/lib.rs" [features] default = ["std"] +jit = ["std", "dep:revmc"] serde = ["dep:serde"] std = [ "alethia-reth-chainspec/std", @@ -26,5 +27,6 @@ alloy-primitives = { workspace = true } reth-evm = { workspace = true, default-features = false } reth-revm = { workspace = true, default-features = false } revm-database-interface = { workspace = true } +revmc = { workspace = true, optional = true } serde = { workspace = true, optional = true } tracing = { workspace = true } diff --git a/crates/evm/src/alloy.rs b/crates/evm/src/alloy.rs index 17e7f89..3f1b1c2 100644 --- a/crates/evm/src/alloy.rs +++ b/crates/evm/src/alloy.rs @@ -6,22 +6,23 @@ use alloy_primitives::{Address, Bytes, TxKind, U256}; // Re-export from primitives so downstream consumers can use the lighter crate. pub use alethia_reth_primitives::addresses::TAIKO_GOLDEN_TOUCH_ADDRESS; use reth_revm::{ - Context, ExecuteEvm, InspectEvm, Inspector, + Context, Inspector, context::{ - BlockEnv, CfgEnv, ContextTr, JournalTr, TxEnv, + BlockEnv, CfgEnv, ContextSetters, ContextTr, JournalTr, TxEnv, result::{ EVMError, ExecutionResult, HaltReason, Output, ResultAndState, ResultGas, SuccessReason, }, }, - handler::PrecompileProvider, - interpreter::InterpreterResult, + handler::{EthFrame, Handler, PrecompileProvider}, + inspector::InspectorHandler, + interpreter::{InterpreterResult, interpreter::EthInterpreter}, state::EvmState, }; use tracing::debug; use crate::{ evm::TaikoEvm, - handler::get_treasury_address, + handler::{TaikoEvmHandler, get_treasury_address}, spec::TaikoSpecId, zk_gas::{ adapter::ZkGasInspector, @@ -32,36 +33,89 @@ use crate::{ /// Maximum transaction gas limit enforced once Osaka/Unzen semantics are active. const MAX_SYSTEM_CALL_GAS_LIMIT: u64 = 16_777_216; +/// Base Taiko EVM implementation before optional JIT dispatch is applied. +type BaseTaikoEvm = TaikoEvm, ZkGasInspector, P>; + +/// Taiko EVM implementation used by the Alloy adapter when JIT support is compiled in. +#[cfg(feature = "jit")] +type InnerTaikoEvm = revmc::revm_evm::JitEvm>; + +/// Taiko EVM implementation used by the Alloy adapter without JIT support. +#[cfg(not(feature = "jit"))] +type InnerTaikoEvm = BaseTaikoEvm; + /// A wrapper around the Taiko EVM that implements the `Evm` trait in `alloy_evm`. pub struct TaikoEvmWrapper { /// Wrapped Taiko EVM instance implementing execution behavior. - inner: TaikoEvm, ZkGasInspector, P>, + /// + /// WARNING (jit builds): revmc's `JitEvm` publicly implements `ExecuteEvm`/`InspectEvm` + /// backed by revm's `MainnetHandler`. Never call those entry points on this field — always + /// drive it with [`TaikoEvmHandler`] (see `transact_raw`), otherwise Taiko's anchor and + /// fee-share semantics are silently dropped. + inner: InnerTaikoEvm, /// Whether to run transactions through the inspector execution path. inspect: bool, } impl TaikoEvmWrapper { - /// Creates a new [`TaikoEvmWrapper`] instance. - pub const fn new( - evm: TaikoEvm, ZkGasInspector, P>, - inspect: bool, - ) -> Self { + /// Creates an interpreter-backed [`TaikoEvmWrapper`] instance. + #[cfg(feature = "jit")] + pub fn new(evm: BaseTaikoEvm, inspect: bool) -> Self + where + P: PrecompileProvider, Output = InterpreterResult>, + { + Self { inner: revmc::revm_evm::JitEvm::disabled(evm), inspect } + } + + /// Creates an interpreter-backed [`TaikoEvmWrapper`] instance. + #[cfg(not(feature = "jit"))] + pub const fn new(evm: BaseTaikoEvm, inspect: bool) -> Self { + Self { inner: evm, inspect } + } + + /// Creates a wrapper around an already configured optional JIT dispatcher. + pub(crate) fn new_with_inner(evm: InnerTaikoEvm, inspect: bool) -> Self { Self { inner: evm, inspect } } /// Consumes self and return the inner EVM instance. - pub fn into_inner(self) -> TaikoEvm, ZkGasInspector, P> { + pub fn into_inner(self) -> BaseTaikoEvm { + #[cfg(feature = "jit")] + { + self.inner.into_inner() + } + #[cfg(not(feature = "jit"))] self.inner } + /// Returns the base Taiko EVM wrapped by the optional JIT dispatcher. + fn base_evm(&self) -> &BaseTaikoEvm { + #[cfg(feature = "jit")] + { + self.inner.inner() + } + #[cfg(not(feature = "jit"))] + &self.inner + } + + /// Returns the mutable base Taiko EVM wrapped by the optional JIT dispatcher. + fn base_evm_mut(&mut self) -> &mut BaseTaikoEvm { + #[cfg(feature = "jit")] + { + self.inner.inner_mut() + } + #[cfg(not(feature = "jit"))] + &mut self.inner + } + /// Provides a reference to the EVM context. - pub const fn ctx(&self) -> &TaikoEvmContext { - &self.inner.inner.ctx + pub fn ctx(&self) -> &TaikoEvmContext { + &self.base_evm().inner.ctx } /// Provides a mutable reference to the EVM context. pub fn ctx_mut(&mut self) -> &mut TaikoEvmContext { - &mut self.inner.inner.ctx + &mut self.base_evm_mut().inner.ctx } /// Returns a reference to the active zk gas meter, if metering is enabled. @@ -69,7 +123,8 @@ impl TaikoEvmWrapper { /// Returns `None` when the active spec/chain combination has no zk gas schedule /// (pre-Unzen specs). pub fn meter(&self) -> Option<&ZkGasMeter<'static>> { - self.inner.zk_gas_meter().or_else(|| self.inner.inner.inspector.meter()) + let evm = self.base_evm(); + evm.zk_gas_meter().or_else(|| evm.inner.inspector.meter()) } /// Returns a mutable reference to the active zk gas meter, if metering is enabled. @@ -77,10 +132,10 @@ impl TaikoEvmWrapper { /// Returns `None` when the active spec/chain combination has no zk gas schedule /// (pre-Unzen specs). pub fn meter_mut(&mut self) -> Option<&mut ZkGasMeter<'static>> { - if self.inner.zk_gas_meter().is_some() { - self.inner.zk_gas_meter_mut() + if self.base_evm().zk_gas_meter().is_some() { + self.base_evm_mut().zk_gas_meter_mut() } else { - self.inner.inner.inspector.meter_mut() + self.base_evm_mut().inner.inspector.meter_mut() } } } @@ -217,19 +272,21 @@ where /// Provides immutable references to the database, inspector and precompiles. fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { + let evm = self.base_evm(); ( - &self.inner.inner.ctx.journaled_state.database, - self.inner.inner.inspector.inner(), - &self.inner.inner.precompiles, + &evm.inner.ctx.journaled_state.database, + evm.inner.inspector.inner(), + &evm.inner.precompiles, ) } /// Provides mutable references to the database, inspector and precompiles. fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { + let evm = self.base_evm_mut(); ( - &mut self.inner.inner.ctx.journaled_state.database, - self.inner.inner.inspector.inner_mut(), - &mut self.inner.inner.precompiles, + &mut evm.inner.ctx.journaled_state.database, + evm.inner.inspector.inner_mut(), + &mut evm.inner.precompiles, ) } @@ -238,7 +295,25 @@ where &mut self, tx: Self::Tx, ) -> Result, Self::Error> { - if self.inspect { self.inner.inspect_tx(tx) } else { self.inner.transact(tx) } + self.ctx_mut().set_tx(tx); + // Run [`TaikoEvmHandler`] against the (possibly JIT-dispatching) inner EVM directly: + // revmc's own `ExecuteEvm`/`InspectEvm` entry points would run revm's `MainnetHandler` + // and silently drop Taiko's anchor and fee-share semantics. + // + // Those entry points also re-validate revmc's per-instance lookup cache against the + // active spec (`JitEvm::invalidate_cache`, private upstream). Skipping that here is + // sound only because reth constructs a fresh EVM per block environment, so the spec + // never changes within this instance's lifetime. + let mut handler = TaikoEvmHandler::<_, EVMError, EthFrame>::new( + self.base_evm().extra_execution_ctx.clone(), + ); + let result = if self.inspect { + handler.inspect_run(&mut self.inner) + } else { + handler.run(&mut self.inner) + }?; + let state = self.ctx_mut().journal_mut().finalize(); + Ok(ResultAndState::new(result, state)) } /// Executes a system call. @@ -266,7 +341,11 @@ where debug!(target: "taiko_evm", "Anchor system call detected: base_fee_share_pctg = {}, caller_nonce = {}", base_fee_share_pctg, caller_nonce); // Set the Anchor transaction information for the later EVM execution. - self.inner.with_extra_execution_context(base_fee_share_pctg, caller, caller_nonce); + self.base_evm_mut().with_extra_execution_context( + base_fee_share_pctg, + caller, + caller_nonce, + ); // Load both system-call participants through the journal so witness generation can // include the same pre-execution dependencies that stateless validation will read @@ -363,7 +442,8 @@ where where Self: Sized, { - let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.inner.ctx; + let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = + self.into_inner().inner.ctx; (journaled_state.database, EvmEnv { block_env, cfg_env }) } @@ -375,7 +455,7 @@ where // `create_evm` installs zk-gas on the production TaikoEvm wrapper and keeps the inner // inspector unmetered. Enabling the NoOp inspector would route around that production // meter, so only EVMs built through `create_evm_with_inspector` may switch to inspect mode. - if enabled && self.inner.zk_gas_meter().is_some() { + if enabled && self.base_evm().zk_gas_meter().is_some() { return; } self.inspect = enabled; @@ -383,22 +463,22 @@ where /// Getter of precompiles. fn precompiles(&self) -> &Self::Precompiles { - &self.inner.inner.precompiles + &self.base_evm().inner.precompiles } /// Mutable getter of precompiles. fn precompiles_mut(&mut self) -> &mut Self::Precompiles { - &mut self.inner.inner.precompiles + &mut self.base_evm_mut().inner.precompiles } /// Getter of inspector. fn inspector(&self) -> &Self::Inspector { - self.inner.inner.inspector.inner() + self.base_evm().inner.inspector.inner() } /// Mutable getter of inspector. fn inspector_mut(&mut self) -> &mut Self::Inspector { - self.inner.inner.inspector.inner_mut() + self.base_evm_mut().inner.inspector.inner_mut() } } @@ -447,7 +527,7 @@ mod tests { let mut env: EvmEnv = EvmEnv::default(); env.cfg_env.chain_id = chain_id; - let mut evm = TaikoEvmFactory.create_evm(db, env); + let mut evm = TaikoEvmFactory::default().create_evm(db, env); evm.transact_system_call(golden_touch, treasury, encode_anchor_system_call_data(25, 7)) .expect("anchor system call should short-circuit successfully"); diff --git a/crates/evm/src/execution.rs b/crates/evm/src/execution.rs index 709514c..7eff12c 100644 --- a/crates/evm/src/execution.rs +++ b/crates/evm/src/execution.rs @@ -8,7 +8,7 @@ use reth_revm::{ }, }, handler::{EthFrame, Handler, PrecompileProvider}, - inspector::{InspectorHandler, JournalExt}, + inspector::{InspectorEvmTr, InspectorHandler, JournalExt}, interpreter::{InterpreterResult, interpreter::EthInterpreter}, state::EvmState, }; @@ -16,6 +16,46 @@ use revm_database_interface::Database; use crate::{evm::TaikoEvm, handler::TaikoEvmHandler}; +/// Inspected-execution plumbing required so [`TaikoEvm`] can sit inside revmc's `JitEvm` +/// dispatcher, whose `InspectorEvmTr` implementation delegates to the wrapped EVM. Inspected +/// frames themselves run revm's default inspected interpreter loop (with `ZkGasInspector` +/// hooks); only the accessors are delegated here. +impl InspectorEvmTr for TaikoEvm +where + CTX: ContextSetters + JournalExt>, + INSP: Inspector, + P: PrecompileProvider, +{ + /// Inspector installed on the wrapped revm engine. + type Inspector = INSP; + + /// Returns shared references to every component required by inspected execution. + fn all_inspector( + &self, + ) -> ( + &Self::Context, + &Self::Instructions, + &Self::Precompiles, + &reth_revm::context::FrameStack, + &Self::Inspector, + ) { + self.inner.all_inspector() + } + + /// Returns mutable references to every component required by inspected execution. + fn all_mut_inspector( + &mut self, + ) -> ( + &mut Self::Context, + &mut Self::Instructions, + &mut Self::Precompiles, + &mut reth_revm::context::FrameStack, + &mut Self::Inspector, + ) { + self.inner.all_mut_inspector() + } +} + // Trait that allows to replay and transact the transaction, we // use [`TaikoEvmHandler`] to handle the transactions execution, besides // that, we use the same implementation as `RevmEvm`. diff --git a/crates/evm/src/factory.rs b/crates/evm/src/factory.rs index 903ec35..1f2d8af 100644 --- a/crates/evm/src/factory.rs +++ b/crates/evm/src/factory.rs @@ -18,9 +18,126 @@ use crate::{ zk_gas::{adapter::ZkGasInspector, schedule::schedule_for}, }; +#[cfg(feature = "jit")] +use crate::jit::JitBackend; + /// A factory type for creating instances of the Taiko EVM given a certain input. -#[derive(Default, Debug, Clone, Copy)] -pub struct TaikoEvmFactory; +/// +/// With the `jit` Cargo feature, the factory owns the shared revmc backend. An EVM can execute +/// JIT-compiled code only when all gates pass: the binary was built with the `jit` feature, +/// runtime compilation was enabled with `--jit` (or the `reth_jit` RPC method), the local config +/// selected JIT support via [`reth_evm::ConfigureEvm::with_jit_support`], and the active fork is +/// on the [`spec_supports_jit`] allowlist. +#[derive(Debug, Clone)] +pub struct TaikoEvmFactory { + /// Shared runtime backend used for JIT-eligible execution. + #[cfg(feature = "jit")] + backend: JitBackend, + /// Disabled backend used for zk-gas forks, inspected execution, and unsupported paths. + #[cfg(feature = "jit")] + disabled_backend: JitBackend, + /// Whether locally created EVMs may dispatch to the shared JIT backend. + #[cfg(feature = "jit")] + jit_support: bool, +} + +#[cfg(feature = "jit")] +impl TaikoEvmFactory { + /// Creates a factory backed by the supplied revmc runtime. + pub fn new(backend: JitBackend) -> Self { + Self { backend, disabled_backend: JitBackend::disabled(), jit_support: false } + } + + /// Enables or disables JIT dispatch for EVMs subsequently created by this factory. + pub const fn with_jit_support_enabled(mut self, enabled: bool) -> Self { + self.jit_support = enabled; + self + } + + /// Enables JIT dispatch for EVMs subsequently created by this factory. + pub const fn with_jit_support(self) -> Self { + self.with_jit_support_enabled(true) + } + + /// Returns whether this factory selects the shared backend for eligible execution. + pub const fn jit_support_enabled(&self) -> bool { + self.jit_support + } + + /// Returns the shared revmc backend handle. + pub const fn backend(&self) -> &JitBackend { + &self.backend + } + + /// Selects the backend for an uninspected execution at the given Taiko fork. + fn backend_for_spec(&self, spec_id: TaikoSpecId) -> JitBackend { + // `schedule_for` is re-checked as a belt-and-suspenders guard: a spec with a zk-gas + // schedule must never reach compiled code even if the allowlist says otherwise. + if self.jit_support && spec_supports_jit(spec_id) && schedule_for(spec_id).is_none() { + self.backend.clone() + } else { + self.disabled_backend.clone() + } + } +} + +impl Default for TaikoEvmFactory { + /// Creates an interpreter-only factory. + fn default() -> Self { + #[cfg(feature = "jit")] + { + Self::new(JitBackend::disabled()) + } + #[cfg(not(feature = "jit"))] + Self {} + } +} + +/// Returns whether execution under `spec_id` may dispatch to revmc-compiled code. +/// +/// Compiled programs bake in upstream mainnet gas and opcode semantics, so only forks that +/// execute with exactly those semantics are eligible. Unzen is excluded because its zk-gas +/// metering needs per-opcode interpreter hooks. +/// +/// This match is deliberately exhaustive: adding a fork variant fails compilation here so that +/// every new fork is classified explicitly. Any fork that reprices gas or changes opcode +/// behavior in any way must stay interpreter-only — compiled code would silently diverge from +/// consensus otherwise. +#[cfg(feature = "jit")] +const fn spec_supports_jit(spec_id: TaikoSpecId) -> bool { + match spec_id { + TaikoSpecId::GENESIS | TaikoSpecId::ONTAKE | TaikoSpecId::PACAYA | TaikoSpecId::SHASTA => { + true + } + TaikoSpecId::UNZEN => false, + } +} + +/// Runtime controls for the shared revmc backend, exposed through +/// [`reth_evm::ConfigureEvm::jit_backend`] so the upstream `reth_jit` RPC action works +/// against the Taiko EVM configuration. +#[cfg(feature = "jit")] +impl reth_evm::JitBackend for TaikoEvmFactory { + /// Enables or disables JIT lookups and background compilation. + fn set_enabled(&self, enabled: bool) -> Result<(), String> { + self.backend.set_enabled(enabled).map_err(|err| err.to_string()) + } + + /// Pauses out-of-process helper execution without discarding compiled code. + fn pause(&self) { + self.backend.pause(); + } + + /// Resumes background JIT promotion. + fn resume(&self) { + self.backend.resume(); + } + + /// Clears resident and persisted compiled artifacts. + fn clear(&self) { + self.backend.clear_all(); + } +} impl EvmFactory for TaikoEvmFactory { /// The EVM type that this factory creates. @@ -58,10 +175,18 @@ impl EvmFactory for TaikoEvmFactory { PrecompileSpecId::from_spec_id(spec_id.into()), ))); - TaikoEvmWrapper::new(TaikoEvm::new(evm).with_zk_gas_schedule(schedule), false) + let evm = TaikoEvm::new(evm).with_zk_gas_schedule(schedule); + #[cfg(feature = "jit")] + let evm = revmc::revm_evm::JitEvm::new(evm, self.backend_for_spec(spec_id)); + + TaikoEvmWrapper::new_with_inner(evm, false) } /// Creates a new instance of an EVM with an inspector. + /// + /// Inspected execution always selects the disabled backend: compiled code cannot deliver + /// per-step inspector callbacks (only log forwarding), which tracers and the zk-gas + /// inspector depend on. fn create_evm_with_inspector>>( &self, db: DB, @@ -79,6 +204,24 @@ impl EvmFactory for TaikoEvmFactory { PrecompileSpecId::from_spec_id(spec_id.into()), ))); - TaikoEvmWrapper::new(TaikoEvm::new(evm), true) + let evm = TaikoEvm::new(evm); + #[cfg(feature = "jit")] + let evm = revmc::revm_evm::JitEvm::new(evm, self.disabled_backend.clone()); + + TaikoEvmWrapper::new_with_inner(evm, true) + } +} + +#[cfg(all(test, feature = "jit"))] +mod tests { + use super::*; + + #[test] + fn jit_dispatch_is_allowlisted_to_non_zk_gas_specs() { + assert!(spec_supports_jit(TaikoSpecId::GENESIS)); + assert!(spec_supports_jit(TaikoSpecId::ONTAKE)); + assert!(spec_supports_jit(TaikoSpecId::PACAYA)); + assert!(spec_supports_jit(TaikoSpecId::SHASTA)); + assert!(!spec_supports_jit(TaikoSpecId::UNZEN)); } } diff --git a/crates/evm/src/jit.rs b/crates/evm/src/jit.rs new file mode 100644 index 0000000..c998327 --- /dev/null +++ b/crates/evm/src/jit.rs @@ -0,0 +1,129 @@ +//! Configuration and runtime integration for revmc JIT execution. + +#[cfg(feature = "jit")] +use std::path::PathBuf; +use std::time::Duration; + +#[cfg(feature = "jit")] +use revmc::runtime::RuntimeTuning; +#[cfg(feature = "jit")] +pub use revmc::runtime::{JitBackend, JitMode, RuntimeConfig, maybe_run_jit_helper}; + +/// Runtime settings for revmc JIT compilation. +/// +/// Building with the `jit` Cargo feature makes the backend available, while [`Self::enabled`] +/// controls whether compilation starts when the node launches. Field defaults mirror reth's +/// upstream `JitArgs`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct JitConfig { + /// Enables JIT compilation when the node starts. + pub enabled: bool, + /// Number of observed misses before bytecode is promoted for compilation. + pub hot_threshold: usize, + /// Number of background compilation workers, or the revmc default when unset. + pub worker_count: Option, + /// Capacity of the non-blocking lookup-observation channel. + pub channel_capacity: usize, + /// Maximum number of compilation jobs that may be pending concurrently. + pub max_pending_jobs: usize, + /// Maximum eligible bytecode length in bytes, where zero means unlimited. + pub max_bytecode_len: usize, + /// Maximum resident compiled-code size in bytes, where zero means unlimited. + pub code_cache_bytes: usize, + /// Idle duration after which an unused compiled program may be evicted. + pub idle_evict_duration: Duration, + /// Enables compiler IR, assembly, and bytecode dumps under the node data directory. + pub debug: bool, + /// Compiles synchronously on the first miss; intended only for tests and debugging. + pub blocking: bool, +} + +impl JitConfig { + /// Default number of misses required before compiling bytecode. + pub const DEFAULT_HOT_THRESHOLD: usize = 8; + /// Default capacity of the lookup-observation channel. + pub const DEFAULT_CHANNEL_CAPACITY: usize = 4096; + /// Default maximum number of pending compilation jobs. + pub const DEFAULT_MAX_PENDING_JOBS: usize = 2048; + /// Default maximum bytecode length, where zero disables the limit. + pub const DEFAULT_MAX_BYTECODE_LEN: usize = 0; + /// Default resident compiled-code budget of one gibibyte. + pub const DEFAULT_CODE_CACHE_BYTES: usize = 1024 * 1024 * 1024; + /// Default idle eviction duration of one hour. + pub const DEFAULT_IDLE_EVICT_DURATION: Duration = Duration::from_secs(60 * 60); + + /// Creates the shared revmc backend represented by this configuration. + /// + /// Compilation runs in an out-of-process helper (see [`maybe_run_jit_helper`]) so compiler + /// resource use stays isolated from the node process. + #[cfg(feature = "jit")] + pub fn build_backend(&self, dump_dir: Option) -> revmc::eyre::Result { + // revmc sizes a `crossbeam` `ArrayQueue` from this value and panics on zero, even when + // compilation is disabled, so reject it with an error instead. + if self.channel_capacity == 0 { + revmc::eyre::bail!("JIT channel capacity must be at least 1"); + } + + let defaults = RuntimeTuning::default(); + let tuning = RuntimeTuning { + channel_capacity: self.channel_capacity, + jit_hot_threshold: self.hot_threshold, + jit_worker_count: self.worker_count.unwrap_or(defaults.jit_worker_count), + jit_max_pending_jobs: self.max_pending_jobs, + jit_max_bytecode_len: self.max_bytecode_len, + resident_code_cache_bytes: self.code_cache_bytes, + idle_evict_duration: Some(self.idle_evict_duration), + ..defaults + }; + + JitBackend::new(RuntimeConfig { + enabled: self.enabled, + tuning, + dump_dir, + debug_assertions: self.debug, + blocking: self.blocking, + jit_mode: JitMode::OutOfProcess, + ..RuntimeConfig::default() + }) + } +} + +impl Default for JitConfig { + /// Returns the safe runtime defaults with compilation disabled. + fn default() -> Self { + Self { + enabled: false, + hot_threshold: Self::DEFAULT_HOT_THRESHOLD, + worker_count: None, + channel_capacity: Self::DEFAULT_CHANNEL_CAPACITY, + max_pending_jobs: Self::DEFAULT_MAX_PENDING_JOBS, + max_bytecode_len: Self::DEFAULT_MAX_BYTECODE_LEN, + code_cache_bytes: Self::DEFAULT_CODE_CACHE_BYTES, + idle_evict_duration: Self::DEFAULT_IDLE_EVICT_DURATION, + debug: false, + blocking: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn defaults_keep_jit_disabled() { + let config = JitConfig::default(); + + assert!(!config.enabled); + assert_eq!(config.hot_threshold, JitConfig::DEFAULT_HOT_THRESHOLD); + assert_eq!(config.code_cache_bytes, JitConfig::DEFAULT_CODE_CACHE_BYTES); + } + + #[cfg(feature = "jit")] + #[test] + fn build_backend_rejects_zero_jit_channel_capacity() { + let config = JitConfig { channel_capacity: 0, ..JitConfig::default() }; + + assert!(config.build_backend(None).is_err()); + } +} diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 989f5c1..621943c 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -12,6 +12,8 @@ pub mod execution; pub mod factory; /// Taiko-specific handler behavior for fee sharing and anchor processing. pub mod handler; +/// Configuration and runtime integration for revmc JIT execution. +pub mod jit; /// Taiko hardfork spec identifiers and conversions. pub mod spec; /// Fork-scoped zk gas schedules for Taiko consensus. diff --git a/crates/evm/src/zk_gas/tests.rs b/crates/evm/src/zk_gas/tests.rs index 4191209..f2a9b30 100644 --- a/crates/evm/src/zk_gas/tests.rs +++ b/crates/evm/src/zk_gas/tests.rs @@ -230,7 +230,7 @@ impl Inspector for StepGasProbeInspector { #[test] fn unzen_adapter_uses_spawn_estimate_for_precompile_dispatch() { let schedule = schedule_for(TaikoSpecId::UNZEN).expect("Unzen schedule"); - let mut evm = TaikoEvmFactory.create_evm_with_inspector( + let mut evm = TaikoEvmFactory::default().create_evm_with_inspector( db_with_contract(staticcall_identity_bytecode()), evm_env(TaikoSpecId::UNZEN), StepGasProbeInspector::default(), @@ -268,13 +268,13 @@ fn unzen_adapter_uses_spawn_estimate_for_precompile_dispatch() { #[test] fn production_metered_path_matches_inspector_path_for_precompile_dispatch() { - let mut production_evm = TaikoEvmFactory + let mut production_evm = TaikoEvmFactory::default() .create_evm(db_with_contract(staticcall_identity_bytecode()), evm_env(TaikoSpecId::UNZEN)); production_evm.transact(tx_env(100_000)).expect("production path should execute"); let production_zk_gas = production_evm.meter().expect("production path should install a meter").tx_zk_gas_used(); - let mut inspector_evm = TaikoEvmFactory.create_evm_with_inspector( + let mut inspector_evm = TaikoEvmFactory::default().create_evm_with_inspector( db_with_contract(staticcall_identity_bytecode()), evm_env(TaikoSpecId::UNZEN), NoOpInspector {}, @@ -288,13 +288,13 @@ fn production_metered_path_matches_inspector_path_for_precompile_dispatch() { #[test] fn production_metered_path_matches_inspector_path_for_ordinary_opcodes() { - let mut production_evm = TaikoEvmFactory + let mut production_evm = TaikoEvmFactory::default() .create_evm(db_with_contract(simple_arithmetic_bytecode()), evm_env(TaikoSpecId::UNZEN)); production_evm.transact(tx_env(100_000)).expect("production path should execute"); let production_zk_gas = production_evm.meter().expect("production path should install a meter").tx_zk_gas_used(); - let mut inspector_evm = TaikoEvmFactory.create_evm_with_inspector( + let mut inspector_evm = TaikoEvmFactory::default().create_evm_with_inspector( db_with_contract(simple_arithmetic_bytecode()), evm_env(TaikoSpecId::UNZEN), NoOpInspector {}, @@ -308,7 +308,7 @@ fn production_metered_path_matches_inspector_path_for_ordinary_opcodes() { #[test] fn unzen_adapter_raises_dedicated_error_when_limit_is_exceeded() { - let mut evm = TaikoEvmFactory.create_evm( + let mut evm = TaikoEvmFactory::default().create_evm( db_with_contract(limit_exceeding_keccak_bytecode()), evm_env(TaikoSpecId::UNZEN), ); @@ -325,7 +325,7 @@ fn unzen_adapter_raises_dedicated_error_when_limit_is_exceeded() { #[test] fn unzen_default_create_evm_path_is_metered() { - let mut evm = TaikoEvmFactory.create_evm( + let mut evm = TaikoEvmFactory::default().create_evm( db_with_contract(limit_exceeding_keccak_bytecode()), evm_env(TaikoSpecId::UNZEN), ); @@ -336,7 +336,7 @@ fn unzen_default_create_evm_path_is_metered() { #[test] fn production_metered_path_stays_metered_when_noop_inspector_is_enabled() { - let mut evm = TaikoEvmFactory.create_evm( + let mut evm = TaikoEvmFactory::default().create_evm( db_with_contract(limit_exceeding_keccak_bytecode()), evm_env(TaikoSpecId::UNZEN), ); @@ -354,7 +354,7 @@ fn production_metered_path_stays_metered_when_noop_inspector_is_enabled() { #[test] fn factory_installs_unzen_schedule() { let env = evm_env(TaikoSpecId::UNZEN); - let evm = TaikoEvmFactory.create_evm(db_with_contract(limit_exceeding_keccak_bytecode()), env); + let evm = TaikoEvmFactory::default().create_evm(db_with_contract(limit_exceeding_keccak_bytecode()), env); let meter = evm.meter().expect("Unzen schedule should install a meter"); assert!(std::ptr::eq(meter.schedule(), &UNZEN_ZK_GAS_SCHEDULE)); @@ -365,7 +365,7 @@ fn factory_installs_unzen_schedule() { fn taiko_zk_gas_evm_charge_tx_intrinsic_adds_intrinsic_to_in_flight_tx() { use crate::alloy::TaikoZkGasEvm; - let mut evm = TaikoEvmFactory + let mut evm = TaikoEvmFactory::default() .create_evm(db_with_contract(staticcall_identity_bytecode()), evm_env(TaikoSpecId::UNZEN)); evm.charge_tx_intrinsic_zk_gas().expect("intrinsic should fit"); @@ -377,7 +377,7 @@ fn taiko_zk_gas_evm_charge_tx_intrinsic_adds_intrinsic_to_in_flight_tx() { fn taiko_zk_gas_evm_charge_tx_intrinsic_is_ok_when_metering_is_disabled() { use crate::alloy::TaikoZkGasEvm; - let mut evm = TaikoEvmFactory + let mut evm = TaikoEvmFactory::default() .create_evm(db_with_contract(staticcall_identity_bytecode()), evm_env(TaikoSpecId::SHASTA)); assert!(evm.meter().is_none()); @@ -386,13 +386,117 @@ fn taiko_zk_gas_evm_charge_tx_intrinsic_is_ok_when_metering_is_disabled() { #[test] fn non_unzen_default_create_evm_path_keeps_metering_disabled() { - let mut evm = TaikoEvmFactory + let mut evm = TaikoEvmFactory::default() .create_evm(db_with_contract(simple_arithmetic_bytecode()), evm_env(TaikoSpecId::SHASTA)); assert!(evm.meter().is_none()); evm.transact(tx_env(5_000_000)).expect("non-Unzen tx should stay on the legacy path"); } +#[cfg(feature = "jit")] +#[test] +fn jit_requires_local_support_and_falls_back_for_unzen() { + use revmc::runtime::{JitBackend, JitMode, RuntimeConfig}; + + let backend = JitBackend::new(RuntimeConfig { + enabled: true, + blocking: true, + single_error: false, + jit_mode: JitMode::InProcess, + ..RuntimeConfig::default() + }) + .expect("blocking JIT backend should start"); + let factory = TaikoEvmFactory::new(backend.clone()); + + let mut unsupported_evm = factory + .create_evm(db_with_contract(simple_arithmetic_bytecode()), evm_env(TaikoSpecId::SHASTA)); + unsupported_evm + .transact(tx_env(100_000)) + .expect("locally disabled JIT execution should use the interpreter"); + assert_eq!(backend.stats().compilations_succeeded, 0); + + let factory = factory.with_jit_support(); + + let mut pre_unzen_evm = factory + .create_evm(db_with_contract(simple_arithmetic_bytecode()), evm_env(TaikoSpecId::SHASTA)); + pre_unzen_evm.transact(tx_env(100_000)).expect("pre-Unzen JIT execution should succeed"); + + let compiled = backend.stats(); + assert_eq!(compiled.compilations_succeeded, 1); + assert_eq!(compiled.lookup_hits, 1); + + let mut unzen_evm = factory + .create_evm(db_with_contract(simple_arithmetic_bytecode()), evm_env(TaikoSpecId::UNZEN)); + unzen_evm.transact(tx_env(100_000)).expect("Unzen interpreter execution should succeed"); + + let after_unzen = backend.stats(); + assert_eq!(after_unzen.compilations_succeeded, compiled.compilations_succeeded); + assert_eq!(after_unzen.lookup_hits, compiled.lookup_hits); + assert!(unzen_evm.meter().is_some()); +} + +/// Executes `bytecode` through a blocking JIT-enabled factory and an interpreter-only factory, +/// returning both outcomes and the JIT backend's stats. +#[cfg(feature = "jit")] +fn jit_and_interpreter_outputs( + bytecode: Bytecode, +) -> ( + reth_revm::context::result::ResultAndState, + reth_revm::context::result::ResultAndState, + revmc::runtime::RuntimeStatsSnapshot, +) { + use revmc::runtime::{JitBackend, JitMode, RuntimeConfig}; + + let backend = JitBackend::new(RuntimeConfig { + enabled: true, + blocking: true, + single_error: false, + jit_mode: JitMode::InProcess, + ..RuntimeConfig::default() + }) + .expect("blocking JIT backend should start"); + + let mut jit_evm = TaikoEvmFactory::new(backend.clone()) + .with_jit_support() + .create_evm(db_with_contract(bytecode.clone()), evm_env(TaikoSpecId::SHASTA)); + let jit_output = jit_evm.transact(tx_env(100_000)).expect("JIT execution should succeed"); + + let mut interpreter_evm = TaikoEvmFactory::default() + .create_evm(db_with_contract(bytecode), evm_env(TaikoSpecId::SHASTA)); + let interpreter_output = + interpreter_evm.transact(tx_env(100_000)).expect("interpreter execution should succeed"); + + (jit_output, interpreter_output, backend.stats()) +} + +#[cfg(feature = "jit")] +#[test] +fn jit_execution_matches_interpreter_execution() { + for (name, bytecode) in [ + ("arithmetic", simple_arithmetic_bytecode()), + ("staticcall", staticcall_identity_bytecode()), + ] { + let (jit_output, interpreter_output, stats) = jit_and_interpreter_outputs(bytecode); + + assert_eq!(jit_output, interpreter_output, "JIT and interpreter diverged for {name}"); + assert!(stats.lookup_hits >= 1, "expected the JIT path to serve compiled code for {name}"); + } +} + +/// Ensures JIT execution preserves the interpreter's dynamic-gas failure ordering. +#[cfg(feature = "jit")] +#[test] +fn jit_matches_interpreter_on_dynamic_gas_failure_order() { + let bytecode = Bytecode::new_raw([0x60, 0x01, 0x60, 0xea, 0x55, 0x01].into()); + + let (jit_output, interpreter_output, _) = jit_and_interpreter_outputs(bytecode); + + assert_eq!( + jit_output, interpreter_output, + "JIT and interpreter must agree on halt reason, gas, and journaled state", + ); +} + fn evm_env(spec: TaikoSpecId) -> EvmEnv { let mut env: EvmEnv = EvmEnv::default(); env.cfg_env.spec = spec; diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 0adebeb..7a3f6f9 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -12,6 +12,7 @@ path = "src/lib.rs" default = ["serde"] arbitrary = [] client = [] +jit = ["alethia-reth-block/jit", "alethia-reth-evm/jit"] prover = [] serde = ["dep:serde", "alethia-reth-chainspec/serde", "alethia-reth-primitives/serde"] @@ -39,6 +40,7 @@ reth-ethereum-primitives = { workspace = true } reth-execution-types = { workspace = true } reth-node-api = { workspace = true } reth-node-builder = { workspace = true } +reth-node-core = { workspace = true } reth-node-ethereum = { workspace = true } reth-optimism-trie = { workspace = true, features = ["metrics"] } reth-primitives-traits = { workspace = true } diff --git a/crates/node/src/components.rs b/crates/node/src/components.rs index 93330fc..ff9fa4e 100644 --- a/crates/node/src/components.rs +++ b/crates/node/src/components.rs @@ -24,6 +24,44 @@ use tracing::info; #[derive(Debug, Clone, Default)] pub struct TaikoExecutorBuilder; +impl TaikoExecutorBuilder { + /// Builds an EVM configuration whose factory owns the shared revmc backend created from + /// the node's `--jit` CLI settings. + #[cfg(feature = "jit")] + fn build_jit_evm_config( + chain_spec: Arc, + jit: &reth_node_core::args::JitArgs, + dump_dir: Option, + ) -> eyre::Result { + let config = alethia_reth_evm::jit::JitConfig { + enabled: jit.enabled, + hot_threshold: jit.hot_threshold, + worker_count: jit.worker_count, + channel_capacity: jit.channel_capacity, + max_pending_jobs: jit.max_pending_jobs, + max_bytecode_len: jit.max_bytecode_len, + code_cache_bytes: jit.code_cache_bytes, + idle_evict_duration: jit.idle_evict_duration, + debug: jit.debug, + blocking: jit.blocking, + }; + let backend = config.build_backend(dump_dir)?; + + if config.enabled { + tracing::warn!( + target: "reth::taiko::cli", + hot_threshold = config.hot_threshold, + workers = ?config.worker_count, + blocking = config.blocking, + "Started experimental revmc JIT backend; this may cause instability" + ); + } + + let factory = alethia_reth_evm::factory::TaikoEvmFactory::new(backend); + Ok(TaikoEvmConfig::new_with_evm_factory(chain_spec, factory)) + } +} + impl ExecutorBuilder for TaikoExecutorBuilder where Types: NodeTypes< @@ -36,12 +74,30 @@ where /// The EVM config to use. type EVM = TaikoEvmConfig; - /// Creates the EVM config. + /// Creates the EVM config from the node's `--jit` CLI settings. fn build_evm( self, ctx: &BuilderContext, ) -> impl future::Future> + Send { - future::ready(Ok(TaikoEvmConfig::new(ctx.chain_spec()))) + let jit = &ctx.config().jit; + + #[cfg(feature = "jit")] + let result = Self::build_jit_evm_config( + ctx.chain_spec(), + jit, + jit.debug.then(|| ctx.config().datadir().data_dir().join("jit")), + ); + + #[cfg(not(feature = "jit"))] + let result = if jit.enabled { + Err(eyre::eyre!( + "JIT compilation was requested with --jit, but this binary was built without the `jit` feature" + )) + } else { + Ok(TaikoEvmConfig::new(ctx.chain_spec())) + }; + + future::ready(result) } } diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 5475ecd..5899be4 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -12,6 +12,8 @@ pub use alethia_reth_evm as evm; pub use alethia_reth_payload as payload; pub use alethia_reth_primitives as primitives; pub use alethia_reth_rpc as rpc; +#[cfg(feature = "jit")] +pub use evm::jit::maybe_run_jit_helper; use chainspec::spec::TaikoChainSpec; use components::{TaikoConsensusBuilder, TaikoExecutorBuilder, TaikoNetworkBuilder}; diff --git a/crates/payload/src/builder/execution.rs b/crates/payload/src/builder/execution.rs index 7be5ccf..4a74b7f 100644 --- a/crates/payload/src/builder/execution.rs +++ b/crates/payload/src/builder/execution.rs @@ -347,7 +347,7 @@ mod tests { ])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let executor = TaikoBlockExecutor::new( evm, unzen_execution_ctx(), @@ -381,7 +381,7 @@ mod tests { .with_database(db_with_contracts(&[(Address::from(TAIKO_GOLDEN_TOUCH_ADDRESS), 0)])) .with_bundle_update() .build(); - let evm = TaikoEvmFactory.create_evm(&mut state, unzen_evm_env()); + let evm = TaikoEvmFactory::default().create_evm(&mut state, unzen_evm_env()); let executor = TaikoBlockExecutor::new( evm, unzen_execution_ctx(), diff --git a/crates/payload/src/builder/mod.rs b/crates/payload/src/builder/mod.rs index 72c6ebc..59a1dec 100644 --- a/crates/payload/src/builder/mod.rs +++ b/crates/payload/src/builder/mod.rs @@ -195,6 +195,10 @@ where debug!(target: "payload_builder", id=%payload_id, parent_hash=?parent_header.hash(), parent_number=parent_header.number, "building new payload"); + // Opt payload building into JIT dispatch (mirrors reth's engine tree and upstream payload + // builder). Unzen blocks still execute through the interpreter via the fork allowlist, and + // this local support flag is one of several gates before compiled code can run. + let evm_config = evm_config.clone().with_jit_support(); let mut builder = evm_config .builder_for_next_block( &mut db, diff --git a/justfile b/justfile index bd1b1c7..aad40f3 100644 --- a/justfile +++ b/justfile @@ -10,9 +10,12 @@ fmt-check: rustup toolchain install {{fmt_toolchain}} --component rustfmt && \ cargo +{{fmt_toolchain}} fmt --check +# The vendored reth-optimism-trie crate keeps upstream's documentation style, so it is linted +# without the missing-docs gates that apply to Alethia's own crates. clippy: rustup toolchain install {{toolchain}} && \ - cargo +{{toolchain}} clippy --workspace --all-features --no-deps -- -D warnings -D missing_docs -D clippy::missing_docs_in_private_items + cargo +{{toolchain}} clippy --workspace --exclude reth-optimism-trie --all-features --no-deps -- -D warnings -D missing_docs -D clippy::missing_docs_in_private_items && \ + cargo +{{toolchain}} clippy -p reth-optimism-trie --all-features --no-deps -- -D warnings clippy-fix: rustup toolchain install {{toolchain}} && \ From b5bcc8f7bc66f053ca4912421faca5f82912c830 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 00:43:14 +0800 Subject: [PATCH 07/28] chore: clippy msrv 1.95, vendor lint fixes, docs refresh; drop working design note Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 31 +++--- Cargo.toml | 4 +- clippy.toml | 2 +- crates/block/src/factory.rs | 7 +- crates/block/src/tx_selection/mod.rs | 4 +- crates/consensus/src/validation/anchor.rs | 14 +-- crates/evm/src/zk_gas/runtime.rs | 4 +- crates/evm/src/zk_gas/tests.rs | 3 +- crates/node/src/proof_history/sidecar.rs | 8 +- crates/node/src/proof_history/storage_init.rs | 5 +- crates/rpc/src/engine/api.rs | 27 +++-- .../2026-07-14-reth-v2.4.0-jit-design.md | 93 ---------------- vendor/reth-optimism-trie/Cargo.toml | 104 +++++++++--------- vendor/reth-optimism-trie/src/backfill/job.rs | 5 +- vendor/reth-optimism-trie/src/db/store.rs | 4 +- vendor/reth-optimism-trie/src/live.rs | 3 - 16 files changed, 117 insertions(+), 201 deletions(-) delete mode 100644 docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md diff --git a/CLAUDE.md b/CLAUDE.md index de392ed..3a84bc8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,8 +8,9 @@ Alethia-Reth is a Rust execution client for the Taiko protocol, built atop Parad ## Key Technologies -- Language: Rust (`1.93.1` toolchain via `rust-toolchain.toml` / `justfile`) -- Framework: Reth v2.0.0 APIs (`reth_node_builder`, `reth_rpc`, `reth_engine_primitives`, etc.) +- Language: Rust (`1.95.0` toolchain via `rust-toolchain.toml` / `justfile`) +- Framework: Reth v2.4.0 APIs (`reth_node_builder`, `reth_rpc`, `reth_engine_primitives`, etc.) +- Optional revmc JIT execution (`jit` feature, on by default in the binary; needs LLVM 22) - Target protocol: Taiko rollup networks - Build & dependency manager: Cargo + `just` @@ -18,18 +19,20 @@ Alethia-Reth is a Rust execution client for the Taiko protocol, built atop Parad ``` / Workspace root, Dockerfile, justfile, docs ├── bin/alethia-reth Binary crate producing `alethia-reth` -└── crates/ - ├── node Public API surface (`TaikoNode`, add-ons, component builders) - ├── block Block execution/assembly - ├── chainspec Chain specs + genesis data - ├── cli CLI wrapper (`TaikoCli`) - ├── consensus Beacon consensus extensions - ├── db Taiko-specific tables & codecs - ├── evm EVM config, handlers, execution helpers - ├── payload Payload builder service - ├── primitives Shared types (engine, payload attributes) - ├── rpc Taiko RPC (eth / engine / auth) - └── rpc-types Lightweight `taikoAuth` request/response types +├── crates/ +│ ├── node Public API surface (`TaikoNode`, add-ons, component builders) +│ ├── block Block execution/assembly +│ ├── chainspec Chain specs + genesis data +│ ├── cli CLI wrapper (`TaikoCli`) +│ ├── consensus Beacon consensus extensions +│ ├── db Taiko-specific tables & codecs +│ ├── evm EVM config, handlers, execution helpers, revmc JIT integration +│ ├── payload Payload builder service +│ ├── primitives Shared types (engine, payload attributes) +│ ├── rpc Taiko RPC (eth / engine / auth) +│ └── rpc-types Lightweight `taikoAuth` request/response types +└── vendor/ + └── reth-optimism-trie Vendored OP proofs-storage crate (see its TAIKO-PROVENANCE.md) ``` ## Development Guidelines diff --git a/Cargo.toml b/Cargo.toml index f9c04e8..ebb8787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,17 +96,16 @@ reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "943af2 reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-codecs = { version = "0.5.0", default-features = false } -reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-db-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-engine-local = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } @@ -114,6 +113,7 @@ reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", rev = reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } reth-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } diff --git a/clippy.toml b/clippy.toml index 9fcf58a..9c00d31 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.94" +msrv = "1.95" too-large-for-stack = 128 doc-valid-idents = [ "P2P", diff --git a/crates/block/src/factory.rs b/crates/block/src/factory.rs index 524379b..b5189d5 100644 --- a/crates/block/src/factory.rs +++ b/crates/block/src/factory.rs @@ -123,7 +123,12 @@ where >; /// Executor type created by this factory. type Executor<'a, DB: StateDB, I: Inspector<::Context>> = - TaikoBlockExecutor<'a, ::Evm, Spec, &'a RethReceiptBuilder>; + TaikoBlockExecutor< + 'a, + ::Evm, + Spec, + &'a RethReceiptBuilder, + >; /// Reference to EVM factory used by the executor. fn evm_factory(&self) -> &Self::EvmFactory { diff --git a/crates/block/src/tx_selection/mod.rs b/crates/block/src/tx_selection/mod.rs index 3c826d2..61a7135 100644 --- a/crates/block/src/tx_selection/mod.rs +++ b/crates/block/src/tx_selection/mod.rs @@ -163,9 +163,7 @@ where if !is_allowed_tx_type(tx.inner()) { best_txs.mark_invalid( &pool_tx, - InvalidPoolTransactionError::Consensus( - InvalidTransactionError::TxTypeNotSupported, - ), + InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported), ); continue; } diff --git a/crates/consensus/src/validation/anchor.rs b/crates/consensus/src/validation/anchor.rs index 0cd56b1..1faf974 100644 --- a/crates/consensus/src/validation/anchor.rs +++ b/crates/consensus/src/validation/anchor.rs @@ -61,10 +61,9 @@ pub fn validate_anchor_transaction( } // Ensure the tip is equal to zero. - let anchor_transaction_tip = - anchor_transaction.effective_tip_per_gas(ctx.base_fee_per_gas).ok_or_else(|| { - other_consensus_error("Anchor transaction tip must be set to zero") - })?; + let anchor_transaction_tip = anchor_transaction + .effective_tip_per_gas(ctx.base_fee_per_gas) + .ok_or_else(|| other_consensus_error("Anchor transaction tip must be set to zero"))?; if anchor_transaction_tip != 0 { return Err(other_consensus_error(format!( @@ -104,9 +103,10 @@ where AnchorValidationContext { timestamp: block.header().timestamp(), block_number: block.number(), - base_fee_per_gas: block.header().base_fee_per_gas().ok_or_else(|| { - other_consensus_error("Block base fee per gas must be set") - })?, + base_fee_per_gas: block + .header() + .base_fee_per_gas() + .ok_or_else(|| other_consensus_error("Block base fee per gas must be set"))?, }, ) } diff --git a/crates/evm/src/zk_gas/runtime.rs b/crates/evm/src/zk_gas/runtime.rs index 44a38cd..181834e 100644 --- a/crates/evm/src/zk_gas/runtime.rs +++ b/crates/evm/src/zk_gas/runtime.rs @@ -54,8 +54,8 @@ pub(crate) fn run_metered_plain( } }; - if let Some(result) = halt - && interpreter.bytecode.action().is_none() + if let Some(result) = halt && + interpreter.bytecode.action().is_none() { interpreter.halt(result); } diff --git a/crates/evm/src/zk_gas/tests.rs b/crates/evm/src/zk_gas/tests.rs index f2a9b30..ce80cdb 100644 --- a/crates/evm/src/zk_gas/tests.rs +++ b/crates/evm/src/zk_gas/tests.rs @@ -354,7 +354,8 @@ fn production_metered_path_stays_metered_when_noop_inspector_is_enabled() { #[test] fn factory_installs_unzen_schedule() { let env = evm_env(TaikoSpecId::UNZEN); - let evm = TaikoEvmFactory::default().create_evm(db_with_contract(limit_exceeding_keccak_bytecode()), env); + let evm = TaikoEvmFactory::default() + .create_evm(db_with_contract(limit_exceeding_keccak_bytecode()), env); let meter = evm.meter().expect("Unzen schedule should install a meter"); assert!(std::ptr::eq(meter.schedule(), &UNZEN_ZK_GAS_SCHEDULE)); diff --git a/crates/node/src/proof_history/sidecar.rs b/crates/node/src/proof_history/sidecar.rs index db2ae8a..e6cc22b 100644 --- a/crates/node/src/proof_history/sidecar.rs +++ b/crates/node/src/proof_history/sidecar.rs @@ -623,8 +623,12 @@ where /// Spawns the periodic proof-history pruning task. fn spawn_pruner_task(&self) { let pruner = Arc::new( - OpProofStoragePruner::new(self.storage.clone(), self.provider.clone(), self.config.window) - .with_batch_size(PROOF_HISTORY_PRUNE_BATCH_SIZE), + OpProofStoragePruner::new( + self.storage.clone(), + self.provider.clone(), + self.config.window, + ) + .with_batch_size(PROOF_HISTORY_PRUNE_BATCH_SIZE), ); let prune_interval = self.config.prune_interval; let retention_window = self.config.window; diff --git a/crates/node/src/proof_history/storage_init.rs b/crates/node/src/proof_history/storage_init.rs index 56123a2..ee51dd5 100644 --- a/crates/node/src/proof_history/storage_init.rs +++ b/crates/node/src/proof_history/storage_init.rs @@ -431,10 +431,7 @@ mod tests { use super::*; use alloy_consensus::Header; use reth_ethereum_primitives::EthPrimitives; - use reth_optimism_trie::{ - InMemoryProofsStorage, OpProofsStorage, - api::OpProofsInitProvider, - }; + use reth_optimism_trie::{InMemoryProofsStorage, OpProofsStorage, api::OpProofsInitProvider}; use reth_provider::test_utils::MockEthProvider; #[test] diff --git a/crates/rpc/src/engine/api.rs b/crates/rpc/src/engine/api.rs index 0dcdf8f..3c95aba 100644 --- a/crates/rpc/src/engine/api.rs +++ b/crates/rpc/src/engine/api.rs @@ -93,9 +93,12 @@ pub struct TaikoEngineApi TaikoEngineApi where - Provider: - HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory - + BalProvider + 'static, + Provider: HeaderProvider + + BlockReader + + DatabaseProviderFactory + + StateProviderFactory + + BalProvider + + 'static, PayloadT: PayloadTypes, Pool: TransactionPool + 'static, ChainSpec: EthereumHardforks + Send + Sync + 'static, @@ -118,9 +121,12 @@ where impl TaikoEngineApi where - Provider: - HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory - + BalProvider + 'static, + Provider: HeaderProvider + + BlockReader + + DatabaseProviderFactory + + StateProviderFactory + + BalProvider + + 'static, EngineT: EngineTypes< ExecutionData = TaikoExecutionData, PayloadAttributes = TaikoPayloadAttributes, @@ -198,9 +204,12 @@ where impl TaikoEngineApiServer for TaikoEngineApi where - Provider: - HeaderProvider + BlockReader + DatabaseProviderFactory + StateProviderFactory - + BalProvider + 'static, + Provider: HeaderProvider + + BlockReader + + DatabaseProviderFactory + + StateProviderFactory + + BalProvider + + 'static, EngineT: EngineTypes< ExecutionData = TaikoExecutionData, PayloadAttributes = TaikoPayloadAttributes, diff --git a/docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md b/docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md deleted file mode 100644 index 44ea8c7..0000000 --- a/docs/superpowers/specs/2026-07-14-reth-v2.4.0-jit-design.md +++ /dev/null @@ -1,93 +0,0 @@ -# Reth v2.4.0 bump + revmc JIT support (proving-isolated) - -Date: 2026-07-14 · Branch: `feat/reth-v2.4.0-jit` · Status: approved for autonomous execution - -## Goal - -1. Bump the reth dependency from rev `27bfddea` (pre-v2.3.0) to **v2.4.0** (`943af245c4d69c6c1df241df016c278ffb5d15df`). -2. Support revmc JIT execution (supersedes PR #218's vendored-revmc approach). -3. Guarantee JIT cannot affect proving: consensus gas accounting (Unzen zk-gas), RPC - execution, traced execution, and the proof-history subsystem stay interpreter/JIT-free. - -## Key facts driving the design - -- reth v2.4.0 ships **native experimental revmc JIT** (reth#23230): `reth_evm::JitBackend` - control trait, `ConfigureEvm::{with_jit_support_enabled, jit_backend}` hooks, - `reth_node_core::args::JitArgs` CLI, `reth_jit` action on the generic `RethApi` - (`reth` namespace), engine-tree/payload-builder opt-in via `.with_jit_support()`, - out-of-process compile helper (`maybe_run_jit_helper`). -- reth v2.4.0 locks revmc at `7e3536d` (branch main, revm 41, alloy-evm 0.37). We pin - **`56ed0063`** = fast-forward +3 commits, adding revmc#395 (dynamic-gas failure ordering — - consensus-relevant correctness fix). revmc#391 (non-blocking runtime controls) is already - in at `7e3536d`. This removes the need for PR #218's `vendor/revmc` tree entirely. -- Published-crate lattice: reth v2.4.0 ⇒ `reth-primitives-traits`/`reth-codecs` **0.5.0**; - v2.3.0 ⇒ 0.4.1. The OP monorepo (source of `reth-optimism-trie`) pins reth **v2.3.0** + - 0.4.1 on `develop` and has no v2.4.0 rev; `[patch]` cannot bridge a 0.4→0.5 registry split. - ⇒ **Vendor `reth-optimism-trie`** into `vendor/reth-optimism-trie` (from OP `develop`, - exact rev recorded in `TAIKO-PROVENANCE.md`), port it to v2.4.0 APIs. This also removes - the standing reth-pin ↔ OP-pin coupling. reth v2.4.0 still has no native historical-proofs - storage, so the crate remains required. -- MSRV: reth v2.4.0 and revmc require Rust **1.95** (toolchain bump 1.94.0 → 1.95.0); - revmc-llvm needs **LLVM 22** (PR #218's CI/Docker scripts are ported). - -## Dependency pin changes - -| dep | from | to | -|---|---|---| -| reth (all git crates) | rev 27bfddea | rev 943af245 (= tag v2.4.0) | -| reth-codecs / reth-primitives-traits | 0.3.0 | 0.5.0 | -| alloy-consensus family (consensus, eips, genesis, json-rpc, network, rpc-*, serde, signer*) | 2.0.0 | 2.1.1 | -| alloy-primitives / alloy-sol-types | 1.5.6 | 1.6.0 | -| alloy-evm | 0.33.0 | 0.37.0 | -| revm-database-interface | 11.0.0 | 41.0.0 | -| revmc (new) | — | git paradigmxyz/revmc rev 56ed0063 | -| reth-optimism-trie | OP git rev bcf489ea | path = vendor/reth-optimism-trie | - -## JIT architecture (upstream-aligned port of PR #218) - -- `crates/evm/src/jit.rs`: `JitConfig` (defaults mirror upstream `JitArgs`), `build_backend()` - → `revmc::runtime::JitBackend` (OutOfProcess mode), re-exports. PR #218's custom - `JitBackendControl` trait is dropped in favor of upstream `reth_evm::JitBackend`. -- `TaikoEvmFactory` (crates/evm/src/factory.rs): holds shared backend + disabled backend + - `jit_support` flag; wraps `TaikoEvm` in `revmc::revm_evm::JitEvm`. Implements - `reth_evm::JitBackend` by delegating to the shared backend. -- `TaikoEvmConfig` (crates/block/src/config.rs): implements - `ConfigureEvm::with_jit_support_enabled` (toggles factory flag) and - `ConfigureEvm::jit_backend`. No `for_rpc()` inversion needed — upstream model is opt-in. -- `TaikoExecutorBuilder::new(JitConfig)` builds the backend (dump dir under datadir when - `--jit.debug`); errors if `--jit` requested on a non-jit build. -- CLI: reuse upstream `reth_node_core::args::JitArgs` flattened into `TaikoCliExtArgs`; - `TaikoNodeExtArgs::jit_config()`; `TaikoNode::new(JitConfig)`. -- bin: `maybe_run_jit_helper()` first thing in `main` (out-of-process compile helper). -- RPC controls: upstream `RethApi` already exposes `reth_jit` (enable/disable/pause/unpause/ - clear) through `ConfigureEvm::jit_backend()` — no Taiko RPC code needed (PR #218's - `crates/rpc/src/eth/jit.rs` is dropped). - -## Proving isolation (four gates + tests) - -1. **Fork allowlist (hard consensus gate)**: `spec_supports_jit(TaikoSpecId)` — exhaustive - match; `UNZEN => false` (zk-gas metering needs per-opcode interpreter hooks). Belt-and- - suspenders re-check `schedule_for(spec).is_none()`. New forks fail compilation until - classified. -2. **Inspected execution** (tracers, zk-gas inspector paths) always selects the disabled - backend — compiled code never runs under an inspector. -3. **Local opt-in**: `jit_support` defaults **off**; only engine-tree canonical execution - (upstream generic code) and the Taiko payload builder call `.with_jit_support()`. All - RPC execution (eth_call/estimate/simulate/trace, taikoAuth preflight) uses the base - config ⇒ interpreter. -4. **Runtime enable**: compilation only starts with `--jit` (or `reth_jit` RPC), default off; - plus the `jit` cargo feature at build time. - -Proof-history reads state/tries and never executes the EVM — structurally unaffected. -Divergence tests (ported from PR #218's `zk_gas/tests.rs` + factory tests) assert -interpreter ≡ JIT results and that RPC/Unzen paths never touch the shared backend. - -## Not in scope - -- Enabling JIT by default (runtime default remains off). -- Dropping the zk-gas schedule (that's the Unzen-successor repricing work, PR #213 line). - -## Verification plan - -`just fmt` · `just clippy` · `just test` (workspace, all features) · non-jit build check · -JIT equality tests (requires local LLVM 22; noted in report if unavailable locally). diff --git a/vendor/reth-optimism-trie/Cargo.toml b/vendor/reth-optimism-trie/Cargo.toml index 3827165..d3b110d 100644 --- a/vendor/reth-optimism-trie/Cargo.toml +++ b/vendor/reth-optimism-trie/Cargo.toml @@ -9,7 +9,39 @@ repository = "https://github.com/paradigmxyz/reth" description = "Trie node storage for serving proofs in FP window fast" publish = false +[features] +test-utils = [ + "reth-codecs/test-utils", + "reth-db/test-utils", + "reth-ethereum-primitives?/test-utils", + "reth-evm/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-revm/test-utils", + "reth-tasks/test-utils", + "reth-trie/test-utils", + "reth-trie-common/test-utils", + "reth-trie-db/test-utils", +] +serde-bincode-compat = [ + "reth-trie-common/serde-bincode-compat", + "alloy-consensus/serde-bincode-compat", + "alloy-eips/serde-bincode-compat", + "reth-trie/serde-bincode-compat", + "dep:reth-ethereum-primitives", + "reth-ethereum-primitives?/serde", +] +metrics = [ + "reth-trie/metrics", + "reth-trie-db/metrics", + "dep:reth-metrics", + "dep:metrics", + "dep:eyre", + "reth-evm/metrics", +] + [dependencies] +reth-codecs.workspace = true # reth reth-db = { workspace = true, features = ["mdbx"] } reth-evm.workspace = true @@ -17,11 +49,10 @@ reth-execution-errors.workspace = true reth-primitives-traits.workspace = true reth-provider.workspace = true reth-revm.workspace = true +reth-tasks.workspace = true reth-trie = { workspace = true, features = ["serde"] } reth-trie-common = { workspace = true, features = ["serde"] } reth-trie-db.workspace = true -reth-codecs.workspace = true -reth-tasks.workspace = true # workaround: reth-trie/serde-bincode-compat activates serde-bincode-compat on # reth-ethereum-primitives (a transitive dep) without also activating its serde feature, @@ -31,82 +62,49 @@ reth-ethereum-primitives = { workspace = true, optional = true } # `metrics` feature metrics = { version = "0.24.3", default-features = false, optional = true } reth-metrics = { workspace = true, features = ["common"], optional = true } +alloy-eips.workspace = true # ethereum alloy-primitives.workspace = true -alloy-eips.workspace = true # async tokio = { workspace = true, features = ["sync"] } # codec +bincode = { version = "2.0.1", features = ["serde"] } bytes = { version = "1.11.1", default-features = false } serde.workspace = true -bincode = { version = "2.0.1", features = ["serde"] } - -# misc -parking_lot = "0.12.5" -thiserror.workspace = true auto_impl.workspace = true +crossbeam-channel = "0.5.13" +derive_more.workspace = true eyre = { workspace = true, optional = true } +parking_lot = "0.12.5" strum = { version = "0.27", default-features = false } +thiserror.workspace = true tracing.workspace = true -crossbeam-channel = "0.5.13" -derive_more.workspace = true [dev-dependencies] -reth-optimism-trie = { path = ".", features = ["test-utils"] } +alloy-consensus.workspace = true +alloy-genesis.workspace = true +eyre.workspace = true +mockall = "0.14.0" +reth-chainspec.workspace = true reth-codecs = { workspace = true, features = ["test-utils"] } -tempfile.workspace = true -tokio = { workspace = true, features = ["test-util", "rt-multi-thread", "macros"] } -test-case = "3" reth-db = { workspace = true, features = ["test-utils"] } # workaround for failing doc test reth-db-api = { workspace = true, features = ["test-utils"] } -reth-trie = { workspace = true, features = ["test-utils"] } -reth-provider = { workspace = true, features = ["test-utils"] } -reth-node-api.workspace = true -alloy-consensus.workspace = true -alloy-genesis.workspace = true -reth-chainspec.workspace = true reth-db-common.workspace = true reth-ethereum-primitives.workspace = true reth-evm-ethereum.workspace = true +reth-node-api.workspace = true +reth-optimism-trie = { path = ".", features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } reth-storage-errors.workspace = true +reth-trie = { workspace = true, features = ["test-utils"] } secp256k1 = { version = "0.31.1", default-features = false, features = ["rand", "std"] } -mockall = "0.14.0" -eyre.workspace = true +tempfile.workspace = true +test-case = "3" +tokio = { workspace = true, features = ["test-util", "rt-multi-thread", "macros"] } # misc serial_test = "3" - -[features] -test-utils = [ - "reth-codecs/test-utils", - "reth-db/test-utils", - "reth-ethereum-primitives?/test-utils", - "reth-evm/test-utils", - "reth-primitives-traits/test-utils", - "reth-provider/test-utils", - "reth-revm/test-utils", - "reth-tasks/test-utils", - "reth-trie/test-utils", - "reth-trie-common/test-utils", - "reth-trie-db/test-utils" -] -serde-bincode-compat = [ - "reth-trie-common/serde-bincode-compat", - "alloy-consensus/serde-bincode-compat", - "alloy-eips/serde-bincode-compat", - "reth-trie/serde-bincode-compat", - "dep:reth-ethereum-primitives", - "reth-ethereum-primitives?/serde", -] -metrics = [ - "reth-trie/metrics", - "reth-trie-db/metrics", - "dep:reth-metrics", - "dep:metrics", - "dep:eyre", - "reth-evm/metrics" -] diff --git a/vendor/reth-optimism-trie/src/backfill/job.rs b/vendor/reth-optimism-trie/src/backfill/job.rs index 78fa54e..5ee0972 100644 --- a/vendor/reth-optimism-trie/src/backfill/job.rs +++ b/vendor/reth-optimism-trie/src/backfill/job.rs @@ -382,10 +382,7 @@ where if existing == target { return Ok(()); } - return Err(BackfillError::SnapshotAnchorMismatch { - expected: target, - found: existing, - }); + return Err(BackfillError::SnapshotAnchorMismatch { expected: target, found: existing }); } info!( diff --git a/vendor/reth-optimism-trie/src/db/store.rs b/vendor/reth-optimism-trie/src/db/store.rs index 7efebc2..e8ad79c 100644 --- a/vendor/reth-optimism-trie/src/db/store.rs +++ b/vendor/reth-optimism-trie/src/db/store.rs @@ -903,7 +903,7 @@ impl OpProofsInitProvider if account_nodes.is_empty() { return Ok(()); } - self.persist_history_batch(0, account_nodes.into_iter(), true)?; + self.persist_history_batch(0, account_nodes, true)?; Ok(()) } @@ -930,7 +930,7 @@ impl OpProofsInitProvider if accounts.is_empty() { return Ok(()); } - self.persist_history_batch(0, accounts.into_iter(), true)?; + self.persist_history_batch(0, accounts, true)?; Ok(()) } diff --git a/vendor/reth-optimism-trie/src/live.rs b/vendor/reth-optimism-trie/src/live.rs index 51e64fb..d784cef 100644 --- a/vendor/reth-optimism-trie/src/live.rs +++ b/vendor/reth-optimism-trie/src/live.rs @@ -116,7 +116,6 @@ where operation_durations.state_root_duration_seconds - operation_durations.execution_duration_seconds; - info!( block_number = block.number(), ?operation_durations, @@ -146,7 +145,6 @@ where operation_durations.total_duration_seconds = write_duration; operation_durations.write_duration_seconds = write_duration; - info!( block_number = block.block.number, ?operation_durations, @@ -200,7 +198,6 @@ where operation_durations.total_duration_seconds = write_duration; operation_durations.write_duration_seconds = write_duration; - info!( start_block_number = block_updates.first().map(|(b, _, _)| b.block.number), end_block_number = block_updates.last().map(|(b, _, _)| b.block.number), From 31adb600f3e6ec5a2fd9fd425b8843c05cecfc26 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 09:27:35 +0800 Subject: [PATCH 08/28] refactor: simplify reth v2.4.0/JIT branch diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small, behavior-preserving cleanups over the v2.4.0 migration: - block/config: route the jit-gated `with_jit_support_enabled` and `jit_backend` through the crate's own `self.evm_factory()` accessor instead of reaching through `self.executor_factory.evm_factory()`, matching the adjacent `self.chain_spec()` usage. - evm/zk_gas/tests: extract the duplicated blocking in-process JIT backend construction into a single `blocking_jit_backend()` helper shared by both jit tests. Verified: cargo clippy (--all-features, CI lint gate) + nextest for alethia-reth-{block,evm} — 72 tests pass. Co-Authored-By: Claude Opus 4.8 --- crates/block/src/config.rs | 5 ++--- crates/evm/src/zk_gas/tests.rs | 25 +++++++++++-------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/block/src/config.rs b/crates/block/src/config.rs index dd96539..0592f44 100644 --- a/crates/block/src/config.rs +++ b/crates/block/src/config.rs @@ -170,8 +170,7 @@ impl ConfigureEvm for TaikoEvmConfig { /// fork allowlist in [`TaikoEvmFactory`]. #[cfg(feature = "jit")] fn with_jit_support_enabled(self, enabled: bool) -> Self { - let evm_factory = - self.executor_factory.evm_factory().clone().with_jit_support_enabled(enabled); + let evm_factory = self.evm_factory().clone().with_jit_support_enabled(enabled); Self::new_with_evm_factory(self.chain_spec().clone(), evm_factory) } @@ -179,7 +178,7 @@ impl ConfigureEvm for TaikoEvmConfig { /// RPC action against this configuration. #[cfg(feature = "jit")] fn jit_backend(&self) -> Option<&dyn reth_evm::JitBackend> { - Some(self.executor_factory.evm_factory()) + Some(self.evm_factory()) } /// Creates a new [`EvmEnv`] for the given header. diff --git a/crates/evm/src/zk_gas/tests.rs b/crates/evm/src/zk_gas/tests.rs index ce80cdb..b8f5893 100644 --- a/crates/evm/src/zk_gas/tests.rs +++ b/crates/evm/src/zk_gas/tests.rs @@ -394,19 +394,25 @@ fn non_unzen_default_create_evm_path_keeps_metering_disabled() { evm.transact(tx_env(5_000_000)).expect("non-Unzen tx should stay on the legacy path"); } +/// Builds a blocking, in-process JIT backend so tests observe compilation synchronously. #[cfg(feature = "jit")] -#[test] -fn jit_requires_local_support_and_falls_back_for_unzen() { +fn blocking_jit_backend() -> revmc::runtime::JitBackend { use revmc::runtime::{JitBackend, JitMode, RuntimeConfig}; - let backend = JitBackend::new(RuntimeConfig { + JitBackend::new(RuntimeConfig { enabled: true, blocking: true, single_error: false, jit_mode: JitMode::InProcess, ..RuntimeConfig::default() }) - .expect("blocking JIT backend should start"); + .expect("blocking JIT backend should start") +} + +#[cfg(feature = "jit")] +#[test] +fn jit_requires_local_support_and_falls_back_for_unzen() { + let backend = blocking_jit_backend(); let factory = TaikoEvmFactory::new(backend.clone()); let mut unsupported_evm = factory @@ -446,16 +452,7 @@ fn jit_and_interpreter_outputs( reth_revm::context::result::ResultAndState, revmc::runtime::RuntimeStatsSnapshot, ) { - use revmc::runtime::{JitBackend, JitMode, RuntimeConfig}; - - let backend = JitBackend::new(RuntimeConfig { - enabled: true, - blocking: true, - single_error: false, - jit_mode: JitMode::InProcess, - ..RuntimeConfig::default() - }) - .expect("blocking JIT backend should start"); + let backend = blocking_jit_backend(); let mut jit_evm = TaikoEvmFactory::new(backend.clone()) .with_jit_support() From 7e70482daefe15f6c4164a54114101c8c0c0c96a Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 10:33:06 +0800 Subject: [PATCH 09/28] fix(evm): restore pre-2.4.0 zk gas charging for OOG-halting steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reth v2.4.0 bump silently changed the committed zk gas of an OOG-terminating step. Pre-bump revm (interpreter 35) ran halt_oog() (spend-all) inside `step` itself, so the metered loop's gas delta charged the full forfeited remaining gas — the reference behavior taiko-geth's zk mirror follows (June 2026 cross-client audit). revm 41 returns a bare Err from `step` and leaves halt materialization to the caller, so the rewritten loop measured a wrapped/untouched `remaining` and the charge collapsed to zero. run_metered_plain now materializes the exceptional halt before charging, restoring the audited semantics; these totals feed header.difficulty on Unzen. revm's stock inspected loop already halts before step_end, so ZkGasInspector measures the same values without further changes. Also: - pin committed zk gas for OOG-terminating (forfeited gas x multiplier) and stack-underflow-terminating (static gas x multiplier; known taiko-geth divergence tracked geth-side) steps on both metering paths - forward log/log_full/frame_start/frame_end/selfdestruct from ZkGasInspector to the wrapped inner inspector (previously dropped) - guard the inspected-execution JIT invariant: debug_assert the disabled backend in transact_raw and downgrade the backend when set_inspector_enabled switches an instance to inspect mode - unit-test commit_would_exceed_block_limit against commit_transaction at the block-limit boundary and on u64 overflow Co-Authored-By: Claude Fable 5 --- crates/evm/src/alloy.rs | 18 +++++ crates/evm/src/execution.rs | 4 +- crates/evm/src/factory.rs | 6 +- crates/evm/src/zk_gas/adapter.rs | 43 ++++++++++- crates/evm/src/zk_gas/meter.rs | 23 ++++++ crates/evm/src/zk_gas/runtime.rs | 35 +++++---- crates/evm/src/zk_gas/tests.rs | 121 ++++++++++++++++++++++++++++++- 7 files changed, 229 insertions(+), 21 deletions(-) diff --git a/crates/evm/src/alloy.rs b/crates/evm/src/alloy.rs index 3f1b1c2..c051ca7 100644 --- a/crates/evm/src/alloy.rs +++ b/crates/evm/src/alloy.rs @@ -308,6 +308,16 @@ where self.base_evm().extra_execution_ctx.clone(), ); let result = if self.inspect { + // Trace correctness requires inspected execution to run on the disabled JIT backend: + // compiled code cannot deliver per-step inspector callbacks (revmc forwards only + // log, selfdestruct, and frame-end events). `create_evm_with_inspector` and + // `set_inspector_enabled` both pin the disabled backend; this guards the invariant + // against future refactors. + #[cfg(feature = "jit")] + debug_assert!( + !self.inner.backend().enabled(), + "inspected execution must run with the disabled JIT backend", + ); handler.inspect_run(&mut self.inner) } else { handler.run(&mut self.inner) @@ -458,6 +468,14 @@ where if enabled && self.base_evm().zk_gas_meter().is_some() { return; } + // Inspected execution cannot dispatch to compiled code (no per-step inspector + // callbacks), so switching an instance to inspect mode permanently downgrades it to a + // disabled backend. EVMs built through `create_evm_with_inspector` already hold one; + // this covers EVMs built through `create_evm` on non-metered specs. + #[cfg(feature = "jit")] + if enabled { + self.inner.set_backend(crate::jit::JitBackend::disabled()); + } self.inspect = enabled; } diff --git a/crates/evm/src/execution.rs b/crates/evm/src/execution.rs index 7eff12c..5dafaa0 100644 --- a/crates/evm/src/execution.rs +++ b/crates/evm/src/execution.rs @@ -19,7 +19,9 @@ use crate::{evm::TaikoEvm, handler::TaikoEvmHandler}; /// Inspected-execution plumbing required so [`TaikoEvm`] can sit inside revmc's `JitEvm` /// dispatcher, whose `InspectorEvmTr` implementation delegates to the wrapped EVM. Inspected /// frames themselves run revm's default inspected interpreter loop (with `ZkGasInspector` -/// hooks); only the accessors are delegated here. +/// hooks); only the accessors are delegated here. That default loop materializes exceptional +/// halts before `step_end`, matching the production loop's charge point (see +/// `zk_gas::runtime::run_metered_plain`). impl InspectorEvmTr for TaikoEvm where CTX: ContextSetters + JournalExt>, diff --git a/crates/evm/src/factory.rs b/crates/evm/src/factory.rs index 1f2d8af..3771aac 100644 --- a/crates/evm/src/factory.rs +++ b/crates/evm/src/factory.rs @@ -184,9 +184,9 @@ impl EvmFactory for TaikoEvmFactory { /// Creates a new instance of an EVM with an inspector. /// - /// Inspected execution always selects the disabled backend: compiled code cannot deliver - /// per-step inspector callbacks (only log forwarding), which tracers and the zk-gas - /// inspector depend on. + /// Inspected execution always selects the disabled backend as a correctness requirement: + /// compiled code cannot deliver the per-step inspector callbacks (revmc forwards only log, + /// selfdestruct, and frame-end events) that tracers and the zk-gas inspector depend on. fn create_evm_with_inspector>>( &self, db: DB, diff --git a/crates/evm/src/zk_gas/adapter.rs b/crates/evm/src/zk_gas/adapter.rs index 3baf7d9..feefdb6 100644 --- a/crates/evm/src/zk_gas/adapter.rs +++ b/crates/evm/src/zk_gas/adapter.rs @@ -6,11 +6,13 @@ //! 3. Either charge immediately, or defer charging until `call_end` / `create_end` confirms whether //! a spawn opcode actually opened child work. +use alloy_primitives::{Address, Log, U256}; use reth_revm::{ Inspector, context::{ContextTr, JournalTr}, + handler::FrameResult, interpreter::{ - CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, + CallInputs, CallOutcome, CreateInputs, CreateOutcome, FrameInput, Interpreter, interpreter::EthInterpreter, interpreter_types::Jumps, }, }; @@ -147,6 +149,30 @@ where } } + /// Forwards emitted logs to the wrapped inner inspector. + fn log(&mut self, context: &mut TaikoEvmContext, log: Log) { + self.inner.log(context, log); + } + + /// Forwards emitted logs (with interpreter access) to the wrapped inner inspector. + fn log_full( + &mut self, + interp: &mut Interpreter, + context: &mut TaikoEvmContext, + log: Log, + ) { + self.inner.log_full(interp, context, log); + } + + /// Forwards the generic frame-start hook to the wrapped inner inspector. + fn frame_start( + &mut self, + context: &mut TaikoEvmContext, + frame_input: &mut FrameInput, + ) -> Option { + self.inner.frame_start(context, frame_input) + } + /// Marks CALL-family steps that actually opened a child frame. fn call( &mut self, @@ -231,6 +257,21 @@ where set_custom_error(context); } } + + /// Forwards the generic frame-end hook to the wrapped inner inspector. + fn frame_end( + &mut self, + context: &mut TaikoEvmContext, + frame_input: &FrameInput, + frame_result: &mut FrameResult, + ) { + self.inner.frame_end(context, frame_input, frame_result); + } + + /// Forwards selfdestruct notifications to the wrapped inner inspector. + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + self.inner.selfdestruct(contract, target, value); + } } /// Per-inspector metering state carried across opcode callbacks. diff --git a/crates/evm/src/zk_gas/meter.rs b/crates/evm/src/zk_gas/meter.rs index 7e46e26..99ab549 100644 --- a/crates/evm/src/zk_gas/meter.rs +++ b/crates/evm/src/zk_gas/meter.rs @@ -34,6 +34,29 @@ impl<'a> ZkGasMeter<'a> { } } + /// Test-only constructor that seeds finalized and in-flight usage directly. + /// + /// The charge path caps in-flight usage at the remaining block budget, so boundary states + /// around (and past) the block limit cannot be reached through public charging — this + /// bypass exists to pin [`Self::commit_would_exceed_block_limit`] against + /// [`Self::commit_transaction`]. + #[cfg(test)] + pub(crate) const fn with_usage_for_tests( + schedule: &'a ZkGasSchedule, + block_zk_gas_used: u64, + tx_zk_gas_used: u64, + ) -> Self { + Self { + schedule, + block_zk_gas_used, + tx_zk_gas_used, + remaining_zk_gas: schedule + .block_limit + .saturating_sub(block_zk_gas_used) + .saturating_sub(tx_zk_gas_used), + } + } + /// Resets the in-flight zk gas for the current transaction. pub fn reset_transaction(&mut self) { self.tx_zk_gas_used = 0; diff --git a/crates/evm/src/zk_gas/runtime.rs b/crates/evm/src/zk_gas/runtime.rs index 181834e..ca40c1a 100644 --- a/crates/evm/src/zk_gas/runtime.rs +++ b/crates/evm/src/zk_gas/runtime.rs @@ -17,10 +17,15 @@ use super::{ /// Runs the interpreter loop while charging zk gas directly in the production path. /// -/// Mirrors revm's `Interpreter::run_plain` control flow: every step signals loop exit through -/// its `Err(InstructionResult)`, and the halt is materialized afterwards only when the step -/// left no pending action. Zk gas is charged for every executed step — including the halting -/// one — matching the interpreter gas it consumed. +/// Mirrors revm's `Interpreter::run_plain` control flow — every step signals loop exit through +/// its `Err(InstructionResult)` — with one deliberate difference: an exceptional halt is +/// materialized *before* the step's zk gas charge instead of after the loop. +/// [`Interpreter::halt`] spends all remaining gas for `OutOfGas` results, and that forfeited +/// gas is part of the step's consensus charge: pre-2.4.0 revm called `halt_oog()` (spend-all) +/// inside `step` itself, taiko-geth's zk mirror follows that reference behavior, and these +/// totals feed `header.difficulty` on Unzen. revm's inspected loop materializes the halt +/// before `step_end` for the same reason, which keeps [`super::adapter::ZkGasInspector`]'s +/// measurements identical to this loop's. #[inline] pub(crate) fn run_metered_plain( context: &mut CTX, @@ -29,12 +34,20 @@ pub(crate) fn run_metered_plain( gas_table: &GasTable, meter: &mut ZkGasMeter<'static>, ) -> InterpreterAction { - let halt = loop { + loop { let opcode = interpreter.bytecode.opcode(); let gas_before = interpreter.gas.remaining(); let step_result = interpreter.step(instruction_table, gas_table, context); + // Materialize an exceptional halt before charging so the `OutOfGas` spend-all is + // reflected in the measured step gas (see the function docs). + if let Err(result) = step_result && + interpreter.bytecode.action().is_none() + { + interpreter.halt(result); + } + let charge = if is_spawn_opcode(opcode) && matches!(interpreter.bytecode.action(), Some(InterpreterAction::NewFrame(_))) { @@ -46,18 +59,12 @@ pub(crate) fn run_metered_plain( if let Err(ZkGasOutcome::LimitExceeded) = charge { set_custom_error(context); interpreter.halt_fatal(); - break None; + break; } - if let Err(result) = step_result { - break Some(result); + if step_result.is_err() { + break; } - }; - - if let Some(result) = halt && - interpreter.bytecode.action().is_none() - { - interpreter.halt(result); } interpreter.take_next_action() diff --git a/crates/evm/src/zk_gas/tests.rs b/crates/evm/src/zk_gas/tests.rs index b8f5893..525fa0f 100644 --- a/crates/evm/src/zk_gas/tests.rs +++ b/crates/evm/src/zk_gas/tests.rs @@ -4,7 +4,10 @@ use alloy_evm::{Evm as AlloyEvm, EvmEnv, EvmFactory}; use alloy_primitives::{Address, address}; use reth_revm::{ Inspector, - context::TxEnv, + context::{ + TxEnv, + result::{ExecutionResult, HaltReason}, + }, db::InMemoryDB, inspector::NoOpInspector, interpreter::{ @@ -22,7 +25,7 @@ use crate::{factory::TaikoEvmFactory, spec::TaikoSpecId}; use super::{ adapter::ZK_GAS_LIMIT_ERR, meter::{ZkGasMeter, ZkGasOutcome}, - schedule::{FAILSAFE_MULTIPLIER, schedule_for}, + schedule::{FAILSAFE_MULTIPLIER, ZkGasSchedule, schedule_for}, unzen::{TX_INTRINSIC_ZK_GAS, UNZEN_ZK_GAS_SCHEDULE}, }; @@ -176,6 +179,28 @@ fn meter_rejects_block_budget_plus_one() { assert!(matches!(meter.charge_opcode(0xf0, 2), Err(ZkGasOutcome::LimitExceeded))); } +#[test] +fn meter_commit_would_exceed_agrees_with_commit_transaction_at_boundaries() { + let schedule = schedule_for(TaikoSpecId::UNZEN).expect("Unzen schedule"); + + // In-flight usage that lands exactly on the block limit commits. + let mut at_limit = ZkGasMeter::with_usage_for_tests(schedule, schedule.block_limit - 1, 1); + assert!(!at_limit.commit_would_exceed_block_limit()); + assert!(at_limit.commit_transaction().is_ok()); + assert_eq!(at_limit.block_zk_gas_used(), schedule.block_limit); + + // One zk gas past the limit is rejected, and the predicate agrees before the commit runs. + let mut past_limit = ZkGasMeter::with_usage_for_tests(schedule, schedule.block_limit - 1, 2); + assert!(past_limit.commit_would_exceed_block_limit()); + assert!(matches!(past_limit.commit_transaction(), Err(ZkGasOutcome::LimitExceeded))); + + // A `u64` overflow in the running block total also counts as exceeding the limit. + let overflow_schedule = ZkGasSchedule { block_limit: u64::MAX, ..UNZEN_ZK_GAS_SCHEDULE }; + let mut overflowing = ZkGasMeter::with_usage_for_tests(&overflow_schedule, u64::MAX, 1); + assert!(overflowing.commit_would_exceed_block_limit()); + assert!(matches!(overflowing.commit_transaction(), Err(ZkGasOutcome::LimitExceeded))); +} + #[test] fn meter_returns_limit_exceeded_for_precompile_over_block_budget() { let schedule = schedule_for(TaikoSpecId::UNZEN).expect("Unzen schedule"); @@ -306,6 +331,93 @@ fn production_metered_path_matches_inspector_path_for_ordinary_opcodes() { assert_eq!(production_zk_gas, inspector_zk_gas); } +/// Executes `bytecode` on Unzen through the production (no-inspector) and the inspected +/// metered path, asserting both halt with `expected_halt` and returning the per-tx zk gas +/// charged by each. +fn metered_paths_zk_gas_for_halting_tx( + bytecode: Bytecode, + gas_limit: u64, + expected_halt: fn(&HaltReason) -> bool, +) -> (u64, u64) { + let mut production_evm = TaikoEvmFactory::default() + .create_evm(db_with_contract(bytecode.clone()), evm_env(TaikoSpecId::UNZEN)); + let result = production_evm.transact(tx_env(gas_limit)).expect("production path executes"); + assert!( + matches!(&result.result, ExecutionResult::Halt { reason, .. } if expected_halt(reason)), + "production path halted unexpectedly: {:?}", + result.result, + ); + let production_zk_gas = + production_evm.meter().expect("production path installs a meter").tx_zk_gas_used(); + + let mut inspector_evm = TaikoEvmFactory::default().create_evm_with_inspector( + db_with_contract(bytecode), + evm_env(TaikoSpecId::UNZEN), + NoOpInspector {}, + ); + let result = inspector_evm.transact(tx_env(gas_limit)).expect("inspector path executes"); + assert!( + matches!(&result.result, ExecutionResult::Halt { reason, .. } if expected_halt(reason)), + "inspector path halted unexpectedly: {:?}", + result.result, + ); + let inspector_zk_gas = + inspector_evm.meter().expect("inspector path installs a meter").tx_zk_gas_used(); + + (production_zk_gas, inspector_zk_gas) +} + +#[test] +fn metered_paths_charge_forfeited_gas_for_an_oog_halting_step() { + // The gas limit admits both `PUSH1` steps (3 gas each) but dies on `ADD`'s table-driven + // static charge with 1 gas left. An `OutOfGas` halt forfeits all remaining gas + // (`Interpreter::halt` spends it), and that forfeiture is part of the step's zk gas + // charge: pre-2.4.0 revm ran `halt_oog()` (spend-all) inside `step` itself, and + // taiko-geth's zk mirror follows that reference behavior. These committed values feed + // `header.difficulty` on Unzen, so any drift here is a consensus change. + let schedule = schedule_for(TaikoSpecId::UNZEN).expect("Unzen schedule"); + let push_multiplier = u64::from(schedule.opcode_multipliers[usize::from(opcode::PUSH1)]); + let add_multiplier = u64::from(schedule.opcode_multipliers[usize::from(opcode::ADD)]); + let forfeited_gas = 1; + let expected = 2 * 3 * push_multiplier + forfeited_gas * add_multiplier; + + let (production_zk_gas, inspector_zk_gas) = + metered_paths_zk_gas_for_halting_tx(simple_arithmetic_bytecode(), 21_000 + 7, |reason| { + matches!(reason, HaltReason::OutOfGas(_)) + }); + + assert_eq!( + production_zk_gas, expected, + "OOG-halting step must charge the gas it forfeited via spend-all" + ); + assert_eq!( + inspector_zk_gas, expected, + "inspector path must charge the OOG-halting step exactly like production" + ); +} + +#[test] +fn metered_paths_charge_static_gas_zk_gas_for_a_stack_underflow_step() { + // `ADD` on an empty stack: the GasTable static charge (3 gas) lands before revm validates + // the stack, so the halting step contributes `3 x multiplier`. Known cross-client + // divergence: taiko-geth validates the stack before charging gas and meters 0 for this + // step; the fix is tracked on the geth side, so this pins reth's committed value. + let schedule = schedule_for(TaikoSpecId::UNZEN).expect("Unzen schedule"); + let add_multiplier = u64::from(schedule.opcode_multipliers[usize::from(opcode::ADD)]); + let expected = 3 * add_multiplier; + + let (production_zk_gas, inspector_zk_gas) = + metered_paths_zk_gas_for_halting_tx(stack_underflow_bytecode(), 100_000, |reason| { + matches!(reason, HaltReason::StackUnderflow) + }); + + assert_eq!(production_zk_gas, expected, "stack-underflow step must charge its static gas cost"); + assert_eq!( + inspector_zk_gas, expected, + "inspector path must charge the underflow-halting step exactly like production" + ); +} + #[test] fn unzen_adapter_raises_dedicated_error_when_limit_is_exceeded() { let mut evm = TaikoEvmFactory::default().create_evm( @@ -580,6 +692,11 @@ fn simple_arithmetic_bytecode() -> Bytecode { ])) } +/// `ADD` with nothing on the stack: halts with a stack underflow after its static gas charge. +fn stack_underflow_bytecode() -> Bytecode { + Bytecode::new_raw(Bytes::from(vec![opcode::ADD])) +} + #[test] fn high_range_precompile_collision_resolves_to_failsafe_not_canonical() { // An `L1Sload`-style address whose low byte (0x01) collides with `ecrecover` but whose upper From 5aa1746244e80253b15ecc3a1ec7e10548121f1f Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 10:33:06 +0800 Subject: [PATCH 10/28] chore: vendored MIT notice, LLVM script hardening, BAL tripwire, doc nits - ship upstream's LICENSE-MIT inside vendor/reth-optimism-trie (copied from rust/op-reth/LICENSE-MIT at the vendored commit) and note it in TAIKO-PROVENANCE.md - install_llvm_ubuntu.sh hard-fails when a required binary is missing instead of warning and surfacing later as an obscure link error - align the remaining actions/checkout@v4 in ci.yml with the other jobs - name-discard the builder's EIP-7928 block access list with a debug_assert tripwire (Taiko schedules no Amsterdam fork) - refresh the init_tracing doc for the TracingGuards return type Co-Authored-By: Claude Fable 5 --- .github/scripts/install_llvm_ubuntu.sh | 4 ++-- .github/workflows/ci.yml | 2 +- crates/cli/src/lib.rs | 2 +- crates/payload/src/builder/mod.rs | 12 ++++++++--- vendor/reth-optimism-trie/LICENSE-MIT | 21 +++++++++++++++++++ vendor/reth-optimism-trie/TAIKO-PROVENANCE.md | 3 ++- 6 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 vendor/reth-optimism-trie/LICENSE-MIT diff --git a/.github/scripts/install_llvm_ubuntu.sh b/.github/scripts/install_llvm_ubuntu.sh index 36a31f3..510ad88 100755 --- a/.github/scripts/install_llvm_ubuntu.sh +++ b/.github/scripts/install_llvm_ubuntu.sh @@ -21,8 +21,8 @@ rm -f "$llvm_installer" for bin in "${bins[@]}"; do if ! command -v "$bin-$version" &>/dev/null; then - echo "Warning: $bin-$version not found" >&2 - continue + echo "Error: $bin-$version not found after install" >&2 + exit 1 fi ln -fs "$(command -v "$bin-$version")" "/usr/bin/$bin" done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43c5f55..cddfc7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: rui314/setup-mold@v1 - run: .github/scripts/install_llvm.sh ubuntu - uses: dtolnay/rust-toolchain@master diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 2de95cc..bff8586 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -265,7 +265,7 @@ impl< /// Initializes tracing with the configured options. /// - /// If file logging is enabled, this function returns a guard that must be kept alive to ensure + /// If file logging is enabled, the returned [`TracingGuards`] must be kept alive to ensure /// that all logs are flushed to disk. pub fn init_tracing(&self) -> eyre::Result { let guard = self.inner.logs.init_tracing()?; diff --git a/crates/payload/src/builder/mod.rs b/crates/payload/src/builder/mod.rs index 59a1dec..04c06db 100644 --- a/crates/payload/src/builder/mod.rs +++ b/crates/payload/src/builder/mod.rs @@ -258,9 +258,7 @@ where } }; - let BlockBuilderOutcome { execution_result: _, block, .. } = if let Some(mut handle) = - state_root_handle - { + let outcome = if let Some(mut handle) = state_root_handle { // Drop the state hook so the trie task sees the final state updates and can finalize. reth_evm::Evm::db_mut(builder.evm_mut()).set_state_hook(None); @@ -284,6 +282,14 @@ where builder.finish(state_provider.as_ref(), None)? }; + // Taiko schedules no Amsterdam fork, so the builder never produces an EIP-7928 block + // access list; the named discard is a tripwire for that assumption. + let BlockBuilderOutcome { execution_result: _, block, block_access_list, .. } = outcome; + debug_assert!( + block_access_list.is_none(), + "unexpected EIP-7928 block access list for a Taiko block" + ); + debug!(target: "payload_builder", id=%payload_id, sealed_block_header = ?block.sealed_header(), "sealed built block"); Ok(BuildOutcome::Freeze(EthBuiltPayload::new(Arc::new(block), total_fees, None, None))) diff --git a/vendor/reth-optimism-trie/LICENSE-MIT b/vendor/reth-optimism-trie/LICENSE-MIT new file mode 100644 index 0000000..8f4c878 --- /dev/null +++ b/vendor/reth-optimism-trie/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022-2026 Reth Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md b/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md index 447dba9..a13ee20 100644 --- a/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md +++ b/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md @@ -4,7 +4,8 @@ This directory vendors the `reth-optimism-trie` crate from the Optimism monorepo commit `9b802fdb62c96a1cd70b2144ce89979d4e41f4ec` (, path `rust/op-reth/crates/trie`). -It retains the crate's original MIT license. +It retains the crate's original MIT license; the upstream notice is vendored alongside the +sources as `LICENSE-MIT` (copied from `rust/op-reth/LICENSE-MIT` at the same commit). ## Why vendored From 0402ca037348782d987464be69536b04d392c075 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 12:03:41 +0800 Subject: [PATCH 11/28] fix(cli): honor --jit on the re-execute command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reth's re-execute command parses JitArgs but never consumes them (v2.4.0), and the components closure it accepts only receives the chain spec — so `alethia-reth re-execute --jit` silently ran the interpreter on a permanently disabled backend, and non-jit builds accepted the flag without the "built without the `jit` feature" error the node command raises. The node executor builder's backend construction is now a shared `evm_config_from_jit_args` helper covering both cfg branches; the ReExecute arm builds its EVM config from the command's own JitArgs through it. Unit tests pin the enabled-flag wiring on jit builds and the hard error on non-jit builds. Also excludes the vendored reth-optimism-trie manifest from `just fmt`'s cargo-sort pass, which was flattening upstream's dependency grouping on every run. Co-Authored-By: Claude Fable 5 --- crates/cli/src/lib.rs | 21 ++++++++- crates/node/src/components.rs | 89 +++++++++++++++++++++++++---------- justfile | 5 +- 3 files changed, 88 insertions(+), 27 deletions(-) diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index bff8586..8296d2a 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -23,7 +23,7 @@ use alethia_reth_block::config::TaikoEvmConfig; use alethia_reth_chainspec::spec::TaikoChainSpec; use alethia_reth_node::{ TaikoNode, - components::ProviderTaikoBlockReader, + components::{ProviderTaikoBlockReader, evm_config_from_jit_args}, consensus::validation::TaikoBeaconConsensus, proof_history::{ DEFAULT_PROOF_HISTORY_MAX_STARTUP_PRUNE_BLOCKS, @@ -258,6 +258,25 @@ impl< runner.run_command_until_exit(|ctx| command.execute::(ctx)) } Commands::ReExecute(command) => { + // reth's re-execute never consumes its own `--jit` args (v2.4.0), and the + // components closure it accepts cannot see them, so the JIT-aware EVM config + // must be built here. Mirrors the node's executor builder, including the hard + // error when `--jit` is requested on a non-jit build. + let chain_spec = command + .chain_spec() + .cloned() + .ok_or_else(|| eyre::eyre!("re-execute requires a chain spec"))?; + let evm = evm_config_from_jit_args(chain_spec, &command.jit, None)?; + let components = move |spec: Arc| { + let block_reader = Arc::new(ProviderTaikoBlockReader(NoopProvider::< + TaikoChainSpec, + EthPrimitives, + >::new( + spec.clone() + ))); + let consensus = Arc::new(TaikoBeaconConsensus::new(spec, block_reader)); + (evm.clone(), consensus) + }; runner.run_until_ctrl_c(command.execute::(components, rt)) } } diff --git a/crates/node/src/components.rs b/crates/node/src/components.rs index ff9fa4e..a5472ae 100644 --- a/crates/node/src/components.rs +++ b/crates/node/src/components.rs @@ -24,15 +24,23 @@ use tracing::info; #[derive(Debug, Clone, Default)] pub struct TaikoExecutorBuilder; -impl TaikoExecutorBuilder { - /// Builds an EVM configuration whose factory owns the shared revmc backend created from - /// the node's `--jit` CLI settings. +/// Builds the EVM configuration honoring the runtime `--jit` CLI settings. +/// +/// With the `jit` Cargo feature, the returned config's factory owns a shared revmc backend +/// created from `jit` (`dump_dir` receives debug artifacts when `--jit.debug` is set). Without +/// the feature this errors when `--jit` was requested, and otherwise returns the plain +/// interpreter config. +/// +/// Shared by the node's [`ExecutorBuilder`] and by CLI subcommands that execute blocks outside +/// the node builder (`re-execute`), whose reth-provided components closure cannot see the +/// command's own `JitArgs`. +pub fn evm_config_from_jit_args( + chain_spec: Arc, + jit: &reth_node_core::args::JitArgs, + dump_dir: Option, +) -> eyre::Result { #[cfg(feature = "jit")] - fn build_jit_evm_config( - chain_spec: Arc, - jit: &reth_node_core::args::JitArgs, - dump_dir: Option, - ) -> eyre::Result { + { let config = alethia_reth_evm::jit::JitConfig { enabled: jit.enabled, hot_threshold: jit.hot_threshold, @@ -60,6 +68,18 @@ impl TaikoExecutorBuilder { let factory = alethia_reth_evm::factory::TaikoEvmFactory::new(backend); Ok(TaikoEvmConfig::new_with_evm_factory(chain_spec, factory)) } + + #[cfg(not(feature = "jit"))] + { + let _ = dump_dir; + if jit.enabled { + Err(eyre::eyre!( + "JIT compilation was requested with --jit, but this binary was built without the `jit` feature" + )) + } else { + Ok(TaikoEvmConfig::new(chain_spec)) + } + } } impl ExecutorBuilder for TaikoExecutorBuilder @@ -80,24 +100,9 @@ where ctx: &BuilderContext, ) -> impl future::Future> + Send { let jit = &ctx.config().jit; + let dump_dir = jit.debug.then(|| ctx.config().datadir().data_dir().join("jit")); - #[cfg(feature = "jit")] - let result = Self::build_jit_evm_config( - ctx.chain_spec(), - jit, - jit.debug.then(|| ctx.config().datadir().data_dir().join("jit")), - ); - - #[cfg(not(feature = "jit"))] - let result = if jit.enabled { - Err(eyre::eyre!( - "JIT compilation was requested with --jit, but this binary was built without the `jit` feature" - )) - } else { - Ok(TaikoEvmConfig::new(ctx.chain_spec())) - }; - - future::ready(result) + future::ready(evm_config_from_jit_args(ctx.chain_spec(), jit, dump_dir)) } } @@ -192,4 +197,38 @@ mod tests { let reader = ProviderTaikoBlockReader(provider); assert_eq!(reader.block_timestamp_by_hash(B256::ZERO), None); } + + #[cfg(feature = "jit")] + #[test] + fn evm_config_from_jit_args_wires_the_runtime_enabled_flag() { + use reth_node_core::args::JitArgs; + + let enabled = evm_config_from_jit_args( + TAIKO_MAINNET.clone(), + &JitArgs { enabled: true, ..Default::default() }, + None, + ) + .expect("jit build constructs a backend"); + assert!(enabled.evm_factory().backend().enabled()); + + let disabled = evm_config_from_jit_args(TAIKO_MAINNET.clone(), &JitArgs::default(), None) + .expect("jit build constructs a backend"); + assert!(!disabled.evm_factory().backend().enabled()); + } + + #[cfg(not(feature = "jit"))] + #[test] + fn evm_config_from_jit_args_rejects_jit_on_non_jit_builds() { + use reth_node_core::args::JitArgs; + + let err = evm_config_from_jit_args( + TAIKO_MAINNET.clone(), + &JitArgs { enabled: true, ..Default::default() }, + None, + ) + .expect_err("non-jit build must reject --jit"); + assert!(err.to_string().contains("`jit` feature")); + + assert!(evm_config_from_jit_args(TAIKO_MAINNET.clone(), &JitArgs::default(), None).is_ok()); + } } diff --git a/justfile b/justfile index aad40f3..c4afc10 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,13 @@ toolchain := "1.95.0" fmt_toolchain := "nightly" +# `cargo sort` lists the Alethia crates explicitly so it skips the vendored +# reth-optimism-trie manifest, which keeps upstream's dependency grouping (mirrors the +# vendored-crate clippy exemption below). fmt: rustup toolchain install {{fmt_toolchain}} --component rustfmt && \ cargo +{{fmt_toolchain}} fmt && \ - cargo sort --workspace --grouped + cargo sort --grouped . bin/alethia-reth crates/* fmt-check: rustup toolchain install {{fmt_toolchain}} --component rustfmt && \ From ba1f3c13249ff9f254e8d3d0efd53396fd21fa02 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 13:09:57 +0800 Subject: [PATCH 12/28] docs: exempt vendor/** from the mandatory documentation policy The justfile's clippy recipe already exempts the vendored reth-optimism-trie crate from the missing-docs gates, but AGENTS.md's Documentation Policy listed only tests and examples as exclusions, so enforcement and the written policy disagreed. Records the vendor exemption in the policy with the same rationale (keep vendored sources close to upstream; `-D warnings` still applies). Co-Authored-By: Claude Fable 5 --- AGENTS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 9653df2..dc4d5be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +27,9 @@ - files under `tests/**` - `#[cfg(test)]` items and test-only helpers - examples + - vendored third-party crates under `vendor/**` — kept close to upstream so re-vendoring + stays a clean diff; still linted with `-D warnings`, but exempt from the missing-docs + gates (see the `just clippy` recipe and each crate's provenance note) - The docs gate is required before completion: run `just clippy`. ## Testing Guidelines From 057c363ed3fef6f46a7671d827e1985652ff040a Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 14:26:27 +0800 Subject: [PATCH 13/28] build(deps): un-vendor reth-optimism-trie, bump reth to f2eecc6 Consume reth-optimism-trie straight from the Optimism monorepo instead of the vendored copy. The vendor existed because OP pinned reth v2.3.0 (published crates 0.4.x) while Alethia pins v2.4.0 (0.5.x), a split cargo cannot unify; ethereum-optimism/optimism#21766 moves OP to f2eecc6 (v2.4.0 + 9 commits), so the git dependency is possible again provided both workspaces reference the identical paradigmxyz/reth commit. The reth pin therefore moves from the v2.4.0 tag to f2eecc6 to match. The dependency rev is the head of the still-open #21766; bump it to the merge commit once that PR lands. The Taiko-retained live collector (upstream deleted the module in favor of its engine service) moves in-tree as first-party code at crates/node/src/proof_history/live.rs, ported to the crate's public API and given direct unit tests (execute/store happy path, window and state-root rejections, unwind and reorg-replacement round-trips). The vendored crate's own test suite was byte-identical to upstream's and now runs in upstream CI instead. With no vendored source left, the vendor carve-outs are reverted: single clippy pass with the missing-docs gates, cargo sort --workspace, and the AGENTS.md vendor/** exclusion and CLAUDE.md tree entry are removed. Verified with just fmt, just clippy, and just test (247/247), plus a default-features check. Co-Authored-By: Claude Fable 5 --- AGENTS.md | 3 - CLAUDE.md | 26 +- Cargo.lock | 357 +- Cargo.toml | 127 +- crates/node/Cargo.toml | 6 + crates/node/src/proof_history/live.rs | 404 ++ crates/node/src/proof_history/mod.rs | 1 + crates/node/src/proof_history/sidecar.rs | 2 +- justfile | 10 +- vendor/reth-optimism-trie/Cargo.toml | 110 - vendor/reth-optimism-trie/LICENSE-MIT | 21 - vendor/reth-optimism-trie/TAIKO-PROVENANCE.md | 26 - vendor/reth-optimism-trie/src/api.rs | 663 --- .../src/backfill/changesets.rs | 104 - .../reth-optimism-trie/src/backfill/error.rs | 61 - vendor/reth-optimism-trie/src/backfill/job.rs | 496 -- vendor/reth-optimism-trie/src/backfill/mod.rs | 25 - .../reth-optimism-trie/src/backfill/tests.rs | 821 ---- vendor/reth-optimism-trie/src/cursor.rs | 129 - .../reth-optimism-trie/src/cursor_factory.rs | 196 - vendor/reth-optimism-trie/src/db/cursor.rs | 1465 ------ vendor/reth-optimism-trie/src/db/mod.rs | 24 - .../reth-optimism-trie/src/db/models/block.rs | 79 - .../src/db/models/change_set.rs | 128 - .../reth-optimism-trie/src/db/models/key.rs | 476 -- vendor/reth-optimism-trie/src/db/models/kv.rs | 68 - .../reth-optimism-trie/src/db/models/mod.rs | 279 -- .../src/db/models/snapshot.rs | 146 - .../src/db/models/storage.rs | 258 -- .../reth-optimism-trie/src/db/models/value.rs | 210 - .../src/db/models/version.rs | 192 - vendor/reth-optimism-trie/src/db/schema.md | 366 -- vendor/reth-optimism-trie/src/db/store.rs | 4014 ----------------- .../src/db/store_v2/backfill.rs | 532 --- .../src/db/store_v2/cursor/account.rs | 151 - .../src/db/store_v2/cursor/account_trie.rs | 236 - .../src/db/store_v2/cursor/mod.rs | 198 - .../src/db/store_v2/cursor/snapshot.rs | 286 -- .../src/db/store_v2/cursor/snapshot_tests.rs | 254 -- .../src/db/store_v2/cursor/storage.rs | 203 - .../src/db/store_v2/cursor/storage_trie.rs | 282 -- .../src/db/store_v2/cursor/tests.rs | 2322 ---------- .../src/db/store_v2/init.rs | 156 - .../src/db/store_v2/metrics.rs | 97 - .../reth-optimism-trie/src/db/store_v2/mod.rs | 121 - .../src/db/store_v2/provider_ro.rs | 148 - .../src/db/store_v2/provider_rw.rs | 192 - .../src/db/store_v2/read.rs | 195 - .../src/db/store_v2/snapshot_init.rs | 161 - .../src/db/store_v2/snapshot_read.rs | 117 - .../src/db/store_v2/snapshot_tests.rs | 773 ---- .../src/db/store_v2/tests.rs | 2299 ---------- .../src/db/store_v2/write.rs | 1127 ----- .../src/engine/buffer/metrics.rs | 12 - .../src/engine/buffer/mod.rs | 12 - .../src/engine/buffer/overlay.rs | 287 -- .../src/engine/buffer/state.rs | 341 -- vendor/reth-optimism-trie/src/engine/error.rs | 73 - .../reth-optimism-trie/src/engine/handle.rs | 171 - .../reth-optimism-trie/src/engine/metrics.rs | 22 - vendor/reth-optimism-trie/src/engine/mod.rs | 86 - .../src/engine/persistence/error.rs | 18 - .../src/engine/persistence/handle.rs | 80 - .../src/engine/persistence/metrics.rs | 55 - .../src/engine/persistence/mod.rs | 13 - .../src/engine/persistence/service.rs | 152 - .../reth-optimism-trie/src/engine/runner.rs | 273 -- .../src/engine/service_guard.rs | 26 - vendor/reth-optimism-trie/src/engine/state.rs | 331 -- .../src/engine/tasks/execute_block.rs | 170 - .../src/engine/tasks/flush.rs | 30 - .../src/engine/tasks/index_block.rs | 103 - .../src/engine/tasks/mod.rs | 21 - .../src/engine/tasks/reorg.rs | 107 - .../src/engine/tasks/sync_to.rs | 30 - .../src/engine/tasks/unwind.rs | 63 - vendor/reth-optimism-trie/src/error.rs | 256 -- vendor/reth-optimism-trie/src/in_memory.rs | 960 ---- vendor/reth-optimism-trie/src/initialize.rs | 1634 ------- vendor/reth-optimism-trie/src/lib.rs | 89 - vendor/reth-optimism-trie/src/live.rs | 217 - vendor/reth-optimism-trie/src/metrics.rs | 718 --- vendor/reth-optimism-trie/src/proof.rs | 403 -- vendor/reth-optimism-trie/src/provider.rs | 257 -- vendor/reth-optimism-trie/src/prune/error.rs | 96 - .../reth-optimism-trie/src/prune/metrics.rs | 39 - vendor/reth-optimism-trie/src/prune/mod.rs | 11 - vendor/reth-optimism-trie/src/prune/pruner.rs | 683 --- vendor/reth-optimism-trie/src/prune/task.rs | 61 - .../reth-optimism-trie/src/snapshot/error.rs | 70 - vendor/reth-optimism-trie/src/snapshot/job.rs | 675 --- vendor/reth-optimism-trie/src/snapshot/mod.rs | 29 - .../reth-optimism-trie/src/snapshot/tests.rs | 487 -- vendor/reth-optimism-trie/src/test_utils.rs | 331 -- vendor/reth-optimism-trie/tests/lib.rs | 2163 --------- vendor/reth-optimism-trie/tests/live.rs | 633 --- 96 files changed, 602 insertions(+), 32829 deletions(-) create mode 100644 crates/node/src/proof_history/live.rs delete mode 100644 vendor/reth-optimism-trie/Cargo.toml delete mode 100644 vendor/reth-optimism-trie/LICENSE-MIT delete mode 100644 vendor/reth-optimism-trie/TAIKO-PROVENANCE.md delete mode 100644 vendor/reth-optimism-trie/src/api.rs delete mode 100644 vendor/reth-optimism-trie/src/backfill/changesets.rs delete mode 100644 vendor/reth-optimism-trie/src/backfill/error.rs delete mode 100644 vendor/reth-optimism-trie/src/backfill/job.rs delete mode 100644 vendor/reth-optimism-trie/src/backfill/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/backfill/tests.rs delete mode 100644 vendor/reth-optimism-trie/src/cursor.rs delete mode 100644 vendor/reth-optimism-trie/src/cursor_factory.rs delete mode 100644 vendor/reth-optimism-trie/src/db/cursor.rs delete mode 100644 vendor/reth-optimism-trie/src/db/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/block.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/change_set.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/key.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/kv.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/snapshot.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/storage.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/value.rs delete mode 100644 vendor/reth-optimism-trie/src/db/models/version.rs delete mode 100644 vendor/reth-optimism-trie/src/db/schema.md delete mode 100644 vendor/reth-optimism-trie/src/db/store.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/backfill.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/account.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/account_trie.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/snapshot.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/snapshot_tests.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/storage.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/storage_trie.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/cursor/tests.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/init.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/metrics.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/provider_ro.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/provider_rw.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/read.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/snapshot_init.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/snapshot_read.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/snapshot_tests.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/tests.rs delete mode 100644 vendor/reth-optimism-trie/src/db/store_v2/write.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/buffer/metrics.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/buffer/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/buffer/overlay.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/buffer/state.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/error.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/handle.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/metrics.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/persistence/error.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/persistence/handle.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/persistence/metrics.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/persistence/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/persistence/service.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/runner.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/service_guard.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/state.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/execute_block.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/flush.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/index_block.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/reorg.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/sync_to.rs delete mode 100644 vendor/reth-optimism-trie/src/engine/tasks/unwind.rs delete mode 100644 vendor/reth-optimism-trie/src/error.rs delete mode 100644 vendor/reth-optimism-trie/src/in_memory.rs delete mode 100644 vendor/reth-optimism-trie/src/initialize.rs delete mode 100644 vendor/reth-optimism-trie/src/lib.rs delete mode 100644 vendor/reth-optimism-trie/src/live.rs delete mode 100644 vendor/reth-optimism-trie/src/metrics.rs delete mode 100644 vendor/reth-optimism-trie/src/proof.rs delete mode 100644 vendor/reth-optimism-trie/src/provider.rs delete mode 100644 vendor/reth-optimism-trie/src/prune/error.rs delete mode 100644 vendor/reth-optimism-trie/src/prune/metrics.rs delete mode 100644 vendor/reth-optimism-trie/src/prune/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/prune/pruner.rs delete mode 100644 vendor/reth-optimism-trie/src/prune/task.rs delete mode 100644 vendor/reth-optimism-trie/src/snapshot/error.rs delete mode 100644 vendor/reth-optimism-trie/src/snapshot/job.rs delete mode 100644 vendor/reth-optimism-trie/src/snapshot/mod.rs delete mode 100644 vendor/reth-optimism-trie/src/snapshot/tests.rs delete mode 100644 vendor/reth-optimism-trie/src/test_utils.rs delete mode 100644 vendor/reth-optimism-trie/tests/lib.rs delete mode 100644 vendor/reth-optimism-trie/tests/live.rs diff --git a/AGENTS.md b/AGENTS.md index dc4d5be..9653df2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,9 +27,6 @@ - files under `tests/**` - `#[cfg(test)]` items and test-only helpers - examples - - vendored third-party crates under `vendor/**` — kept close to upstream so re-vendoring - stays a clean diff; still linted with `-D warnings`, but exempt from the missing-docs - gates (see the `just clippy` recipe and each crate's provenance note) - The docs gate is required before completion: run `just clippy`. ## Testing Guidelines diff --git a/CLAUDE.md b/CLAUDE.md index 3a84bc8..444d0bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,20 +19,18 @@ Alethia-Reth is a Rust execution client for the Taiko protocol, built atop Parad ``` / Workspace root, Dockerfile, justfile, docs ├── bin/alethia-reth Binary crate producing `alethia-reth` -├── crates/ -│ ├── node Public API surface (`TaikoNode`, add-ons, component builders) -│ ├── block Block execution/assembly -│ ├── chainspec Chain specs + genesis data -│ ├── cli CLI wrapper (`TaikoCli`) -│ ├── consensus Beacon consensus extensions -│ ├── db Taiko-specific tables & codecs -│ ├── evm EVM config, handlers, execution helpers, revmc JIT integration -│ ├── payload Payload builder service -│ ├── primitives Shared types (engine, payload attributes) -│ ├── rpc Taiko RPC (eth / engine / auth) -│ └── rpc-types Lightweight `taikoAuth` request/response types -└── vendor/ - └── reth-optimism-trie Vendored OP proofs-storage crate (see its TAIKO-PROVENANCE.md) +└── crates/ + ├── node Public API surface (`TaikoNode`, add-ons, component builders) + ├── block Block execution/assembly + ├── chainspec Chain specs + genesis data + ├── cli CLI wrapper (`TaikoCli`) + ├── consensus Beacon consensus extensions + ├── db Taiko-specific tables & codecs + ├── evm EVM config, handlers, execution helpers, revmc JIT integration + ├── payload Payload builder service + ├── primitives Shared types (engine, payload attributes) + ├── rpc Taiko RPC (eth / engine / auth) + └── rpc-types Lightweight `taikoAuth` request/response types ``` ## Development Guidelines diff --git a/Cargo.lock b/Cargo.lock index 7434def..d03358c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,14 +227,19 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-eth", + "derive_more", "eyre", "futures-util", "reth", + "reth-chainspec", "reth-db", + "reth-db-common", "reth-engine-local", "reth-engine-primitives", "reth-ethereum", "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", "reth-execution-types", "reth-node-api", "reth-node-builder", @@ -243,6 +248,7 @@ dependencies = [ "reth-optimism-trie", "reth-primitives-traits", "reth-provider", + "reth-revm", "reth-rpc", "reth-rpc-builder", "reth-rpc-eth-api", @@ -3217,12 +3223,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - [[package]] name = "dunce" version = "1.0.5" @@ -3679,15 +3679,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fragile" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" -dependencies = [ - "futures-core", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -5607,32 +5598,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "mockall" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "modular-bitfield" version = "0.13.1" @@ -6419,32 +6384,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - [[package]] name = "prefix-trie" version = "0.8.4" @@ -7101,7 +7040,7 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "reth" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -7142,7 +7081,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7169,7 +7108,7 @@ dependencies = [ [[package]] name = "reth-chain-state" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7202,7 +7141,7 @@ dependencies = [ [[package]] name = "reth-chainspec" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7222,7 +7161,7 @@ dependencies = [ [[package]] name = "reth-cli" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-genesis", "clap", @@ -7235,7 +7174,7 @@ dependencies = [ [[package]] name = "reth-cli-commands" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7319,7 +7258,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "reth-tasks", "tokio", @@ -7329,7 +7268,7 @@ dependencies = [ [[package]] name = "reth-cli-util" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7380,7 +7319,7 @@ dependencies = [ [[package]] name = "reth-config" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "eyre", "humantime-serde", @@ -7396,7 +7335,7 @@ dependencies = [ [[package]] name = "reth-consensus" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -7410,7 +7349,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7423,7 +7362,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7448,7 +7387,7 @@ dependencies = [ [[package]] name = "reth-db" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "derive_more", @@ -7477,7 +7416,7 @@ dependencies = [ [[package]] name = "reth-db-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7503,7 +7442,7 @@ dependencies = [ [[package]] name = "reth-db-common" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7533,7 +7472,7 @@ dependencies = [ [[package]] name = "reth-db-models" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7548,7 +7487,7 @@ dependencies = [ [[package]] name = "reth-discv4" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7573,7 +7512,7 @@ dependencies = [ [[package]] name = "reth-discv5" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7597,7 +7536,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "dashmap", @@ -7621,7 +7560,7 @@ dependencies = [ [[package]] name = "reth-downloaders" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7652,7 +7591,7 @@ dependencies = [ [[package]] name = "reth-ecies" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "aes", "alloy-primitives", @@ -7679,7 +7618,7 @@ dependencies = [ [[package]] name = "reth-engine-local" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7702,7 +7641,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7729,7 +7668,7 @@ dependencies = [ [[package]] name = "reth-engine-tree" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -7779,7 +7718,7 @@ dependencies = [ [[package]] name = "reth-engine-util" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7809,7 +7748,7 @@ dependencies = [ [[package]] name = "reth-era" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7827,7 +7766,7 @@ dependencies = [ [[package]] name = "reth-era-downloader" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "bytes", @@ -7843,7 +7782,7 @@ dependencies = [ [[package]] name = "reth-era-utils" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7866,7 +7805,7 @@ dependencies = [ [[package]] name = "reth-errors" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -7877,7 +7816,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7905,7 +7844,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7927,7 +7866,7 @@ dependencies = [ [[package]] name = "reth-ethereum" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -7947,7 +7886,7 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "clap", "eyre", @@ -7970,7 +7909,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7986,7 +7925,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8002,7 +7941,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -8015,7 +7954,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8045,7 +7984,7 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8059,7 +7998,7 @@ dependencies = [ [[package]] name = "reth-etl" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "rayon", "reth-db-api", @@ -8069,7 +8008,7 @@ dependencies = [ [[package]] name = "reth-evm" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -8094,7 +8033,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8114,7 +8053,7 @@ dependencies = [ [[package]] name = "reth-execution-cache" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "fixed-cache", @@ -8132,7 +8071,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-evm", "alloy-primitives", @@ -8145,7 +8084,7 @@ dependencies = [ [[package]] name = "reth-execution-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8164,7 +8103,7 @@ dependencies = [ [[package]] name = "reth-exex" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8202,7 +8141,7 @@ dependencies = [ [[package]] name = "reth-exex-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8216,7 +8155,7 @@ dependencies = [ [[package]] name = "reth-fs-util" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "serde", "serde_json", @@ -8226,7 +8165,7 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8252,7 +8191,7 @@ dependencies = [ [[package]] name = "reth-ipc" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "bytes", "futures", @@ -8272,7 +8211,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "bitflags", "byteorder", @@ -8289,7 +8228,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "bindgen", "cc", @@ -8298,7 +8237,7 @@ dependencies = [ [[package]] name = "reth-metrics" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "futures", "metrics", @@ -8311,7 +8250,7 @@ dependencies = [ [[package]] name = "reth-net-banlist" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "ipnet", @@ -8320,7 +8259,7 @@ dependencies = [ [[package]] name = "reth-net-nat" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "futures-util", "if-addrs", @@ -8334,7 +8273,7 @@ dependencies = [ [[package]] name = "reth-network" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8391,7 +8330,7 @@ dependencies = [ [[package]] name = "reth-network-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8416,7 +8355,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -8439,7 +8378,7 @@ dependencies = [ [[package]] name = "reth-network-peers" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8454,7 +8393,7 @@ dependencies = [ [[package]] name = "reth-network-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -8468,7 +8407,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "anyhow", "bincode 1.3.3", @@ -8485,7 +8424,7 @@ dependencies = [ [[package]] name = "reth-node-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -8509,7 +8448,7 @@ dependencies = [ [[package]] name = "reth-node-builder" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8577,7 +8516,7 @@ dependencies = [ [[package]] name = "reth-node-core" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8632,7 +8571,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8665,6 +8604,7 @@ dependencies = [ "reth-rpc", "reth-rpc-api", "reth-rpc-builder", + "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-server-types", @@ -8680,7 +8620,7 @@ dependencies = [ [[package]] name = "reth-node-ethstats" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8704,7 +8644,7 @@ dependencies = [ [[package]] name = "reth-node-events" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8728,7 +8668,7 @@ dependencies = [ [[package]] name = "reth-node-metrics" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "bytes", "eyre", @@ -8752,7 +8692,7 @@ dependencies = [ [[package]] name = "reth-node-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "reth-chainspec", "reth-db-api", @@ -8764,10 +8704,9 @@ dependencies = [ [[package]] name = "reth-optimism-trie" version = "1.11.3" +source = "git+https://github.com/ethereum-optimism/optimism?rev=43795a4eae8bf5515a7ced196879385b05237ba9#43795a4eae8bf5515a7ced196879385b05237ba9" dependencies = [ - "alloy-consensus", "alloy-eips", - "alloy-genesis", "alloy-primitives", "auto_impl", "bincode 2.0.1", @@ -8776,34 +8715,22 @@ dependencies = [ "derive_more", "eyre", "metrics", - "mockall", "parking_lot", - "reth-chainspec", "reth-codecs", "reth-db", - "reth-db-api", - "reth-db-common", "reth-ethereum-primitives", "reth-evm", - "reth-evm-ethereum", "reth-execution-errors", "reth-metrics", - "reth-node-api", - "reth-optimism-trie", "reth-primitives-traits", "reth-provider", "reth-revm", - "reth-storage-errors", "reth-tasks", "reth-trie", "reth-trie-common", "reth-trie-db", - "secp256k1 0.31.1", "serde", - "serial_test", "strum 0.27.2", - "tempfile", - "test-case", "thiserror 2.0.18", "tokio", "tracing", @@ -8812,7 +8739,7 @@ dependencies = [ [[package]] name = "reth-payload-builder" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8836,7 +8763,7 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8848,7 +8775,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8871,7 +8798,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8914,7 +8841,7 @@ dependencies = [ [[package]] name = "reth-provider" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -8962,7 +8889,7 @@ dependencies = [ [[package]] name = "reth-prune" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8989,7 +8916,7 @@ dependencies = [ [[package]] name = "reth-prune-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "arbitrary", @@ -9005,7 +8932,7 @@ dependencies = [ [[package]] name = "reth-revm" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9020,7 +8947,7 @@ dependencies = [ [[package]] name = "reth-rpc" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -9097,7 +9024,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-genesis", @@ -9127,7 +9054,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-network", "alloy-provider", @@ -9170,7 +9097,7 @@ dependencies = [ [[package]] name = "reth-rpc-convert" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-evm", @@ -9190,7 +9117,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9221,7 +9148,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -9268,7 +9195,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -9319,7 +9246,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-rpc-types-engine", "http", @@ -9333,7 +9260,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9364,7 +9291,7 @@ dependencies = [ [[package]] name = "reth-stages" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9412,7 +9339,7 @@ dependencies = [ [[package]] name = "reth-stages-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9440,7 +9367,7 @@ dependencies = [ [[package]] name = "reth-stages-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "arbitrary", @@ -9454,7 +9381,7 @@ dependencies = [ [[package]] name = "reth-static-file" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "parking_lot", @@ -9474,7 +9401,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "clap", @@ -9489,7 +9416,7 @@ dependencies = [ [[package]] name = "reth-storage-api" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -9515,7 +9442,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9532,7 +9459,7 @@ dependencies = [ [[package]] name = "reth-tasks" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "crossbeam-utils", "dashmap", @@ -9553,7 +9480,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "tokio", "tokio-stream", @@ -9563,7 +9490,7 @@ dependencies = [ [[package]] name = "reth-tracing" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "clap", "eyre", @@ -9581,7 +9508,7 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "base64 0.22.1", "clap", @@ -9600,7 +9527,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9643,7 +9570,7 @@ dependencies = [ [[package]] name = "reth-trie" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9668,7 +9595,7 @@ dependencies = [ [[package]] name = "reth-trie-common" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9695,7 +9622,7 @@ dependencies = [ [[package]] name = "reth-trie-db" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "metrics", @@ -9714,7 +9641,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-evm", "alloy-primitives", @@ -9737,7 +9664,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse" version = "2.4.0" -source = "git+https://github.com/paradigmxyz/reth?rev=943af245c4d69c6c1df241df016c278ffb5d15df#943af245c4d69c6c1df241df016c278ffb5d15df" +source = "git+https://github.com/paradigmxyz/reth?rev=f2eecc65482af4085e43eedb27a32024bf177a0d#f2eecc65482af4085e43eedb27a32024bf177a0d" dependencies = [ "alloy-primitives", "alloy-trie", @@ -10783,31 +10710,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serial_test" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" -dependencies = [ - "futures-executor", - "futures-util", - "log", - "once_cell", - "parking_lot", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "sha1" version = "0.10.7" @@ -11247,45 +11149,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "test-case" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" -dependencies = [ - "test-case-macros", -] - -[[package]] -name = "test-case-core" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "test-case-macros" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", - "test-case-core", -] - [[package]] name = "thin-vec" version = "0.2.18" diff --git a/Cargo.toml b/Cargo.toml index ebb8787..60f67b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "crates/primitives", "crates/rpc", "crates/rpc-types", - "vendor/reth-optimism-trie", ] resolver = "2" @@ -78,7 +77,7 @@ parity-scale-codec = "3.2.1" # LLVM toolchain into every build; Alethia wires revmc into its own EVM factory instead and # gates it behind the workspace `jit` features. `reth-revm/portable` (a default) is applied # via this workspace's reth-revm dependency below. -reth = { git = "https://github.com/paradigmxyz/reth", package = "reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false, features = [ +reth = { git = "https://github.com/paradigmxyz/reth", package = "reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false, features = [ "jemalloc", "otlp", "otlp-logs", @@ -88,75 +87,81 @@ reth = { git = "https://github.com/paradigmxyz/reth", package = "reth", rev = "9 "gmp", "min-trace-logs", ] } -reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-chain-state = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-cli = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-cli = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } reth-codecs = { version = "0.5.0", default-features = false } -reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-db-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-engine-local = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-node-core = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-primitives = { git = "https://github.com/paradigmxyz/reth", package = "reth-ethereum-primitives", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } +reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-db-api = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-engine-local = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-primitives = { git = "https://github.com/paradigmxyz/reth", package = "reth-ethereum-primitives", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } reth-primitives-traits = { version = "0.5.0", default-features = false } -reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false, features = [ +reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false, features = [ "portable", ] } -reth-rpc = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } -reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-trie-common = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df", default-features = false } -reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "943af245c4d69c6c1df241df016c278ffb5d15df" } - -# Vendored from the Optimism monorepo; see vendor/reth-optimism-trie/TAIKO-PROVENANCE.md. -reth-optimism-trie = { path = "vendor/reth-optimism-trie", default-features = false, features = ["serde-bincode-compat"] } +reth-rpc = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-trie-common = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } + +# Consumed from the Optimism monorepo (path rust/op-reth/crates/trie). The rev is the head of +# ethereum-optimism/optimism#21766, their post-v2.4.0 reth bump: the crate's reth dependencies +# resolve inside that workspace, so this rev and the reth pin above must reference the same +# paradigmxyz/reth commit (spelled identically) or the graph gets two incompatible reth copies. +# Move this to the merge commit once #21766 lands; the Taiko live collector the crate used to +# carry for us lives in crates/node/src/proof_history/live.rs. +reth-optimism-trie = { git = "https://github.com/ethereum-optimism/optimism", rev = "43795a4eae8bf5515a7ced196879385b05237ba9", default-features = false, features = ["serde-bincode-compat"] } # revm revm-database-interface = { version = "41.0.0", default-features = false } # revmc JIT: pinned to the fast-forward of reth v2.4.0's locked revision (7e3536d) that adds # revmc#395 (dynamic-gas failure ordering). revmc#391 (non-blocking runtime controls) is -# already included. Bump alongside the reth pin. +# already included. reth f2eecc6 locks a newer revmc (520462a4); bump ours deliberately — +# the JIT integration needs revalidation — rather than in lockstep with the reth pin. revmc = { git = "https://github.com/paradigmxyz/revmc", rev = "56ed00631c4542507f7198bdc9d53dd6471a2ef8", default-features = false, features = [ "llvm-prefer-static", ] } diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 7a3f6f9..286a771 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -29,6 +29,7 @@ alloy-consensus = { workspace = true } alloy-eips = { workspace = true } alloy-primitives = { workspace = true } alloy-rpc-types-eth = { workspace = true } +derive_more = { workspace = true } eyre = { workspace = true } futures-util = { workspace = true } reth = { workspace = true } @@ -37,6 +38,7 @@ reth-engine-local = { workspace = true } reth-engine-primitives = { workspace = true } reth-ethereum = { workspace = true } reth-ethereum-primitives = { workspace = true } +reth-evm = { workspace = true } reth-execution-types = { workspace = true } reth-node-api = { workspace = true } reth-node-builder = { workspace = true } @@ -45,6 +47,7 @@ reth-node-ethereum = { workspace = true } reth-optimism-trie = { workspace = true, features = ["metrics"] } reth-primitives-traits = { workspace = true } reth-provider = { workspace = true } +reth-revm = { workspace = true } reth-rpc = { workspace = true } reth-rpc-builder = { workspace = true } reth-rpc-eth-api = { workspace = true } @@ -57,5 +60,8 @@ tokio = { workspace = true, features = ["time"] } tracing = { workspace = true } [dev-dependencies] +reth-chainspec = { workspace = true } +reth-db-common = { workspace = true } +reth-evm-ethereum = { workspace = true } reth-provider = { workspace = true, features = ["test-utils"] } tempfile = { workspace = true } diff --git a/crates/node/src/proof_history/live.rs b/crates/node/src/proof_history/live.rs new file mode 100644 index 0000000..2fe6697 --- /dev/null +++ b/crates/node/src/proof_history/live.rs @@ -0,0 +1,404 @@ +//! Live trie collector executing blocks against proof-history storage. +//! +//! Ported from the `live` module of the OP monorepo's `reth-optimism-trie` crate (last present +//! upstream at `bcf489ea`): upstream replaced it with an `engine` service driven by op-node's +//! engine flow, while Alethia's proof-history sidecar drives collection itself. The collector +//! therefore lives on here as first-party code, written against the crate's public storage API +//! (`get_proof_window`). + +use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; +use derive_more::Constructor; +use reth_evm::{ConfigureEvm, execute::Executor}; +use reth_optimism_trie::{ + BlockStateDiff, OpProofsStorage, OpProofsStorageError, OpProofsStore, + api::{OpProofsProviderRO, OpProofsProviderRw, OperationDurations}, + provider::OpProofsStateProviderRef, +}; +use reth_primitives_traits::{AlloyBlockHeader, BlockTy, RecoveredBlock}; +use reth_provider::{ + DatabaseProviderFactory, HashedPostStateProvider, StateProviderFactory, StateReader, + StateRootProvider, +}; +use reth_revm::database::StateProviderDatabase; +use reth_trie_common::{HashedPostStateSorted, updates::TrieUpdatesSorted}; +use std::{sync::Arc, time::Instant}; +use tracing::info; + +/// Live trie collector for external proofs storage. +#[derive(Debug, Constructor)] +pub struct LiveTrieCollector<'tx, Evm, Provider, PreimageStore> +where + Evm: ConfigureEvm, + Provider: StateReader + DatabaseProviderFactory + StateProviderFactory, +{ + /// EVM configuration used to re-execute blocks whose trie updates are collected. + evm_config: Evm, + /// Provider the collector reads parent state from. + provider: Provider, + /// Proof-history storage the collected trie updates are written to. + storage: &'tx OpProofsStorage, +} + +impl<'tx, Evm, Provider, Store> LiveTrieCollector<'tx, Evm, Provider, Store> +where + Evm: ConfigureEvm, + Provider: StateReader + DatabaseProviderFactory + StateProviderFactory, + Store: 'tx + OpProofsStore + Clone + 'static, +{ + /// Execute a block and store the updates in the storage. + pub fn execute_and_store_block_updates( + &self, + block: &RecoveredBlock>, + ) -> Result<(), OpProofsStorageError> { + let mut operation_durations = OperationDurations::default(); + + let start = Instant::now(); + // ensure that we have the state of the parent block + let provider_ro = self.storage.provider_ro()?; + // Errors with `NoBlocksFound` when the proof window is empty. + let window = provider_ro.get_proof_window()?; + let (earliest, latest) = (window.earliest.number, window.latest.number); + + let parent_block_number = block.number() - 1; + if parent_block_number < earliest { + return Err(OpProofsStorageError::UnknownParent); + } + + if parent_block_number > latest { + return Err(OpProofsStorageError::MissingParentBlock { + block_number: block.number(), + parent_block_number, + latest_block_number: latest, + }); + } + + let block_ref = + BlockWithParent::new(block.parent_hash(), NumHash::new(block.number(), block.hash())); + + // TODO: should we check block hash here? + + let state_provider = OpProofsStateProviderRef::new( + self.provider.state_by_block_hash(block.parent_hash())?, + self.storage.provider_ro()?, + parent_block_number, + ); + + let db = StateProviderDatabase::new(&state_provider); + let block_executor = self.evm_config.batch_executor(db); + + let execution_result = block_executor.execute(&(*block).clone())?; + + operation_durations.execution_duration_seconds = start.elapsed(); + + let hashed_state = state_provider.hashed_post_state(&execution_result.state); + let (state_root, trie_updates) = + state_provider.state_root_with_updates(hashed_state.clone())?; + + operation_durations.state_root_duration_seconds = + start.elapsed() - operation_durations.execution_duration_seconds; + + if state_root != block.state_root() { + return Err(OpProofsStorageError::StateRootMismatch { + block_number: block.number(), + current_state_hash: state_root, + expected_state_hash: block.state_root(), + }); + } + + let provider_rw = self.storage.provider_rw()?; + let update_result = provider_rw.store_trie_updates( + block_ref, + BlockStateDiff { + sorted_trie_updates: trie_updates.into_sorted(), + sorted_post_state: hashed_state.into_sorted(), + }, + )?; + provider_rw.commit()?; + + operation_durations.total_duration_seconds = start.elapsed(); + operation_durations.write_duration_seconds = operation_durations.total_duration_seconds - + operation_durations.state_root_duration_seconds - + operation_durations.execution_duration_seconds; + + info!( + block_number = block.number(), + ?operation_durations, + ?update_result, + "Block executed and trie updates stored successfully", + ); + + Ok(()) + } + + /// Store trie updates for a given block. + pub fn store_block_updates( + &self, + block: BlockWithParent, + sorted_trie_updates: TrieUpdatesSorted, + sorted_post_state: HashedPostStateSorted, + ) -> Result<(), OpProofsStorageError> { + let start = Instant::now(); + let mut operation_durations = OperationDurations::default(); + + let provider_rw = self.storage.provider_rw()?; + let storage_result = provider_rw + .store_trie_updates(block, BlockStateDiff { sorted_trie_updates, sorted_post_state })?; + provider_rw.commit()?; + + let write_duration = start.elapsed(); + operation_durations.total_duration_seconds = write_duration; + operation_durations.write_duration_seconds = write_duration; + + info!( + block_number = block.block.number, + ?operation_durations, + ?storage_result, + "Trie updates stored successfully", + ); + + Ok(()) + } + + /// Handles chain reorganizations by replacing block updates after a common ancestor. + /// + /// This method removes all block updates after the latest common ancestor (the block before + /// the first block in `new_blocks`) and replaces them with the updates from the provided new + /// chain. + /// + /// # Arguments + /// + /// * `new_blocks` - A vector of references to `RecoveredBlock` instances representing the new + /// blocks to be added to the trie storage. + pub fn unwind_and_store_block_updates( + &self, + block_updates: Vec<(BlockWithParent, Arc, Arc)>, + ) -> Result<(), OpProofsStorageError> { + if block_updates.is_empty() { + return Ok(()); + } + + let start = Instant::now(); + let mut operation_durations = OperationDurations::default(); + let first = &block_updates[0].0; + let latest_common_block = + BlockNumHash::new(first.block.number.saturating_sub(1), first.parent); + let mut block_trie_updates: Vec<(BlockWithParent, BlockStateDiff)> = + Vec::with_capacity(block_updates.len()); + + for (block, trie_updates, hashed_state) in &block_updates { + block_trie_updates.push(( + *block, + BlockStateDiff { + sorted_trie_updates: (**trie_updates).clone(), + sorted_post_state: (**hashed_state).clone(), + }, + )); + } + + let provider_rw = self.storage.provider_rw()?; + provider_rw.replace_updates(latest_common_block, block_trie_updates)?; + provider_rw.commit()?; + let write_duration = start.elapsed(); + operation_durations.total_duration_seconds = write_duration; + operation_durations.write_duration_seconds = write_duration; + + info!( + start_block_number = block_updates.first().map(|(b, _, _)| b.block.number), + end_block_number = block_updates.last().map(|(b, _, _)| b.block.number), + ?operation_durations, + "Trie updates rewound and stored successfully", + ); + Ok(()) + } + + /// Remove account, storage and trie updates from historical storage for all blocks from + /// the specified block (inclusive). + pub fn unwind_history(&self, to: BlockWithParent) -> Result<(), OpProofsStorageError> { + let provider_rw = self.storage.provider_rw()?; + provider_rw.unwind_history(to)?; + provider_rw.commit() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::Header; + use alloy_primitives::B256; + use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET}; + use reth_db::Database; + use reth_db_common::init::init_genesis; + use reth_ethereum_primitives::{Block, BlockBody}; + use reth_evm_ethereum::EthEvmConfig; + use reth_optimism_trie::{ + RethTrieStorageLayout, db::MdbxProofsStorage, initialize::InitializationJob, + }; + use reth_primitives_traits::Block as _; + use reth_provider::{ + ProviderFactory, StorageSettingsCache, + providers::BlockchainProvider, + test_utils::{MockNodeTypesWithDB, create_test_provider_factory_with_chain_spec}, + }; + use tempfile::TempDir; + + /// Paris-activated chain spec on the mainnet genesis; empty blocks keep the genesis root. + fn test_chain_spec() -> Arc { + Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(MAINNET.genesis.clone()) + .paris_activated() + .build(), + ) + } + + /// Empty block at `number` on top of `parent_hash` claiming `state_root`. + fn empty_block(number: u64, parent_hash: B256, state_root: B256) -> RecoveredBlock { + Block { + header: Header { parent_hash, number, state_root, ..Default::default() }, + body: BlockBody::default(), + } + .try_into_recovered() + .expect("empty block recovers without senders") + } + + /// Genesis-initialized provider factory plus proofs storage seeded at block zero. + fn genesis_fixture( + chain_spec: &Arc, + ) -> (ProviderFactory, OpProofsStorage>) { + let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(&factory).expect("genesis state initializes"); + + let path = TempDir::new().expect("temp dir").keep(); + let storage: OpProofsStorage> = + Arc::new(MdbxProofsStorage::new(&path).expect("mdbx proofs storage opens")).into(); + + let layout = if factory.cached_storage_settings().is_v2() { + RethTrieStorageLayout::Packed + } else { + RethTrieStorageLayout::Legacy + }; + let tx = factory.db_ref().tx().expect("read transaction opens"); + InitializationJob::new(storage.clone(), tx, layout) + .run(0, chain_spec.genesis_hash()) + .expect("proofs storage initializes to genesis"); + + (factory, storage) + } + + /// Latest block recorded in the proofs storage window. + fn stored_latest(storage: &OpProofsStorage>) -> NumHash { + storage + .provider_ro() + .expect("read provider opens") + .get_proof_window() + .expect("proof window exists") + .latest + } + + #[test] + fn collector_executes_and_stores_an_empty_block() { + let chain_spec = test_chain_spec(); + let (factory, storage) = genesis_fixture(&chain_spec); + let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + let genesis_root = chain_spec.genesis_header().state_root; + let block = empty_block(1, chain_spec.genesis_hash(), genesis_root); + + collector.execute_and_store_block_updates(&block).expect("empty block stores cleanly"); + assert_eq!(stored_latest(&storage), NumHash::new(1, block.hash())); + } + + #[test] + fn collector_rejects_a_block_beyond_the_stored_window() { + let chain_spec = test_chain_spec(); + let (factory, storage) = genesis_fixture(&chain_spec); + let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + // Parent 2 is past the window (storage only holds genesis), so collection must refuse. + let block = empty_block(3, B256::repeat_byte(0x11), B256::ZERO); + let err = collector.execute_and_store_block_updates(&block).unwrap_err(); + assert!(matches!(err, OpProofsStorageError::MissingParentBlock { .. }), "got {err:?}"); + } + + #[test] + fn collector_rejects_a_block_with_a_wrong_state_root() { + let chain_spec = test_chain_spec(); + let (factory, storage) = genesis_fixture(&chain_spec); + let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + let block = empty_block(1, chain_spec.genesis_hash(), B256::repeat_byte(0xAA)); + let err = collector.execute_and_store_block_updates(&block).unwrap_err(); + assert!(matches!(err, OpProofsStorageError::StateRootMismatch { .. }), "got {err:?}"); + } + + #[test] + fn collector_stores_precomputed_updates_and_unwinds_them() { + let chain_spec = test_chain_spec(); + let (factory, storage) = genesis_fixture(&chain_spec); + let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + let block_hash = B256::repeat_byte(0x01); + let block_ref = + BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, block_hash)); + collector + .store_block_updates( + block_ref, + TrieUpdatesSorted::default(), + HashedPostStateSorted::default(), + ) + .expect("precomputed updates store cleanly"); + assert_eq!(stored_latest(&storage), NumHash::new(1, block_hash)); + + collector.unwind_history(block_ref).expect("stored block unwinds"); + assert_eq!(stored_latest(&storage), NumHash::new(0, chain_spec.genesis_hash())); + } + + #[test] + fn collector_replaces_blocks_after_the_common_ancestor() { + let chain_spec = test_chain_spec(); + let (factory, storage) = genesis_fixture(&chain_spec); + let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + // `replace_updates` refuses a common ancestor at the window's earliest block (genesis + // here), so grow the window to [0, 2] and reorg block 2 on top of block 1. + let block_one = + BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, B256::repeat_byte(1))); + let original = + BlockWithParent::new(block_one.block.hash, NumHash::new(2, B256::repeat_byte(2))); + for block in [block_one, original] { + collector + .store_block_updates( + block, + TrieUpdatesSorted::default(), + HashedPostStateSorted::default(), + ) + .expect("canonical block stores cleanly"); + } + + let replacement = + BlockWithParent::new(block_one.block.hash, NumHash::new(2, B256::repeat_byte(3))); + collector + .unwind_and_store_block_updates(vec![( + replacement, + Arc::new(TrieUpdatesSorted::default()), + Arc::new(HashedPostStateSorted::default()), + )]) + .expect("reorg replacement stores cleanly"); + assert_eq!(stored_latest(&storage), replacement.block); + + // An empty update set is a no-op. + collector.unwind_and_store_block_updates(vec![]).expect("empty replacement is a no-op"); + assert_eq!(stored_latest(&storage), replacement.block); + } +} diff --git a/crates/node/src/proof_history/mod.rs b/crates/node/src/proof_history/mod.rs index 5fda2e3..8dad30f 100644 --- a/crates/node/src/proof_history/mod.rs +++ b/crates/node/src/proof_history/mod.rs @@ -2,6 +2,7 @@ mod config; mod init; +mod live; mod sidecar; mod storage_init; diff --git a/crates/node/src/proof_history/sidecar.rs b/crates/node/src/proof_history/sidecar.rs index e6cc22b..d796e3b 100644 --- a/crates/node/src/proof_history/sidecar.rs +++ b/crates/node/src/proof_history/sidecar.rs @@ -2,6 +2,7 @@ use super::{ config::ProofHistoryConfig, + live::LiveTrieCollector, opt_block, storage_init::{ DelayedProofHistoryStart, ProofHistoryInitializationAction, delayed_proof_history_start, @@ -30,7 +31,6 @@ use reth_node_api::{FullNodeComponents, NodePrimitives, NodeTypes}; use reth_optimism_trie::{ OpProofStoragePruner, OpProofsStorage, OpProofsStorageError, OpProofsStore, api::{OpProofsProviderRO, OpProofsProviderRw}, - live::LiveTrieCollector, }; use reth_storage_api::{ ChainStateBlockReader, ChangeSetReader, StorageChangeSetReader, StorageSettingsCache, diff --git a/justfile b/justfile index c4afc10..bd1b1c7 100644 --- a/justfile +++ b/justfile @@ -1,24 +1,18 @@ toolchain := "1.95.0" fmt_toolchain := "nightly" -# `cargo sort` lists the Alethia crates explicitly so it skips the vendored -# reth-optimism-trie manifest, which keeps upstream's dependency grouping (mirrors the -# vendored-crate clippy exemption below). fmt: rustup toolchain install {{fmt_toolchain}} --component rustfmt && \ cargo +{{fmt_toolchain}} fmt && \ - cargo sort --grouped . bin/alethia-reth crates/* + cargo sort --workspace --grouped fmt-check: rustup toolchain install {{fmt_toolchain}} --component rustfmt && \ cargo +{{fmt_toolchain}} fmt --check -# The vendored reth-optimism-trie crate keeps upstream's documentation style, so it is linted -# without the missing-docs gates that apply to Alethia's own crates. clippy: rustup toolchain install {{toolchain}} && \ - cargo +{{toolchain}} clippy --workspace --exclude reth-optimism-trie --all-features --no-deps -- -D warnings -D missing_docs -D clippy::missing_docs_in_private_items && \ - cargo +{{toolchain}} clippy -p reth-optimism-trie --all-features --no-deps -- -D warnings + cargo +{{toolchain}} clippy --workspace --all-features --no-deps -- -D warnings -D missing_docs -D clippy::missing_docs_in_private_items clippy-fix: rustup toolchain install {{toolchain}} && \ diff --git a/vendor/reth-optimism-trie/Cargo.toml b/vendor/reth-optimism-trie/Cargo.toml deleted file mode 100644 index d3b110d..0000000 --- a/vendor/reth-optimism-trie/Cargo.toml +++ /dev/null @@ -1,110 +0,0 @@ -[package] -name = "reth-optimism-trie" -version = "1.11.3" -edition = "2024" -rust-version.workspace = true -license = "MIT" -homepage = "https://paradigmxyz.github.io/reth" -repository = "https://github.com/paradigmxyz/reth" -description = "Trie node storage for serving proofs in FP window fast" -publish = false - -[features] -test-utils = [ - "reth-codecs/test-utils", - "reth-db/test-utils", - "reth-ethereum-primitives?/test-utils", - "reth-evm/test-utils", - "reth-primitives-traits/test-utils", - "reth-provider/test-utils", - "reth-revm/test-utils", - "reth-tasks/test-utils", - "reth-trie/test-utils", - "reth-trie-common/test-utils", - "reth-trie-db/test-utils", -] -serde-bincode-compat = [ - "reth-trie-common/serde-bincode-compat", - "alloy-consensus/serde-bincode-compat", - "alloy-eips/serde-bincode-compat", - "reth-trie/serde-bincode-compat", - "dep:reth-ethereum-primitives", - "reth-ethereum-primitives?/serde", -] -metrics = [ - "reth-trie/metrics", - "reth-trie-db/metrics", - "dep:reth-metrics", - "dep:metrics", - "dep:eyre", - "reth-evm/metrics", -] - -[dependencies] -reth-codecs.workspace = true -# reth -reth-db = { workspace = true, features = ["mdbx"] } -reth-evm.workspace = true -reth-execution-errors.workspace = true -reth-primitives-traits.workspace = true -reth-provider.workspace = true -reth-revm.workspace = true -reth-tasks.workspace = true -reth-trie = { workspace = true, features = ["serde"] } -reth-trie-common = { workspace = true, features = ["serde"] } -reth-trie-db.workspace = true - -# workaround: reth-trie/serde-bincode-compat activates serde-bincode-compat on -# reth-ethereum-primitives (a transitive dep) without also activating its serde feature, -# breaking compilation. Adding it as an optional dep lets us enable both features together. -reth-ethereum-primitives = { workspace = true, optional = true } - -# `metrics` feature -metrics = { version = "0.24.3", default-features = false, optional = true } -reth-metrics = { workspace = true, features = ["common"], optional = true } -alloy-eips.workspace = true - -# ethereum -alloy-primitives.workspace = true - -# async -tokio = { workspace = true, features = ["sync"] } - -# codec -bincode = { version = "2.0.1", features = ["serde"] } -bytes = { version = "1.11.1", default-features = false } -serde.workspace = true -auto_impl.workspace = true -crossbeam-channel = "0.5.13" -derive_more.workspace = true -eyre = { workspace = true, optional = true } -parking_lot = "0.12.5" -strum = { version = "0.27", default-features = false } -thiserror.workspace = true -tracing.workspace = true - -[dev-dependencies] -alloy-consensus.workspace = true -alloy-genesis.workspace = true -eyre.workspace = true -mockall = "0.14.0" -reth-chainspec.workspace = true -reth-codecs = { workspace = true, features = ["test-utils"] } -reth-db = { workspace = true, features = ["test-utils"] } -# workaround for failing doc test -reth-db-api = { workspace = true, features = ["test-utils"] } -reth-db-common.workspace = true -reth-ethereum-primitives.workspace = true -reth-evm-ethereum.workspace = true -reth-node-api.workspace = true -reth-optimism-trie = { path = ".", features = ["test-utils"] } -reth-provider = { workspace = true, features = ["test-utils"] } -reth-storage-errors.workspace = true -reth-trie = { workspace = true, features = ["test-utils"] } -secp256k1 = { version = "0.31.1", default-features = false, features = ["rand", "std"] } -tempfile.workspace = true -test-case = "3" -tokio = { workspace = true, features = ["test-util", "rt-multi-thread", "macros"] } - -# misc -serial_test = "3" diff --git a/vendor/reth-optimism-trie/LICENSE-MIT b/vendor/reth-optimism-trie/LICENSE-MIT deleted file mode 100644 index 8f4c878..0000000 --- a/vendor/reth-optimism-trie/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2022-2026 Reth Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md b/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md deleted file mode 100644 index a13ee20..0000000 --- a/vendor/reth-optimism-trie/TAIKO-PROVENANCE.md +++ /dev/null @@ -1,26 +0,0 @@ -# reth-optimism-trie provenance - -This directory vendors the `reth-optimism-trie` crate from the Optimism monorepo at -commit `9b802fdb62c96a1cd70b2144ce89979d4e41f4ec` -(, path `rust/op-reth/crates/trie`). - -It retains the crate's original MIT license; the upstream notice is vendored alongside the -sources as `LICENSE-MIT` (copied from `rust/op-reth/LICENSE-MIT` at the same commit). - -## Why vendored - -Alethia-Reth pins reth **v2.4.0**, which builds against the published -`reth-primitives-traits`/`reth-codecs` **0.5.x** crates. The Optimism monorepo pins reth -v2.3.0 (published crates 0.4.x). Cargo cannot unify a 0.4→0.5 split across a git -dependency, so consuming `reth-optimism-trie` as a git dependency would force Alethia's -reth pin to always match Optimism's. Vendoring decouples the two pins. - -## Local changes - -- `Cargo.toml`: rewritten against the Alethia workspace (explicit versions for externals - the workspace does not declare; `publish = false`; upstream `[lints]` table dropped). -- Source adjustments required by the reth v2.3.0 → v2.4.0 API migration are kept minimal - and are visible in this repository's history for this directory. - -The crate is excluded from Alethia's `missing_docs` clippy gate (see `justfile`) to keep -the vendored source close to upstream. diff --git a/vendor/reth-optimism-trie/src/api.rs b/vendor/reth-optimism-trie/src/api.rs deleted file mode 100644 index 9c5f7b7..0000000 --- a/vendor/reth-optimism-trie/src/api.rs +++ /dev/null @@ -1,663 +0,0 @@ -//! Storage API for external storage of intermediary trie nodes. - -use crate::{ - OpProofsStorageResult, - db::{HashedStorageKey, StorageTrieKey}, -}; -use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; -use alloy_primitives::{B256, U256}; -use auto_impl::auto_impl; -use derive_more::{AddAssign, Constructor}; -use reth_primitives_traits::Account; -use reth_trie::{ - hashed_cursor::{HashedCursor, HashedStorageCursor}, - trie_cursor::{TrieCursor, TrieStorageCursor}, -}; -use reth_trie_common::{ - BranchNodeCompact, HashedPostStateSorted, Nibbles, StoredNibbles, updates::TrieUpdatesSorted, -}; -use std::{fmt::Debug, time::Duration}; - -/// Duration metrics for block processing. -#[derive(Debug, Default, Clone)] -pub struct OperationDurations { - /// Total time to process a block (end-to-end) in seconds - pub total_duration_seconds: Duration, - /// Time spent executing the block (EVM) in seconds - pub execution_duration_seconds: Duration, - /// Time spent calculating state root in seconds - pub state_root_duration_seconds: Duration, - /// Time spent writing trie updates to storage in seconds - pub write_duration_seconds: Duration, -} - -/// Diff of trie updates and post state for a block. -#[derive(Debug, Clone, Default)] -pub struct BlockStateDiff { - /// Trie updates for branch nodes - pub sorted_trie_updates: TrieUpdatesSorted, - /// Post state for leaf nodes (accounts and storage) - pub sorted_post_state: HashedPostStateSorted, -} - -impl BlockStateDiff { - /// Extend the [` BlockStateDiff`] from other latest [`BlockStateDiff`] - pub fn extend_ref(&mut self, other: &Self) { - self.sorted_trie_updates.extend_ref_and_sort(&other.sorted_trie_updates); - self.sorted_post_state.extend_ref_and_sort(&other.sorted_post_state); - } -} - -/// The block range covered by the proof window: earliest persisted block and latest persisted -/// block. Both endpoints are inclusive. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ProofWindowRange { - /// Earliest block stored. - pub earliest: NumHash, - /// Latest block stored. - pub latest: NumHash, -} - -/// Counts of trie updates written to storage. -#[derive(Debug, Clone, Default, AddAssign, Constructor, Eq, PartialEq)] -pub struct WriteCounts { - /// Number of account trie updates written - pub account_trie_updates_written_total: u64, - /// Number of storage trie updates written - pub storage_trie_updates_written_total: u64, - /// Number of hashed accounts written - pub hashed_accounts_written_total: u64, - /// Number of hashed storages written - pub hashed_storages_written_total: u64, -} - -/// Provider for interacting with the proofs storage within a transaction. -#[auto_impl(Arc)] -pub trait OpProofsProviderRO: Send + Sync + Debug { - /// Cursor for iterating over trie branches. - type StorageTrieCursor<'tx>: TrieStorageCursor + 'tx - where - Self: 'tx; - - /// Cursor for iterating over account trie branches. - type AccountTrieCursor<'tx>: TrieCursor + 'tx - where - Self: 'tx; - - /// Cursor for iterating over storage leaves. - type StorageCursor<'tx>: HashedStorageCursor + Send + 'tx - where - Self: 'tx; - - /// Cursor for iterating over account leaves. - type AccountHashedCursor<'tx>: HashedCursor + Send + 'tx - where - Self: 'tx; - - /// Get the earliest block number and hash that has been stored. Returns - /// [`crate::OpProofsStorageError::NoBlocksFound`] if the proof window is empty. - fn get_earliest_block(&self) -> OpProofsStorageResult; - - /// Get the latest block number and hash that has been stored. Returns - /// [`crate::OpProofsStorageError::NoBlocksFound`] if the proof window is empty. - fn get_latest_block(&self) -> OpProofsStorageResult; - - /// Get the proof window range — earliest and latest persisted blocks — in a single read. - /// Prefer this over calling [`Self::get_earliest_block`] and [`Self::get_latest_block`] - /// separately when you need both. Returns [`crate::OpProofsStorageError::NoBlocksFound`] if the - /// proof window is empty. - fn get_proof_window(&self) -> OpProofsStorageResult; - - /// Get a trie cursor for the storage backend - fn storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult>; - - /// Get a trie cursor for the account backend - fn account_trie_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult>; - - /// Get a storage cursor for the storage backend - fn storage_hashed_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult>; - - /// Get an account hashed cursor for the storage backend - fn account_hashed_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult>; - - /// Fetch all updates for a given block number. - fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult; -} - -/// Blanket [`OpProofsProviderRO`] for shared references. -impl<'a, T: OpProofsProviderRO + 'a> OpProofsProviderRO for &'a T { - type StorageTrieCursor<'tx> - = T::StorageTrieCursor<'tx> - where - Self: 'tx, - T: 'tx; - type AccountTrieCursor<'tx> - = T::AccountTrieCursor<'tx> - where - Self: 'tx, - T: 'tx; - type StorageCursor<'tx> - = T::StorageCursor<'tx> - where - Self: 'tx, - T: 'tx; - type AccountHashedCursor<'tx> - = T::AccountHashedCursor<'tx> - where - Self: 'tx, - T: 'tx; - - fn get_earliest_block(&self) -> OpProofsStorageResult { - T::get_earliest_block(self) - } - - fn get_latest_block(&self) -> OpProofsStorageResult { - T::get_latest_block(self) - } - - fn get_proof_window(&self) -> OpProofsStorageResult { - T::get_proof_window(self) - } - - fn storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::storage_trie_cursor(self, hashed_address, max_block_number) - } - - fn account_trie_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::account_trie_cursor(self, max_block_number) - } - - fn storage_hashed_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::storage_hashed_cursor(self, hashed_address, max_block_number) - } - - fn account_hashed_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::account_hashed_cursor(self, max_block_number) - } - - fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { - T::fetch_trie_updates(self, block_number) - } -} - -/// Provider for writing to the proofs storage within a transaction. -pub trait OpProofsProviderRw: OpProofsProviderRO { - /// Store trie updates for a block. - fn store_trie_updates( - &self, - block_ref: BlockWithParent, - block_state_diff: BlockStateDiff, - ) -> OpProofsStorageResult; - - /// Store a batch of trie updates for a block. - fn store_trie_updates_batch( - &self, - updates: Vec<(BlockWithParent, BlockStateDiff)>, - ) -> OpProofsStorageResult; - - /// Applies [`BlockStateDiff`] to the earliest state (updating/deleting nodes) and updates the - /// earliest block number. - fn prune_earliest_state( - &self, - new_earliest_block_ref: BlockWithParent, - ) -> OpProofsStorageResult; - - /// Remove account, storage and trie updates from historical storage for all blocks till - /// the specified block (inclusive). - fn unwind_history(&self, to: BlockWithParent) -> OpProofsStorageResult<()>; - - /// Deletes all updates > `latest_common_block` and replaces them with the new updates. - fn replace_updates( - &self, - latest_common_block: BlockNumHash, - blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)>, - ) -> OpProofsStorageResult<()>; - - /// Commit the changes to the database. - /// Consumes the provider. - fn commit(self) -> OpProofsStorageResult<()>; -} - -/// Provider for writing historical records for blocks older than the current window boundary, -/// and for the snapshot RW operations that ride on the same transaction. -/// -/// Unlike [`OpProofsProviderRw::store_trie_updates`], which is strictly append-only (validates -/// parent hash against `latest` and advances `latest`), this provider is designed for -/// **prepend-style** writes that extend the window backward. It does not touch the `latest` -/// marker, and it does not enforce parent-hash ordering against `latest`. -/// -/// The typical call sequence for one snapshot-accelerated backfill step is: -/// ```ignore -/// let bp = storage.backfill_provider()?; -/// bp.update_snapshot(new_anchor, &diff)?; -/// bp.prepend_block(block_ref, diff)?; -/// bp.commit()?; // commits backfill + snapshot writes atomically -/// ``` -pub trait OpProofsBackfillProvider: OpProofsSnapshotProviderRO + OpProofsProviderRO { - /// Write historical changeset and history-bitmap entries for `block_ref`, and move the - /// `earliest` marker to `block_ref.parent`. - /// - /// `diff` contains: - /// - `sorted_trie_updates`: trie node **before-values** for `block_ref.block.number` (i.e. what - /// each changed node looked like *before* the block executed). - /// - `sorted_post_state`: account / storage **before-values** for the same block. - /// - /// The implementation must **not** update the `latest` marker and must **not** - /// validate `diff` against the current `latest` block. - fn prepend_block( - &self, - block_ref: BlockWithParent, - diff: BlockStateDiff, - ) -> OpProofsStorageResult; - - /// Wipe all snapshot tables and the meta row. - fn clear_snapshot(&self) -> OpProofsStorageResult<()>; - - /// Project `diff` onto the snapshot and advance the anchor to - /// `new_anchor` atomically. Trie direction is implicit: - /// `(path, Some(node))` sets, `(path, None)` deletes. Leaf direction - /// follows the same convention: `Some(value)` upserts the leaf, `None` - /// deletes it. - /// - /// The returned [`WriteCounts`] reports per-table row counts (mirrors - /// [`OpProofsBackfillProvider::prepend_block`]'s return shape). - /// - /// Requires status `Ready`; errors with - /// [`OpProofsStorageError::SnapshotUpdateNotReady`](crate::OpProofsStorageError::SnapshotUpdateNotReady) - /// otherwise. - fn update_snapshot( - &self, - new_anchor: BlockNumHash, - diff: &BlockStateDiff, - ) -> OpProofsStorageResult; - - /// Commit the transaction. Consumes the provider. Flushes both the backfill writes - /// and any pending snapshot writes atomically. - fn commit(self) -> OpProofsStorageResult<()>; -} - -/// Factory trait for creating providers to interact with the proofs storage. -#[auto_impl(Arc)] -pub trait OpProofsStore: Send + Sync + Debug { - /// The read-only provider type created by the factory. - type ProviderRO<'a>: OpProofsProviderRO + Clone + 'a - where - Self: 'a; - - /// The read-write provider type created by the factory. - type ProviderRw<'a>: OpProofsProviderRw + 'a - where - Self: 'a; - - /// The initialization provider type created by the factory. - type Initializer<'a>: OpProofsInitProvider + 'a - where - Self: 'a; - - /// Create a read-only provider for interacting with the proofs storage. - fn provider_ro<'a>(&'a self) -> OpProofsStorageResult>; - - /// Create a read-write provider for interacting with the proofs storage. - fn provider_rw<'a>(&'a self) -> OpProofsStorageResult>; - - /// Create an initialization provider for interacting with the proofs storage. - fn initialization_provider<'a>(&'a self) -> OpProofsStorageResult>; -} - -/// Factory extension for stores that support backfill — extending the `earliest` block of -/// the proof window backward. -/// -/// Bundles the trie-state snapshot machinery (snapshot RO / RW / init providers) on the -/// same trait because snapshot is internal infrastructure that accelerates backfill. -#[auto_impl(Arc)] -pub trait OpProofsBackfillStore: OpProofsStore { - /// The backfill provider type created by the factory. - type BackfillProvider<'a>: OpProofsBackfillProvider + 'a - where - Self: 'a; - - /// The snapshot RO provider type created by the factory. - type SnapshotProviderRO<'a>: OpProofsSnapshotProviderRO + Clone + 'a - where - Self: 'a; - - /// Init-time bulk writer (used by [`crate::snapshot::SnapshotInitJob`]). - type SnapshotInitializer<'a>: OpProofsSnapshotInitProvider + 'a - where - Self: 'a; - - /// Open the writer provider for backfill and snapshot RW operations. Backfill writes, - /// snapshot updates, and snapshot teardown all share this single tx and commit - /// atomically through [`OpProofsBackfillProvider::commit`]. - fn backfill_provider<'a>(&'a self) -> OpProofsStorageResult>; - - /// Open a RO snapshot provider. - fn snapshot_provider_ro<'a>(&'a self) -> OpProofsStorageResult>; - - /// Open an init-time snapshot provider. - fn snapshot_initialization_provider<'a>( - &'a self, - ) -> OpProofsStorageResult>; -} - -/// Status of the initial state anchor. -#[derive(Debug, Clone, Copy, Default)] -pub enum InitialStateStatus { - /// Init isn't yet started - #[default] - NotStarted, - /// Init is in progress (some tables may already be populated) - InProgress, - /// Init completed successfully (all tables done + earliest block set) - Completed, -} - -/// Anchor for the initial state. -#[derive(Debug, Clone, Default)] -pub struct InitialStateAnchor { - /// The block for which the initial state is being initialized. None if initialization is not - /// yet started. - pub block: Option, - /// Whether initialization is still running or completed. - pub status: InitialStateStatus, - /// The latest key stored for `AccountTrieHistory`. - pub latest_account_trie_key: Option, - /// The latest key stored for `StorageTrieHistory`. - pub latest_storage_trie_key: Option, - /// The latest key stored for `HashedAccountHistory`. - pub latest_hashed_account_key: Option, - /// The latest key stored for `HashedStorageHistory`. - pub latest_hashed_storage_key: Option, -} - -/// Trait for storing and retrieving the initial state anchor. -pub trait OpProofsInitProvider: Send + Sync + Debug { - /// Read the current anchor. - fn initial_state_anchor(&self) -> OpProofsStorageResult; - - /// Create the anchor if it doesn't exist. - /// Returns `Err` if an anchor already exists (prevents accidental overwrite). - fn set_initial_state_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()>; - - /// Store a batch of account trie branches. Used for saving existing state. For live state - /// capture, use [`store_trie_updates`](OpProofsProviderRw::store_trie_updates). - fn store_account_branches( - &self, - account_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()>; - - /// Store a batch of storage trie branches. Used for saving existing state. - fn store_storage_branches( - &self, - hashed_address: B256, - storage_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()>; - - /// Store a batch of account trie leaf nodes. Used for saving existing state. - fn store_hashed_accounts( - &self, - accounts: Vec<(B256, Option)>, - ) -> OpProofsStorageResult<()>; - - /// Store a batch of storage trie leaf nodes. Used for saving existing state. - fn store_hashed_storages( - &self, - hashed_address: B256, - storages: Vec<(B256, U256)>, - ) -> OpProofsStorageResult<()>; - - /// Commit the initial state - mark the anchor as completed and also set the earliest block - /// number to anchor. - fn commit_initial_state(&self) -> OpProofsStorageResult; - - /// Commit the changes to the database. - /// Consumes the provider. - fn commit(self) -> OpProofsStorageResult<()>; -} - -/// Read access to the trie-state snapshot. -/// -/// Cursors read the snapshot tables directly (no history-bitmap lookups) and -/// are only valid at [`Self::snapshot_anchor`]. -#[auto_impl(Arc)] -pub trait OpProofsSnapshotProviderRO: OpProofsProviderRO { - /// Cursor over the snapshot's account trie table. - type SnapshotAccountTrieCursor<'tx>: TrieCursor + 'tx - where - Self: 'tx; - - /// Cursor over the snapshot's storage trie table. - type SnapshotStorageTrieCursor<'tx>: TrieStorageCursor + 'tx - where - Self: 'tx; - - /// Cursor over the snapshot's hashed-account leaf table. - type SnapshotHashedAccountCursor<'tx>: HashedCursor + Send + 'tx - where - Self: 'tx; - - /// Cursor over the snapshot's hashed-storage leaf table. - type SnapshotHashedStorageCursor<'tx>: HashedStorageCursor + Send + 'tx - where - Self: 'tx; - - /// Anchor block of a `Ready` snapshot. Errors with - /// [`OpProofsStorageError::SnapshotNotReady`](crate::OpProofsStorageError::SnapshotNotReady) - /// otherwise. - fn snapshot_anchor(&self) -> OpProofsStorageResult; - - /// Open a cursor over the snapshot's account trie table. - fn snapshot_account_trie_cursor<'tx>( - &self, - ) -> OpProofsStorageResult>; - - /// Open a cursor over the snapshot's storage trie table for `hashed_address`. - fn snapshot_storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - ) -> OpProofsStorageResult>; - - /// Open a cursor over the snapshot's hashed-account leaf table. - fn snapshot_hashed_account_cursor<'tx>( - &self, - ) -> OpProofsStorageResult>; - - /// Open a cursor over the snapshot's hashed-storage leaf table for `hashed_address`. - fn snapshot_hashed_storage_cursor<'tx>( - &self, - hashed_address: B256, - ) -> OpProofsStorageResult>; -} - -/// Blanket [`OpProofsSnapshotProviderRO`] for shared references — mirrors the -/// equivalent impl on [`OpProofsProviderRO`] above. Lets callers pass `&bp` to -/// owning cursor factories (e.g., [`crate::SnapshotTrieCursorFactory::new`]) -/// without wrapping in [`std::sync::Arc`]. -impl<'a, T: OpProofsSnapshotProviderRO + 'a> OpProofsSnapshotProviderRO for &'a T { - type SnapshotAccountTrieCursor<'tx> - = T::SnapshotAccountTrieCursor<'tx> - where - Self: 'tx, - T: 'tx; - type SnapshotStorageTrieCursor<'tx> - = T::SnapshotStorageTrieCursor<'tx> - where - Self: 'tx, - T: 'tx; - type SnapshotHashedAccountCursor<'tx> - = T::SnapshotHashedAccountCursor<'tx> - where - Self: 'tx, - T: 'tx; - type SnapshotHashedStorageCursor<'tx> - = T::SnapshotHashedStorageCursor<'tx> - where - Self: 'tx, - T: 'tx; - - fn snapshot_anchor(&self) -> OpProofsStorageResult { - T::snapshot_anchor(self) - } - - fn snapshot_account_trie_cursor<'tx>( - &self, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::snapshot_account_trie_cursor(self) - } - - fn snapshot_storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::snapshot_storage_trie_cursor(self, hashed_address) - } - - fn snapshot_hashed_account_cursor<'tx>( - &self, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::snapshot_hashed_account_cursor(self) - } - - fn snapshot_hashed_storage_cursor<'tx>( - &self, - hashed_address: B256, - ) -> OpProofsStorageResult> - where - 'a: 'tx, - { - T::snapshot_hashed_storage_cursor(self, hashed_address) - } -} - -/// Lifecycle of the snapshot init job. Mirrors [`InitialStateStatus`]. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub enum SnapshotInitStatus { - /// Init has never run. - #[default] - NotStarted, - /// Init is running; snapshot tables may be partially populated. - InProgress, - /// Init completed. Use [`OpProofsSnapshotProviderRO::snapshot_anchor`] to read. - Completed, -} - -/// Status + anchor block + resume keys for the snapshot init job. Mirrors -/// [`InitialStateAnchor`]'s shape. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct SnapshotInitAnchor { - /// Anchor block, or `None` if init has never run. - pub block: Option, - /// Lifecycle status. - pub status: SnapshotInitStatus, - /// Last key in [`crate::db::V2AccountsTrieSnapshot`]; resumes the account-trie phase. - pub last_account_trie_key: Option, - /// Last entry in [`crate::db::V2StoragesTrieSnapshot`]; resumes the storage-trie phase. - pub last_storage_trie_key: Option, - /// Last key in [`crate::db::V2HashedAccountsSnapshot`]; resumes the hashed-accounts phase. - pub last_hashed_account_key: Option, - /// Last entry in [`crate::db::V2HashedStoragesSnapshot`]; resumes the - /// hashed-storages phase. - pub last_hashed_storage_key: Option, -} - -/// Init-time read + write surface for the trie-state snapshot. Mirrors -/// [`OpProofsInitProvider`]'s role. Driven by [`crate::snapshot::SnapshotInitJob`] -/// over short chunked rw-tx; meta stays `Building` mid-init so a crash -/// resumes from [`Self::snapshot_init_anchor`]. -pub trait OpProofsSnapshotInitProvider: Send + Sync + Debug { - /// Read status + anchor block + resume keys in one call. - fn snapshot_init_anchor(&self) -> OpProofsStorageResult; - - /// Plant the meta row at `anchor` with status `Building`. Errors if a - /// meta row already exists — call - /// [`OpProofsBackfillProvider::clear_snapshot`] first to rebuild. - fn set_snapshot_init_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()>; - - /// Append a chunk to [`crate::db::V2AccountsTrieSnapshot`]. Entries must - /// be sorted and strictly greater than the table's current last key. - fn store_account_trie_snapshot_branches( - &self, - entries: Vec<(StoredNibbles, BranchNodeCompact)>, - ) -> OpProofsStorageResult<()>; - - /// Append a chunk to [`crate::db::V2StoragesTrieSnapshot`]. Entries must - /// be sorted and strictly greater than the table's current last entry. - fn store_storage_trie_snapshot_branches( - &self, - hashed_address: B256, - storage_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()>; - - /// Append a chunk to [`crate::db::V2HashedAccountsSnapshot`]. Entries must - /// be sorted by `hashed_address` and strictly greater than the table's - /// current last key. - fn store_hashed_accounts_snapshot( - &self, - entries: Vec<(B256, Account)>, - ) -> OpProofsStorageResult<()>; - - /// Append a chunk to [`crate::db::V2HashedStoragesSnapshot`] for - /// `hashed_address`. Entries must be sorted by storage key and strictly - /// greater than the table's current last entry for this address. - fn store_hashed_storages_snapshot( - &self, - hashed_address: B256, - entries: Vec<(B256, U256)>, - ) -> OpProofsStorageResult<()>; - - /// Transition the meta row from `Building` to `Ready`. Errors if no meta - /// exists or if it isn't `Building`. - fn commit_snapshot(&self) -> OpProofsStorageResult<()>; - - /// Commit the transaction. Consumes the provider. - fn commit(self) -> OpProofsStorageResult<()>; -} diff --git a/vendor/reth-optimism-trie/src/backfill/changesets.rs b/vendor/reth-optimism-trie/src/backfill/changesets.rs deleted file mode 100644 index c102053..0000000 --- a/vendor/reth-optimism-trie/src/backfill/changesets.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Per-block backfill diff computation. See [`compute_block_backfill_diff`]. -//! -//! Equivalent to `reth_trie_db::changesets::compute_block_trie_changesets_inner`, -//! but reads the trie via caller-supplied cursor factories — typically a -//! history-aware factory at `max_block_number = N` over the **op-reth proofs -//! storage** (making per-block cost scale with the block's diff size, not the -//! tail size of changesets between N and the DB tip), or a snapshot-backed -//! factory when a [`SnapshotStatus::Ready`](crate::db::SnapshotStatus::Ready) -//! snapshot is available at the right anchor. - -use crate::backfill::error::BackfillError; -use alloy_primitives::BlockNumber; -use reth_primitives_traits::AlloyBlockHeader; -use reth_provider::{ - BlockNumReader, ChangeSetReader, DBProvider, HeaderProvider, ProviderError, - StorageChangeSetReader, StorageSettingsCache, -}; -use reth_trie::{ - StateRoot, - hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory}, - trie_cursor::TrieCursorFactory, -}; -use reth_trie_common::{HashedPostStateSorted, updates::TrieUpdatesSorted}; -use reth_trie_db::from_reverts_auto; - -/// Compute the backfill diff for `block_number`: -/// - `HashedPostStateSorted` — per-block leaf revert (state before block N ran), reused as -/// `BlockStateDiff::sorted_post_state`. -/// - `TrieUpdatesSorted` — trie-node before-values for paths block N touched, written into the four -/// `V2*TrieChangeSets` tables by `prepend_block`. -/// -/// The trie + hashed cursor factories must read trie state **at the start of -/// this iteration** (`earliest == block_number`). For the history-aware path -/// this means cursors built with `max_block_number = block_number`; for the -/// snapshot path the snapshot's anchor must equal `block_number`. -pub(super) fn compute_block_backfill_diff( - reth_provider: &P, - trie_cursor_factory: T, - hashed_cursor_factory: H, - block_number: BlockNumber, -) -> Result<(TrieUpdatesSorted, HashedPostStateSorted), BackfillError> -where - P: ChangeSetReader - + StorageChangeSetReader - + BlockNumReader - + DBProvider - + HeaderProvider - + StorageSettingsCache, - T: TrieCursorFactory + Clone, - H: HashedCursorFactory + Clone, -{ - // Per-block leaf revert: doubles as `post_state` for `prepend_block` and - // as the state overlay for the trie@N-1 reconstruction below. - let individual_state_revert = from_reverts_auto(reth_provider, block_number..=block_number)?; - - // Reth prunes the per-block changesets together with the body. An empty revert means either: - // (a) the block genuinely changed nothing — header[N].state_root == header[N-1].state_root, or - // (b) reth has pruned the changesets we need. - // Fail fast on (b) with an accurate error; (a) is a legal no-op and falls through. - if individual_state_revert.is_empty() && block_number > 0 { - let state_root_n = reth_provider - .header_by_number(block_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))? - .state_root(); - let state_root_prev = reth_provider - .header_by_number(block_number - 1)? - .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))? - .state_root(); - if state_root_n != state_root_prev { - return Err(BackfillError::BlockBodyPruned(block_number)); - } - } - - let trie_changesets = compute_trie_changesets_against_proofs( - trie_cursor_factory, - hashed_cursor_factory, - &individual_state_revert, - )?; - Ok((trie_changesets, individual_state_revert)) -} - -fn compute_trie_changesets_against_proofs( - trie_cursor_factory: T, - hashed_cursor_factory: H, - individual_state_revert: &HashedPostStateSorted, -) -> Result -where - T: TrieCursorFactory + Clone, - H: HashedCursorFactory + Clone, -{ - // Apply block N's leaf revert as a state overlay on top of the supplied - // trie cursor at max=N. The returned `TrieUpdates` describes trie@N-1 - // relative to the cursor's view at max=N for the paths block N touched — - // which is the changeset we want (`Some` for modified/destroyed, `None` - // for newly created branches). - let prefix_sets = individual_state_revert.construct_prefix_sets().freeze(); - let (_, trie_updates) = StateRoot::new( - trie_cursor_factory, - HashedPostStateCursorFactory::new(hashed_cursor_factory, individual_state_revert), - ) - .with_prefix_sets(prefix_sets) - .root_with_updates()?; - Ok(trie_updates.into_sorted()) -} diff --git a/vendor/reth-optimism-trie/src/backfill/error.rs b/vendor/reth-optimism-trie/src/backfill/error.rs deleted file mode 100644 index 9b1b6d7..0000000 --- a/vendor/reth-optimism-trie/src/backfill/error.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Error type for backfill operations. - -use crate::{OpProofsStorageError, snapshot::SnapshotError}; -use alloy_eips::BlockNumHash; -use alloy_primitives::B256; -use reth_execution_errors::StateRootError; -use reth_provider::ProviderError; - -/// Error type for backfill operations. -#[derive(Debug, thiserror::Error)] -pub enum BackfillError { - /// Error bubbled up from proofs storage operations. - #[error(transparent)] - Storage(#[from] OpProofsStorageError), - /// Error from reth provider operations. - #[error(transparent)] - Provider(#[from] ProviderError), - /// State root computation failed. - #[error(transparent)] - StateRoot(#[from] StateRootError), - /// Error from snapshot-init / state operations during snapshot-accelerated backfill. - #[error(transparent)] - Snapshot(#[from] SnapshotError), - /// Reth has pruned data needed to recompute the per-block diff. - /// - /// Backfill reconstructs each block's state delta from MDBX changesets (via - /// `from_reverts_auto`). When reth prunes a block it typically prunes the body AND the - /// account/storage changesets together; the empty revert that comes back would otherwise - /// surface as a misleading [`Self::StateRootMismatch`]. Detected when the per-block revert is - /// empty *and* the block actually changed state (`header_n.state_root != - /// header_prev.state_root`). - #[error( - "Block #{0} has been pruned by reth (changesets missing); cannot backfill. \ - Re-sync reth without history pruning, or reduce the backfill window." - )] - BlockBodyPruned(u64), - /// Computed state root does not match the expected root from the header. - #[error( - "State root mismatch at block {block_number}: computed {computed:?}, expected {expected:?}" - )] - StateRootMismatch { - /// Block number being validated (the block whose before-state is being checked). - block_number: u64, - /// Computed root from the proofs storage overlay. - computed: B256, - /// Expected root from reth's block header. - expected: B256, - }, - /// Snapshot-accelerated backfill requires the snapshot anchor to equal - /// the proofs window's current `earliest`. - #[error( - "snapshot anchor {found:?} does not match proofs `earliest` {expected:?}; \ - drop the snapshot and re-init at {expected:?} or run backfill without --use-snapshot" - )] - SnapshotAnchorMismatch { - /// Current `earliest` block of the proofs window. - expected: BlockNumHash, - /// Anchor block of the existing snapshot. - found: BlockNumHash, - }, -} diff --git a/vendor/reth-optimism-trie/src/backfill/job.rs b/vendor/reth-optimism-trie/src/backfill/job.rs deleted file mode 100644 index 5ee0972..0000000 --- a/vendor/reth-optimism-trie/src/backfill/job.rs +++ /dev/null @@ -1,496 +0,0 @@ -//! [`BackfillJob`] implementation. - -use super::{changesets::compute_block_backfill_diff, error::BackfillError}; -use crate::{ - BlockStateDiff, OpProofsBackfillProvider, OpProofsBackfillStore, - OpProofsHashedAccountCursorFactory, OpProofsProviderRO, OpProofsSnapshotInitProvider, - OpProofsSnapshotProviderRO, OpProofsTrieCursorFactory, SnapshotHashedCursorFactory, - SnapshotInitJob, SnapshotInitStatus, SnapshotTrieCursorFactory, proof::DatabaseStateRoot, -}; -use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; -use alloy_primitives::BlockNumber; -use reth_primitives_traits::AlloyBlockHeader; -use reth_provider::{ - BlockHashReader, BlockNumReader, ChangeSetReader, DBProvider, HeaderProvider, ProviderError, - StageCheckpointReader, StorageChangeSetReader, StorageSettingsCache, -}; -use reth_trie::{HashedPostState, StateRoot, hashed_cursor::HashedPostStateCursorFactory}; -use std::time::{Duration, Instant}; -use tracing::info; - -/// How often to emit a progress line during a long backfill, measured in -/// blocks committed. -const LOG_EVERY: u64 = 1_000; - -/// Run a fallible closure and return its value alongside the wall-clock -/// duration on success. Errors are propagated; the duration is not returned -/// when the closure fails. -#[inline] -fn timed(f: F) -> Result<(R, Duration), E> -where - F: FnOnce() -> Result, -{ - let start = Instant::now(); - let r = f()?; - Ok((r, start.elapsed())) -} - -/// Cumulative time spent in each phase of [`BackfillJob::backfill_block`]. -/// Reported alongside the progress line so operators can see which phase -/// dominates a slow backfill. -#[derive(Debug, Default, Clone, Copy)] -struct PhaseTimings { - compute: Duration, - prepend: Duration, - validate: Duration, - commit: Duration, -} - -impl PhaseTimings { - fn add(&mut self, other: Self) { - self.compute += other.compute; - self.prepend += other.prepend; - self.validate += other.validate; - self.commit += other.commit; - } - - /// Per-block average. `done` must be > 0. - fn averages(&self, done: u64) -> Self { - let n = done as u32; - Self { - compute: self.compute / n, - prepend: self.prepend / n, - validate: self.validate / n, - commit: self.commit / n, - } - } -} - -/// Default number of blocks written per MDBX transaction. -/// -/// `25` measured ~2.6× throughput on a 1.296M-block op-mainnet backfill and sits at the sweet -/// spot on the K sweep: bitmap + commit amortization has done most of its work, but the open -/// tx's dirty-page pressure hasn't yet started slowing cursor reads. Tune via -/// [`BackfillJob::with_batch_size`] / `--proofs-history.backfill-batch-size` — see that flag -/// for the throughput / memory / restart-loss trade-offs. -pub const DEFAULT_BACKFILL_BATCH_SIZE: usize = 25; - -/// Backfill job for proofs storage. -#[derive(Debug)] -pub struct BackfillJob { - provider: P, - storage: S, - /// Number of blocks written per MDBX transaction. Amortizes commit cost across blocks at the - /// price of restart granularity. See [`DEFAULT_BACKFILL_BATCH_SIZE`]. - batch_size: usize, -} - -impl BackfillJob { - /// Create a new backfill job using [`DEFAULT_BACKFILL_BATCH_SIZE`]. - pub const fn new(provider: P, storage: S) -> Self { - Self { provider, storage, batch_size: DEFAULT_BACKFILL_BATCH_SIZE } - } - - /// Override the batch size (number of blocks per MDBX transaction). - /// - /// `batch_size` is clamped to `1` if zero — backfill needs to make per-block progress. - pub fn with_batch_size(mut self, batch_size: usize) -> Self { - self.batch_size = batch_size.max(1); - self - } -} - -impl BackfillJob -where - P: DBProvider - + StageCheckpointReader - + ChangeSetReader - + StorageChangeSetReader - + BlockNumReader - + BlockHashReader - + HeaderProvider - + StorageSettingsCache - + Send, - S: OpProofsBackfillStore + Send, -{ - /// Backfill proofs data down to `target_earliest_block`. - /// - /// Extends the stored proof window from `[earliest, latest]` backward to - /// `[target_earliest_block, latest]`. Blocks are written in batches of - /// [`Self::with_batch_size`] (default [`DEFAULT_BACKFILL_BATCH_SIZE`]) — each batch holds - /// one MDBX transaction open across K blocks and commits once, amortizing fsync cost. - /// A crash mid-batch loses at most K blocks of progress; on restart, resume from the - /// current `earliest`. - /// - /// Returns immediately if `target_earliest_block >= current earliest`. - pub fn run(&self, target_earliest_block: u64) -> Result<(), BackfillError> { - let current_earliest = self.storage.provider_ro()?.get_earliest_block()?; - - if target_earliest_block >= current_earliest.number { - return Ok(()); - } - self.drive_batched_loop(current_earliest, target_earliest_block, "standard", |bp, n| { - self.backfill_block(bp, n) - }) - } - - /// Shared batched per-block driver, used by [`Self::run`] and [`Self::run_with_snapshot`]. - /// - /// Holds one RW backfill provider open across `batch_size` blocks, dispatches per-block work - /// to the supplied closure (which uses the same `bp` for reads + writes, so MDBX same-tx - /// visibility makes in-flight writes from earlier blocks of the batch visible), and commits - /// once per batch. Mirrors the previous per-block `drive_loop` shape but with the bp opened - /// at the batch boundary instead of per call. - fn drive_batched_loop( - &self, - current_earliest: NumHash, - target_earliest_block: u64, - kind: &'static str, - mut process_block: F, - ) -> Result<(), BackfillError> - where - F: FnMut(&S::BackfillProvider<'_>, BlockNumber) -> Result, - { - let total = current_earliest.number - target_earliest_block; - let start = Instant::now(); - let mut phase_totals = PhaseTimings::default(); - info!( - target: "trie::backfill::job", - from = current_earliest.number, - to = target_earliest_block, - total, - batch_size = self.batch_size, - kind, - "Starting proofs backfill" - ); - - let mut next_block = current_earliest.number; - while next_block > target_earliest_block { - let batch_end = - next_block.saturating_sub(self.batch_size as u64).max(target_earliest_block); - let batch_low = batch_end + 1; - let batch_high = next_block; - - let bp = self.storage.backfill_provider()?; - - for block_number in (batch_low..=batch_high).rev() { - let timings = process_block(&bp, block_number)?; - phase_totals.add(timings); - - let done = current_earliest.number - block_number + 1; - let is_final = block_number == target_earliest_block + 1; - if done.is_multiple_of(LOG_EVERY) || is_final { - self.log_progress(start, done, total, &phase_totals); - } - } - - let (_, commit_duration) = timed(|| bp.commit())?; - // Amortize the batch's commit time across the blocks in it so per-block averages - // remain comparable across batch sizes. Note: intermediate `log_progress` lines - // fire before this add (inside the inner loop), so per-batch progress lines - // under-report `avg_commit` by one batch. The final summary logged after the - // outer loop exits is exact. - phase_totals.commit += commit_duration; - next_block = batch_end; - } - - let final_avg = phase_totals.averages(total); - info!( - target: "trie::backfill::job", - blocks = total, - elapsed = ?start.elapsed(), - avg_compute = ?final_avg.compute, - avg_prepend = ?final_avg.prepend, - avg_validate = ?final_avg.validate, - avg_commit = ?final_avg.commit, - kind, - "Proofs backfill complete" - ); - - Ok(()) - } - - /// Per-block work for the standard backfill path. Called inside [`Self::drive_batched_loop`] - /// with the batch's shared open RW provider; reads in [`Self::compute_diff_via`] go through - /// it so same-tx writes from earlier iterations are visible. - fn backfill_block( - &self, - bp: &S::BackfillProvider<'_>, - block_number: BlockNumber, - ) -> Result { - let block_ref = self.resolve_block_ref(block_number)?; - let (diff, compute) = self.compute_diff_via(bp, block_number)?; - let (_, prepend) = timed(|| bp.prepend_block(block_ref, diff))?; - let validate = self.validate_state_root(bp, block_number)?; - Ok(PhaseTimings { compute, prepend, validate, commit: Duration::ZERO }) - } - - fn log_progress(&self, start: Instant, done: u64, total: u64, phase_totals: &PhaseTimings) { - let elapsed_secs = start.elapsed().as_secs_f64(); - let blocks_per_sec = - if elapsed_secs.is_normal() { done as f64 / elapsed_secs } else { 0.0 }; - let eta_secs = if blocks_per_sec.is_normal() && blocks_per_sec > 0.0 { - (total - done) as f64 / blocks_per_sec - } else { - 0.0 - }; - let progress_pct = (done as f64 / total as f64) * 100.0; - let avg = phase_totals.averages(done); - info!( - target: "trie::backfill::job", - done, - total, - avg_compute = ?avg.compute, - avg_prepend = ?avg.prepend, - avg_validate = ?avg.validate, - avg_commit = ?avg.commit, - "progress: {progress_pct:.2}% ({blocks_per_sec:.1} blk/s, ETA {eta_secs:.0}s)" - ); - } - - /// Resolve the `(block, parent)` hashes for `block_number` from reth. - fn resolve_block_ref( - &self, - block_number: BlockNumber, - ) -> Result { - let block_hash = self - .provider - .block_hash(block_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; - let parent_hash = self - .provider - .block_hash(block_number - 1)? - .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))?; - Ok(BlockWithParent { block: NumHash::new(block_number, block_hash), parent: parent_hash }) - } - - /// Compute the per-block backfill diff (trie node + leaf before-values) and time the call. - /// - /// The batched [`Self::run`] passes the open RW backfill provider so cursors see writes - /// made earlier in the same MDBX transaction (the prepended blocks of this batch). - /// MDBX same-tx visibility means each `compute_diff_via(&bp, N)` sees the in-flight - /// `prepend_block` writes for blocks > N. - fn compute_diff_via( - &self, - proofs_ro: &RO, - block_number: BlockNumber, - ) -> Result<(BlockStateDiff, Duration), BackfillError> - where - RO: OpProofsProviderRO, - { - timed(|| { - // History-aware cursors at `max_block_number = block_number`. - let trie_factory = OpProofsTrieCursorFactory::new(proofs_ro, block_number); - let hashed_factory = OpProofsHashedAccountCursorFactory::new(proofs_ro, block_number); - let (trie_updates, post_state) = compute_block_backfill_diff( - &self.provider, - trie_factory, - hashed_factory, - block_number, - )?; - Ok(BlockStateDiff { sorted_trie_updates: trie_updates, sorted_post_state: post_state }) - }) - } - - /// Validate the just-prepended diff by computing a full state root at - /// `block_number - 1` against the open backfill provider (which sees its - /// own uncommitted writes) and comparing to the reth header. - fn validate_state_root( - &self, - bp: &BP, - block_number: BlockNumber, - ) -> Result - where - BP: OpProofsProviderRO, - { - let (_, elapsed) = timed(|| -> Result<(), BackfillError> { - let expected_root = self - .provider - .header_by_number(block_number - 1)? - .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))? - .state_root(); - let computed_root = - StateRoot::overlay_root(bp, block_number - 1, HashedPostState::default())?; - if computed_root != expected_root { - return Err(BackfillError::StateRootMismatch { - block_number, - computed: computed_root, - expected: expected_root, - }); - } - Ok(()) - })?; - Ok(elapsed) - } -} - -impl BackfillJob -where - P: DBProvider - + StageCheckpointReader - + ChangeSetReader - + StorageChangeSetReader - + BlockNumReader - + BlockHashReader - + HeaderProvider - + StorageSettingsCache - + Send - + Sync, - S: OpProofsBackfillStore + Clone + Send, -{ - /// Backfill using a `Ready` snapshot to accelerate per-block reads. - /// - /// Mirrors [`Self::run`] but reads trie state at each iteration's - /// `block_number` from the snapshot tables instead of the V2 merge-walk, - /// and advances the snapshot anchor atomically with each `prepend_block` - /// (so the snapshot stays in sync with the moving `earliest`). - /// - /// **Snapshot preconditions** (handled internally by `ensure_snapshot_ready`): - /// - If no snapshot exists, runs [`SnapshotInitJob`] at the current `earliest` and then - /// proceeds. - /// - If a `Ready` snapshot exists at the current `earliest`, proceeds. - /// - Otherwise (snapshot at a different anchor, or partial build in progress at a different - /// anchor), errors out and asks the caller to drop or finish the snapshot. - pub fn run_with_snapshot(&self, target_earliest_block: u64) -> Result<(), BackfillError> { - let current_earliest = self.storage.provider_ro()?.get_earliest_block()?; - if target_earliest_block >= current_earliest.number { - return Ok(()); - } - self.ensure_snapshot_ready(current_earliest)?; - self.drive_batched_loop( - current_earliest, - target_earliest_block, - "snapshot-accelerated", - |bp, n| self.backfill_block_with_snapshot(bp, n), - ) - } - - /// Ensure a `Ready` snapshot exists at `current_earliest`. - /// - /// - `Completed` at matching anchor → ok, return. - /// - `Completed` at a different anchor → [`BackfillError::SnapshotAnchorMismatch`]. - /// - `NotStarted` / `InProgress` → delegate to [`SnapshotInitJob`] (which handles fresh build - /// and crash-resume; errors on drift). - fn ensure_snapshot_ready(&self, current_earliest: NumHash) -> Result<(), BackfillError> { - let target = BlockNumHash::new(current_earliest.number, current_earliest.hash); - let init_anchor = - self.storage.snapshot_initialization_provider()?.snapshot_init_anchor()?; - - if let (SnapshotInitStatus::Completed, Some(existing)) = - (init_anchor.status, init_anchor.block) - { - if existing == target { - return Ok(()); - } - return Err(BackfillError::SnapshotAnchorMismatch { expected: target, found: existing }); - } - - info!( - target: "trie::backfill::job", - anchor = ?target, - status = ?init_anchor.status, - "Bootstrapping snapshot before backfill" - ); - - SnapshotInitJob::new(&self.provider, self.storage.clone()).run(current_earliest.number)?; - Ok(()) - } - - /// Per-block work for the snapshot-accelerated path. Reads via the open RW provider's - /// snapshot cursors (which see in-flight `update_snapshot` writes via MDBX same-tx - /// visibility), then advances snapshot anchor + proofs window in the same tx, then - /// validates against reth's header at `E-1`. - fn backfill_block_with_snapshot( - &self, - bp: &S::BackfillProvider<'_>, - block_number: BlockNumber, - ) -> Result { - let block_ref = self.resolve_block_ref(block_number)?; - let (diff, compute) = self.compute_diff_with_snapshot_via(bp, block_number)?; - - // After this iteration the proofs window's earliest moves from E to E-1, so the - // snapshot anchor advances to the parent block. - let new_anchor = BlockNumHash::new(block_number - 1, block_ref.parent); - - // Advance snapshot anchor + proofs window in the same tx. - let (_, prepend) = timed(|| -> Result<(), BackfillError> { - bp.update_snapshot(new_anchor, &diff)?; - bp.prepend_block(block_ref, diff)?; - Ok(()) - })?; - - let validate = self.validate_state_root_with_snapshot(bp, block_number)?; - Ok(PhaseTimings { compute, prepend, validate, commit: Duration::ZERO }) - } - - /// Compute the per-block backfill diff using snapshot trie + leaf cursors. - /// - /// The batched [`Self::run_with_snapshot`] passes the open RW backfill provider - /// (which implements [`OpProofsSnapshotProviderRO`] via the trait hierarchy), so cursors - /// see the in-flight `update_snapshot` writes from earlier blocks in the same MDBX - /// transaction. - fn compute_diff_with_snapshot_via( - &self, - sp: &SP, - block_number: BlockNumber, - ) -> Result<(BlockStateDiff, Duration), BackfillError> - where - SP: OpProofsSnapshotProviderRO, - { - // `block_number` is unused on the snapshot path: the snapshot reflects state at its - // anchor, which the caller guaranteed equals `block_number` via `ensure_snapshot_ready` - // (or via the prior iteration's `update_snapshot` advancing the anchor). - let _ = block_number; - timed(|| { - let trie_factory = SnapshotTrieCursorFactory::new(sp); - let hashed_factory = SnapshotHashedCursorFactory::new(sp); - let (sorted_trie_updates, sorted_post_state) = compute_block_backfill_diff( - &self.provider, - trie_factory, - hashed_factory, - block_number, - )?; - Ok(BlockStateDiff { sorted_trie_updates, sorted_post_state }) - }) - } - - /// Validate the just-prepended state at `block_number - 1` using snapshot - /// cursors (the snapshot has been updated to anchor at `E-1` within the - /// same tx, so its reads reflect the new state). - fn validate_state_root_with_snapshot( - &self, - bp: &BP, - block_number: BlockNumber, - ) -> Result - where - BP: OpProofsSnapshotProviderRO, - { - let (_, elapsed) = timed(|| -> Result<(), BackfillError> { - let expected_root = self - .provider - .header_by_number(block_number - 1)? - .ok_or_else(|| ProviderError::HeaderNotFound((block_number - 1).into()))? - .state_root(); - - let state_sorted = HashedPostState::default().into_sorted(); - let computed_root = StateRoot::new( - SnapshotTrieCursorFactory::new(bp), - HashedPostStateCursorFactory::new( - SnapshotHashedCursorFactory::new(bp), - &state_sorted, - ), - ) - .root()?; - - if computed_root != expected_root { - return Err(BackfillError::StateRootMismatch { - block_number, - computed: computed_root, - expected: expected_root, - }); - } - Ok(()) - })?; - Ok(elapsed) - } -} diff --git a/vendor/reth-optimism-trie/src/backfill/mod.rs b/vendor/reth-optimism-trie/src/backfill/mod.rs deleted file mode 100644 index e459a90..0000000 --- a/vendor/reth-optimism-trie/src/backfill/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Backfill: extend the proofs window from `[earliest, latest]` to -//! `[target_earliest, latest]` where `target_earliest < earliest`. See -//! [`BackfillJob`] for the per-step implementation. -//! -//! `earliest` is a **base-state boundary**, not "oldest block with its own -//! changeset rows". To move it from `E` to `E-1` the job materializes block -//! `E`'s historical records (changesets + history-bitmap entries) and then -//! flips the marker — mirroring prune in reverse. -//! -//! ## Invariants per step -//! -//! - The proofs current-state tables are untouched; only history is written. -//! - `earliest` decreases by exactly one per successful step. -//! - Each step commits atomically, so a crash mid-backfill resumes cleanly from the current -//! `earliest`. - -mod changesets; -mod error; -mod job; - -#[cfg(test)] -mod tests; - -pub use error::BackfillError; -pub use job::{BackfillJob, DEFAULT_BACKFILL_BATCH_SIZE}; diff --git a/vendor/reth-optimism-trie/src/backfill/tests.rs b/vendor/reth-optimism-trie/src/backfill/tests.rs deleted file mode 100644 index a0df442..0000000 --- a/vendor/reth-optimism-trie/src/backfill/tests.rs +++ /dev/null @@ -1,821 +0,0 @@ -//! Integration tests for [`BackfillJob`]. -//! -//! Chain-construction helpers live in [`crate::test_utils`] and are shared -//! with the snapshot tests. - -use super::{BackfillError, BackfillJob}; -use crate::{ - BlockStateDiff, OpProofsBackfillStore, OpProofsSnapshotInitProvider, - OpProofsSnapshotProviderRO, OpProofsStorageError, OpProofsStore, RethTrieStorageLayout, - SnapshotInitJob, SnapshotInitStatus, - api::{OpProofsProviderRO, OpProofsProviderRw}, - initialize::InitializationJob, - proof::DatabaseStateRoot, - test_utils::{ - build_chain_and_initialize_storage, build_chain_with_storage_writes_and_initialize_storage, - build_transfer_block, chain_spec_with_address, commit_block_to_database, create_storage, - deterministic_keypair, execute_block, public_key_to_address, - }, -}; -use alloy_consensus::BlockHeader; -use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; -use alloy_primitives::{Address, B256}; -use reth_db::Database; -use reth_db_common::init::init_genesis; -use reth_evm::{ConfigureEvm, execute::Executor}; -use reth_evm_ethereum::EthEvmConfig; -use reth_provider::{ - DatabaseProviderFactory, HashedPostStateProvider, LatestStateProviderRef, StateRootProvider, - StorageSettingsCache, test_utils::create_test_provider_factory_with_chain_spec, -}; -use reth_revm::database::StateProviderDatabase; -use reth_trie::{HashedPostState, StateRoot}; -use serial_test::serial; - -// ============================ Tests ============================ - -#[test] -fn run_is_noop_when_target_at_or_above_earliest() { - // Build a chain of 3 blocks; storage initialized at block 3 (earliest = 3). - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(3); - - // target == earliest: no-op. - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(latest_num).unwrap(); - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); - } - - // target > earliest: also no-op. - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(latest_num + 100).unwrap(); - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); - } -} - -#[test] -fn run_errors_when_storage_uninitialized() { - let key_pair = deterministic_keypair(); - let chain_spec = chain_spec_with_address(public_key_to_address(key_pair.public_key())); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); - init_genesis(&provider_factory).unwrap(); - - // Storage created but never initialized — no earliest marker. - let storage = create_storage(); - let provider = provider_factory.database_provider_ro().unwrap(); - let err = BackfillJob::new(provider, storage).run(0).unwrap_err(); - assert!( - matches!(err, BackfillError::Storage(OpProofsStorageError::NoBlocksFound)), - "expected NoBlocksFound, got {err:?}" - ); -} - -#[test] -fn run_extends_window_backward_multi_block() { - // 5-block chain — exercises descending iteration across multiple - // `BackfillContext::step` calls. - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(5); - - { - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); - } - - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(0).unwrap(); - } - - let provider = provider_factory.database_provider_ro().unwrap(); - let genesis_hash = reth_provider::BlockHashReader::block_hash(&provider, 0).unwrap().unwrap(); - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); -} - -#[test] -fn run_extends_window_backward() { - // Smallest possible case: 1-block chain, single backfill step from 1 → 0. - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(1); - - // Sanity: earliest starts at the latest block. - { - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); - } - - // Backfill all the way down to block 0 (genesis). - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(0).unwrap(); - } - - // Earliest should now point at block 0 (the genesis hash). - let provider = provider_factory.database_provider_ro().unwrap(); - let genesis_hash = reth_provider::BlockHashReader::block_hash(&provider, 0).unwrap().unwrap(); - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); -} - -#[test] -fn run_extends_window_backward_with_storage_writes() { - // Every block calls `STORAGE_CONTRACT`, writing `block.number` to slot 0. - // This exercises the backfill code paths that are silent in plain-transfer - // tests: - // - `V2HashedStorageChangeSets` / `V2HashedStoragesHistory` writes during `prepend_block` - // (the slot value changes every block). - // - Storage-side reconstruction via `V2StorageCursor` at each historical block during the - // in-job `StateRoot::overlay_root` validation. - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - - { - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); - } - - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(0).unwrap(); - } - - let provider = provider_factory.database_provider_ro().unwrap(); - let genesis_hash = reth_provider::BlockHashReader::block_hash(&provider, 0).unwrap().unwrap(); - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); -} - -#[test] -fn backfill_then_forward_write_preserves_state_roots() { - // End-to-end check that backfill and forward writes can share a proofs DB - // without corrupting historical reads: - // - // 1. Build a 5-block reth chain. Init proofs at block 5 → earliest=latest=5. - // 2. Backfill earliest from 5 down to 2. - // 3. Build + forward-write blocks 6 and 7 via `store_trie_updates`. - // 4. Assert state roots at every block in [2, 7] match reth's headers. - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient = Address::repeat_byte(0x42); - let mut last_hash = chain_spec.genesis_hash(); - - // 1. Build blocks 1..=5 in reth. - for n in 1..=5u64 { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - } - - // 2. Initialize proofs storage at block 5. - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout).run(5, last_hash).unwrap(); - } - - // 3. Backfill earliest from 5 down to 2. - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(2).unwrap(); - } - { - let window = storage.provider_ro().unwrap().get_proof_window().unwrap(); - assert_eq!(window.earliest.number, 2); - assert_eq!(window.latest.number, 5); - } - - // 4. Build + forward-write blocks 6 and 7. - for n in 6..=7u64 { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - - // Execute the block + compute (state_root, trie_updates) against reth's - // current state. Mirrors `execute_block` but also returns the trie - // updates + hashed post-state needed to build a `BlockStateDiff`. - let (exec, hashed_state, trie_updates) = { - let provider = provider_factory.provider().unwrap(); - let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); - let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); - let block_executor = evm_config.batch_executor(db); - let exec = block_executor.execute(&block).unwrap(); - let hashed_state = - LatestStateProviderRef::new(&provider).hashed_post_state(&exec.state); - let (state_root, trie_updates) = LatestStateProviderRef::new(&provider) - .state_root_with_updates(hashed_state.clone()) - .unwrap(); - block.set_state_root(state_root); - (exec, hashed_state, trie_updates) - }; - - // Advance reth's chain to block n. - commit_block_to_database(&block, &exec, &provider_factory); - - // Forward-write block n into the proofs storage. - let rw = storage.provider_rw().unwrap(); - rw.store_trie_updates( - BlockWithParent { block: NumHash::new(n, block.hash()), parent: last_hash }, - BlockStateDiff { - sorted_trie_updates: trie_updates.into_sorted(), - sorted_post_state: hashed_state.into_sorted(), - }, - ) - .unwrap(); - OpProofsProviderRw::commit(rw).unwrap(); - - last_hash = block.hash(); - } - - // Window should now span [2, 7]. - { - let window = storage.provider_ro().unwrap().get_proof_window().unwrap(); - assert_eq!(window.earliest.number, 2); - assert_eq!(window.latest.number, 7); - } - - // 5. Validate state roots at every block in [2, 7] against reth's headers. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - for n in 2..=7u64 { - let expected = reth_provider::HeaderProvider::header_by_number(&reth_provider, n) - .unwrap() - .unwrap() - .state_root(); - let computed = - StateRoot::overlay_root(storage.provider_ro().unwrap(), n, HashedPostState::default()) - .unwrap(); - assert_eq!(computed, expected, "state root mismatch at block {n}"); - } -} - -/// Batched (K > 1) and per-block (K = 1) backfill must be observationally equivalent: both -/// runs land the same proof window and reconstruct the same state root at every block in it. -#[test] -fn run_batched_matches_unbatched_state_roots() { - use crate::test_utils::build_chain_with_storage_writes_and_initialize_storage; - - // Storage-writing chain exercises both account and storage history paths. - const N: u64 = 20; - const TARGET: u64 = 3; - - // Run 1: K=1 (per-block commits). - let (pf_a, storage_a, latest_a, _hash_a) = - build_chain_with_storage_writes_and_initialize_storage(N); - { - let provider = pf_a.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage_a.clone()).with_batch_size(1).run(TARGET).unwrap(); - } - - // Run 2: K=10 (batched commits). - let (pf_b, storage_b, latest_b, _hash_b) = - build_chain_with_storage_writes_and_initialize_storage(N); - { - let provider = pf_b.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage_b.clone()).with_batch_size(10).run(TARGET).unwrap(); - } - - assert_eq!(latest_a, latest_b, "chain construction is deterministic"); - assert_eq!(latest_a, N); - - // Both runs should leave `earliest` at the same place. - { - let win_a = storage_a.provider_ro().unwrap().get_proof_window().unwrap(); - let win_b = storage_b.provider_ro().unwrap().get_proof_window().unwrap(); - assert_eq!(win_a.earliest, win_b.earliest); - assert_eq!(win_a.latest, win_b.latest); - assert_eq!(win_a.earliest.number, TARGET); - } - - // Reconstruct state root at every block in [TARGET, N] from BOTH storages and confirm they - // agree with each other and with reth's header. If batching corrupted any per-block history, - // the overlay-root computation here would diverge. - let reth_provider_a = pf_a.database_provider_ro().unwrap(); - for n in TARGET..=N { - let expected = reth_provider::HeaderProvider::header_by_number(&reth_provider_a, n) - .unwrap() - .unwrap() - .state_root(); - let computed_a = StateRoot::overlay_root( - storage_a.provider_ro().unwrap(), - n, - HashedPostState::default(), - ) - .unwrap(); - let computed_b = StateRoot::overlay_root( - storage_b.provider_ro().unwrap(), - n, - HashedPostState::default(), - ) - .unwrap(); - assert_eq!(computed_a, expected, "K=1 path: state root mismatch at block {n}",); - assert_eq!(computed_b, expected, "K=10 path: state root mismatch at block {n}",); - assert_eq!(computed_a, computed_b, "K=1 vs K=10 disagree at block {n}"); - } -} - -/// Job-loop atomicity: a validation failure mid-batch drops the open RW tx, rolling back -/// the in-flight writes for every prior block in the same batch. -#[test] -fn run_rolls_back_in_flight_batch_writes_on_validation_failure() { - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient = Address::repeat_byte(0x42); - const NUM_BLOCKS: u64 = 5; - // Corrupt `header[2].state_root` — `validate_state_root` for block 3 will check it, - // *after* blocks 5 and 4 have prepended successfully within the same open tx. - const CORRUPTED_BLOCK: u64 = 2; - const FAILING_VALIDATE_BLOCK: u64 = CORRUPTED_BLOCK + 1; - const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); - - let mut last_hash = chain_spec.genesis_hash(); - for n in 1..=NUM_BLOCKS { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - if n == CORRUPTED_BLOCK { - block.set_state_root(BOGUS_ROOT); - } - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - } - - // Init proofs at the chain tip. - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(NUM_BLOCKS, last_hash) - .unwrap(); - } - let earliest_before = storage.provider_ro().unwrap().get_earliest_block().unwrap(); - assert_eq!(earliest_before, NumHash::new(NUM_BLOCKS, last_hash)); - - // K=10 means the whole 5-block range fits in one batch — no commit before the failure. - let provider = provider_factory.database_provider_ro().unwrap(); - let err = BackfillJob::new(provider, storage.clone()).with_batch_size(10).run(0).unwrap_err(); - - match err { - BackfillError::StateRootMismatch { block_number, expected, .. } => { - assert_eq!( - block_number, FAILING_VALIDATE_BLOCK, - "validation must fire at block {FAILING_VALIDATE_BLOCK} (parent header is bogus)", - ); - assert_eq!(expected, BOGUS_ROOT, "expected root comes from the tampered header"); - } - other => panic!("expected StateRootMismatch, got {other:?}"), - } - - // Atomicity check: blocks 5 and 4 had successfully prepended (and validated) within the open - // tx before iteration 3 failed. The tx dropped on `Err`, so none of those writes — nor block - // 3's prepend — persisted. `earliest` must still be at the pre-backfill value. - let earliest_after = storage.provider_ro().unwrap().get_earliest_block().unwrap(); - assert_eq!( - earliest_after, earliest_before, - "in-flight batch writes (blocks 5, 4, 3) must roll back when the batch fails", - ); -} - -// ============================ Snapshot-accelerated tests ============================ - -#[test] -#[serial] -fn run_with_snapshot_is_noop_when_target_at_or_above_earliest() { - // Build 3-block chain; storage init at block 3 (earliest = 3). - // `run_with_snapshot` must short-circuit before touching the snapshot, - // so no snapshot is bootstrapped. - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(3); - - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run_with_snapshot(latest_num).unwrap(); - } - - // Earliest unchanged. - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(latest_num, latest_hash)); - - // Snapshot was never bootstrapped — init anchor still NotStarted. - let init_anchor = - storage.snapshot_initialization_provider().unwrap().snapshot_init_anchor().unwrap(); - assert_eq!(init_anchor.status, SnapshotInitStatus::NotStarted); - assert_eq!(init_anchor.block, None); -} - -#[test] -#[serial] -fn run_with_snapshot_bootstraps_snapshot_when_missing() { - // 5-block chain; no snapshot exists. `run_with_snapshot` must bootstrap - // the snapshot at the current `earliest` and then backfill down to 0. - // After backfill the snapshot anchor must track the new `earliest`. - let (provider_factory, storage, _, _) = build_chain_and_initialize_storage(5); - - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run_with_snapshot(0).unwrap(); - } - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let genesis_hash = - reth_provider::BlockHashReader::block_hash(&reth_provider, 0).unwrap().unwrap(); - - // Earliest reached genesis. - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); - - let init_anchor = - storage.snapshot_initialization_provider().unwrap().snapshot_init_anchor().unwrap(); - assert_eq!(init_anchor.status, SnapshotInitStatus::Completed); - assert_eq!(init_anchor.block, Some(BlockNumHash::new(0, genesis_hash))); - - let sp = storage.snapshot_provider_ro().unwrap(); - assert_eq!(sp.snapshot_anchor().unwrap(), BlockNumHash::new(0, genesis_hash)); -} - -#[test] -#[serial] -fn run_with_snapshot_uses_existing_ready_snapshot() { - // Pre-initialize the snapshot at `earliest`, then run snapshot-accelerated - // backfill. The job must reuse the existing snapshot (no re-init) and - // still drive earliest to the target. - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(4); - - // Pre-init snapshot at the current earliest. - { - let reth_provider = provider_factory.database_provider_ro().unwrap(); - SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).unwrap(); - } - // Sanity: snapshot is Completed at (latest_num, latest_hash). - { - let init_anchor = - storage.snapshot_initialization_provider().unwrap().snapshot_init_anchor().unwrap(); - assert_eq!(init_anchor.status, SnapshotInitStatus::Completed); - assert_eq!(init_anchor.block, Some(BlockNumHash::new(latest_num, latest_hash))); - } - - // Snapshot-accelerated backfill all the way to genesis. - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run_with_snapshot(0).unwrap(); - } - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let genesis_hash = - reth_provider::BlockHashReader::block_hash(&reth_provider, 0).unwrap().unwrap(); - - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); - - let sp = storage.snapshot_provider_ro().unwrap(); - assert_eq!(sp.snapshot_anchor().unwrap(), BlockNumHash::new(0, genesis_hash)); -} - -#[test] -#[serial] -fn run_with_snapshot_errors_on_anchor_mismatch() { - // Plant a `Completed` snapshot at the initial `earliest`, then advance - // the proofs window via plain (non-snapshot) backfill so `earliest` - // diverges from the snapshot anchor. A subsequent `run_with_snapshot` - // call must refuse rather than silently corrupting the snapshot. - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(5); - - // Snapshot at (5, hash5). - { - let reth_provider = provider_factory.database_provider_ro().unwrap(); - SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).unwrap(); - } - - // Plain backfill from 5 down to 3 — leaves the snapshot anchor at (5, _) - // while earliest moves to (3, hash3). - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run(3).unwrap(); - } - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let hash3 = reth_provider::BlockHashReader::block_hash(&reth_provider, 3).unwrap().unwrap(); - - // Snapshot-accelerated backfill must detect the mismatch. - let err = BackfillJob::new(reth_provider, storage).run_with_snapshot(0).unwrap_err(); - match err { - BackfillError::SnapshotAnchorMismatch { expected, found } => { - assert_eq!(expected, BlockNumHash::new(3, hash3)); - assert_eq!(found, BlockNumHash::new(latest_num, latest_hash)); - } - other => panic!("expected SnapshotAnchorMismatch, got {other:?}"), - } -} - -#[test] -#[serial] -fn run_with_snapshot_extends_window_backward_with_storage_writes() { - // Every block touches a storage slot, so each iteration drives the - // storage-trie codepaths inside the snapshot writer (`update_snapshot`) - // and the snapshot storage cursors used by `validate_state_root_with_snapshot`. - let (provider_factory, storage, _latest_num, _latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - - { - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage.clone()).run_with_snapshot(0).unwrap(); - } - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let genesis_hash = - reth_provider::BlockHashReader::block_hash(&reth_provider, 0).unwrap().unwrap(); - - let ro = storage.provider_ro().unwrap(); - assert_eq!(ro.get_earliest_block().unwrap(), NumHash::new(0, genesis_hash)); - - let sp = storage.snapshot_provider_ro().unwrap(); - assert_eq!(sp.snapshot_anchor().unwrap(), BlockNumHash::new(0, genesis_hash)); -} - -/// Snapshot-accelerated batched backfill (K > 1) must produce the same proofs DB state and -/// snapshot anchor as the per-block path (K = 1). Mirrors -/// [`run_batched_matches_unbatched_state_roots`] for the snapshot path. -#[test] -#[serial] -fn run_with_snapshot_batched_matches_unbatched_state_roots() { - const N: u64 = 20; - const TARGET: u64 = 3; - - // Run A: K=1 (per-block commits on the snapshot path). - let (pf_a, storage_a, _, _) = build_chain_with_storage_writes_and_initialize_storage(N); - { - let provider = pf_a.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage_a.clone()) - .with_batch_size(1) - .run_with_snapshot(TARGET) - .unwrap(); - } - - // Run B: K=10 (batched commits, same path). - let (pf_b, storage_b, _, _) = build_chain_with_storage_writes_and_initialize_storage(N); - { - let provider = pf_b.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage_b.clone()) - .with_batch_size(10) - .run_with_snapshot(TARGET) - .unwrap(); - } - - // Earliest + snapshot anchor must match across both runs. - let win_a = storage_a.provider_ro().unwrap().get_proof_window().unwrap(); - let win_b = storage_b.provider_ro().unwrap().get_proof_window().unwrap(); - assert_eq!(win_a.earliest, win_b.earliest); - assert_eq!(win_a.latest, win_b.latest); - assert_eq!(win_a.earliest.number, TARGET); - - let anchor_a = storage_a.snapshot_provider_ro().unwrap().snapshot_anchor().unwrap(); - let anchor_b = storage_b.snapshot_provider_ro().unwrap().snapshot_anchor().unwrap(); - assert_eq!(anchor_a, anchor_b, "snapshot anchors must match across batch sizes"); - - // State roots from BOTH storages must agree with reth at every backfilled block. - let reth_provider = pf_a.database_provider_ro().unwrap(); - for n in TARGET..=N { - let expected = reth_provider::HeaderProvider::header_by_number(&reth_provider, n) - .unwrap() - .unwrap() - .state_root(); - let root_a = StateRoot::overlay_root( - storage_a.provider_ro().unwrap(), - n, - HashedPostState::default(), - ) - .unwrap(); - let root_b = StateRoot::overlay_root( - storage_b.provider_ro().unwrap(), - n, - HashedPostState::default(), - ) - .unwrap(); - assert_eq!(root_a, expected, "snapshot K=1: state root mismatch at block {n}",); - assert_eq!(root_b, expected, "snapshot K=10: state root mismatch at block {n}",); - } -} - -/// Negative test for the validation safety net in [`BackfillJob`]. Every -/// "happy path" test feeds a self-consistent chain, so the -/// [`BackfillError::StateRootMismatch`] arm in `validate_state_root` is never -/// taken — a bug there (wrong overlay block, inverted compare, etc.) would let -/// corrupt history through silently. Here we commit one block with a deliberately -/// wrong `state_root` field and assert the job aborts at the right block. -#[test] -fn run_aborts_with_state_root_mismatch_when_header_corrupted() { - // Custom chain build - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient = Address::repeat_byte(0x42); - const NUM_BLOCKS: u64 = 3; - const CORRUPTED_BLOCK: u64 = NUM_BLOCKS - 1; - const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); - - let mut last_hash = chain_spec.genesis_hash(); - for n in 1..=NUM_BLOCKS { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - if n == CORRUPTED_BLOCK { - block.set_state_root(BOGUS_ROOT); - } - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - } - - // Initialization captures *real* state at the latest block, so backfill's - // reconstruction at any prior block will produce the real state root — - // which disagrees with the bogus header at CORRUPTED_BLOCK. - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(NUM_BLOCKS, last_hash) - .unwrap(); - } - - let provider = provider_factory.database_provider_ro().unwrap(); - let err = BackfillJob::new(provider, storage).run(0).unwrap_err(); - match err { - BackfillError::StateRootMismatch { block_number, expected, .. } => { - // Backfill descends from `latest`; the first prepend is NUM_BLOCKS, - // which validates at CORRUPTED_BLOCK. - assert_eq!(block_number, NUM_BLOCKS, "validation must fire on the first prepend"); - assert_eq!(expected, BOGUS_ROOT, "expected root must come from the tampered header"); - } - other => panic!("expected StateRootMismatch, got {other:?}"), - } -} - -/// Snapshot-accelerated mirror of -/// [`run_aborts_with_state_root_mismatch_when_header_corrupted`]: the -/// validation step in `validate_state_root_with_snapshot` (the snapshot path's -/// safety net) must reject a mismatch between the snapshot's computed root and -/// reth's header at `E-1`. -#[test] -fn run_with_snapshot_aborts_with_state_root_mismatch_when_header_corrupted() { - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient = Address::repeat_byte(0x42); - const NUM_BLOCKS: u64 = 3; - - const CORRUPTED_BLOCK: u64 = NUM_BLOCKS - 1; - const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); - - let mut last_hash = chain_spec.genesis_hash(); - for n in 1..=NUM_BLOCKS { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - if n == CORRUPTED_BLOCK { - block.set_state_root(BOGUS_ROOT); - } - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - } - - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(NUM_BLOCKS, last_hash) - .unwrap(); - } - - let provider = provider_factory.database_provider_ro().unwrap(); - let err = BackfillJob::new(provider, storage).run_with_snapshot(0).unwrap_err(); - match err { - BackfillError::StateRootMismatch { block_number, expected, .. } => { - assert_eq!( - block_number, NUM_BLOCKS, - "validation must fire on the first snapshot-accelerated prepend", - ); - assert_eq!(expected, BOGUS_ROOT, "expected root must come from the tampered header"); - } - other => panic!("expected StateRootMismatch, got {other:?}"), - } -} - -/// Negative test for the changeset-pruned detection in -/// [`compute_block_backfill_diff`](super::changesets::compute_block_backfill_diff). -/// -/// When reth prunes a block it typically deletes the per-block account/storage changesets -/// together with the body. Without detection, `from_reverts_auto` returns an empty revert, -/// the reconstructed trie@N-1 equals trie@N, and validation surfaces the misleading -/// [`BackfillError::StateRootMismatch`]. The fix detects the case directly and returns -/// [`BackfillError::BlockBodyPruned`] instead. -/// -/// We exercise this by: -/// 1. Building a 3-block transfer chain with `StorageSettings::v1()` (so changesets live in MDBX -/// and are surgically deletable). -/// 2. Deleting every `AccountChangeSets` entry at the targeted block. -/// 3. Running backfill and asserting `BlockBodyPruned(n)` rather than `StateRootMismatch`. -#[test] -fn run_aborts_with_block_body_pruned_when_changesets_deleted() { - use reth_db::{ - cursor::{DbCursorRO, DbDupCursorRW}, - tables, - transaction::{DbTx, DbTxMut}, - }; - use reth_db_api::models::StorageSettings; - use reth_db_common::init::init_genesis_with_settings; - - // Custom chain build: force v1 so changesets land in MDBX (deletable via cursor). - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis_with_settings(&provider_factory, StorageSettings::v1()).unwrap(); - - let recipient = Address::repeat_byte(0x42); - const NUM_BLOCKS: u64 = 3; - const PRUNED_BLOCK: u64 = 2; - - let mut last_hash = chain_spec.genesis_hash(); - for n in 1..=NUM_BLOCKS { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - } - - // Simulate reth pruning: delete every AccountChangeSets entry at PRUNED_BLOCK. - // Transfer-only txs produce account changesets but no storage changesets, so the - // account table is enough to break the per-block revert at that height. - { - let tx = provider_factory.db_ref().tx_mut().unwrap(); - let mut cursor = tx.cursor_dup_write::().unwrap(); - let found = cursor.seek_exact(PRUNED_BLOCK).unwrap(); - assert!(found.is_some(), "block {PRUNED_BLOCK} must have changeset entries pre-deletion"); - cursor.delete_current_duplicates().unwrap(); - assert!(cursor.seek_exact(PRUNED_BLOCK).unwrap().is_none(), "deletion left residue"); - drop(cursor); - tx.commit().unwrap(); - } - - // Initialize proofs storage at the chain tip. - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(NUM_BLOCKS, last_hash) - .unwrap(); - } - - // Backfill descends from NUM_BLOCKS down. Block NUM_BLOCKS prepends successfully; the - // detection fires when the job moves on to PRUNED_BLOCK. - let provider = provider_factory.database_provider_ro().unwrap(); - let err = BackfillJob::new(provider, storage).run(0).unwrap_err(); - match err { - BackfillError::BlockBodyPruned(block_number) => { - assert_eq!( - block_number, PRUNED_BLOCK, - "detection must fire at the block whose changesets are missing", - ); - } - other => panic!("expected BlockBodyPruned, got {other:?}"), - } -} diff --git a/vendor/reth-optimism-trie/src/cursor.rs b/vendor/reth-optimism-trie/src/cursor.rs deleted file mode 100644 index 732e806..0000000 --- a/vendor/reth-optimism-trie/src/cursor.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Implementation of [`HashedCursor`] and [`TrieCursor`] for -//! [`OpProofsStorage`](crate::OpProofsStorage). - -use alloy_primitives::{B256, U256}; -use derive_more::Constructor; -use reth_db::DatabaseError; -use reth_primitives_traits::Account; -use reth_trie::{ - hashed_cursor::{HashedCursor, HashedStorageCursor}, - trie_cursor::{TrieCursor, TrieStorageCursor}, -}; -use reth_trie_common::{BranchNodeCompact, Nibbles}; - -/// Manages reading storage or account trie nodes from [`TrieCursor`]. -#[derive(Debug, Clone, Constructor)] -pub struct OpProofsTrieCursor(pub C); - -impl TrieCursor for OpProofsTrieCursor -where - C: TrieCursor, -{ - #[inline] - fn seek_exact( - &mut self, - key: Nibbles, - ) -> Result, DatabaseError> { - self.0.seek_exact(key) - } - - #[inline] - fn seek( - &mut self, - key: Nibbles, - ) -> Result, DatabaseError> { - self.0.seek(key) - } - - #[inline] - fn next(&mut self) -> Result, DatabaseError> { - self.0.next() - } - - #[inline] - fn current(&mut self) -> Result, DatabaseError> { - self.0.current() - } - - #[inline] - fn reset(&mut self) { - self.0.reset() - } -} - -impl TrieStorageCursor for OpProofsTrieCursor -where - C: TrieStorageCursor, -{ - #[inline] - fn set_hashed_address(&mut self, hashed_address: B256) { - self.0.set_hashed_address(hashed_address) - } -} - -/// Manages reading hashed account nodes from external storage. -#[derive(Debug, Clone, Constructor)] -pub struct OpProofsHashedAccountCursor(pub C); - -impl HashedCursor for OpProofsHashedAccountCursor -where - C: HashedCursor + Send, -{ - type Value = Account; - - #[inline] - fn seek(&mut self, key: B256) -> Result, DatabaseError> { - self.0.seek(key) - } - - #[inline] - fn next(&mut self) -> Result, DatabaseError> { - self.0.next() - } - - #[inline] - fn reset(&mut self) { - self.0.reset() - } -} - -/// Manages reading hashed storage nodes from external storage. -#[derive(Debug, Clone, Constructor)] -pub struct OpProofsHashedStorageCursor(pub C); - -impl HashedCursor for OpProofsHashedStorageCursor -where - C: HashedCursor + Send, -{ - type Value = U256; - - #[inline] - fn seek(&mut self, key: B256) -> Result, DatabaseError> { - self.0.seek(key) - } - - #[inline] - fn next(&mut self) -> Result, DatabaseError> { - self.0.next() - } - - #[inline] - fn reset(&mut self) { - self.0.reset() - } -} - -impl HashedStorageCursor for OpProofsHashedStorageCursor -where - C: HashedStorageCursor + Send, -{ - #[inline] - fn is_storage_empty(&mut self) -> Result { - self.0.is_storage_empty() - } - - #[inline] - fn set_hashed_address(&mut self, hashed_address: B256) { - self.0.set_hashed_address(hashed_address) - } -} diff --git a/vendor/reth-optimism-trie/src/cursor_factory.rs b/vendor/reth-optimism-trie/src/cursor_factory.rs deleted file mode 100644 index cb21a26..0000000 --- a/vendor/reth-optimism-trie/src/cursor_factory.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Implements [`TrieCursorFactory`] and [`HashedCursorFactory`] for [`crate::OpProofsStore`] types. - -use crate::{ - api::{OpProofsProviderRO, OpProofsSnapshotProviderRO}, - cursor::{OpProofsHashedAccountCursor, OpProofsHashedStorageCursor, OpProofsTrieCursor}, -}; -use alloy_primitives::B256; -use reth_db::DatabaseError; -use reth_trie::{hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory}; - -/// Factory for creating trie cursors for [`OpProofsProviderRO`]. -#[derive(Debug, Clone)] -pub struct OpProofsTrieCursorFactory

{ - provider: P, - block_number: u64, -} - -impl OpProofsTrieCursorFactory

{ - /// Initializes new `OpProofsTrieCursorFactory` - pub const fn new(provider: P, block_number: u64) -> Self { - Self { provider, block_number } - } -} - -impl

TrieCursorFactory for OpProofsTrieCursorFactory

-where - P: OpProofsProviderRO, -{ - type AccountTrieCursor<'a> - = OpProofsTrieCursor> - where - Self: 'a; - type StorageTrieCursor<'a> - = OpProofsTrieCursor> - where - Self: 'a; - - fn account_trie_cursor(&self) -> Result, DatabaseError> { - Ok(OpProofsTrieCursor::new( - self.provider - .account_trie_cursor(self.block_number) - .map_err(Into::::into)?, - )) - } - - fn storage_trie_cursor( - &self, - hashed_address: B256, - ) -> Result, DatabaseError> { - Ok(OpProofsTrieCursor::new( - self.provider - .storage_trie_cursor(hashed_address, self.block_number) - .map_err(Into::::into)?, - )) - } -} - -/// Factory for creating trie cursors backed by a snapshot reader. -/// -/// Unlike [`OpProofsTrieCursorFactory`] (which reads history-aware cursors at -/// a given block number), this factory reads directly from the snapshot -/// tables. It carries no block-number context: the snapshot already reflects -/// trie state at a fixed anchor block. The caller is responsible for first -/// resolving that anchor via -/// [`crate::api::OpProofsSnapshotProviderRO::snapshot_anchor`] and ensuring -/// the block being queried matches it. -#[derive(Debug, Clone)] -pub struct SnapshotTrieCursorFactory

{ - reader: P, -} - -impl SnapshotTrieCursorFactory

{ - /// Create a new snapshot-backed trie cursor factory. - pub const fn new(reader: P) -> Self { - Self { reader } - } -} - -impl

TrieCursorFactory for SnapshotTrieCursorFactory

-where - P: OpProofsSnapshotProviderRO, -{ - type AccountTrieCursor<'a> - = P::SnapshotAccountTrieCursor<'a> - where - Self: 'a; - type StorageTrieCursor<'a> - = P::SnapshotStorageTrieCursor<'a> - where - Self: 'a; - - fn account_trie_cursor(&self) -> Result, DatabaseError> { - self.reader.snapshot_account_trie_cursor().map_err(Into::::into) - } - - fn storage_trie_cursor( - &self, - hashed_address: B256, - ) -> Result, DatabaseError> { - self.reader - .snapshot_storage_trie_cursor(hashed_address) - .map_err(Into::::into) - } -} - -/// Factory for creating hashed account cursors for [`OpProofsProviderRO`]. -#[derive(Debug, Clone)] -pub struct OpProofsHashedAccountCursorFactory

{ - provider: P, - block_number: u64, -} - -impl OpProofsHashedAccountCursorFactory

{ - /// Creates a new `OpProofsHashedAccountCursorFactory` instance. - pub const fn new(provider: P, block_number: u64) -> Self { - Self { provider, block_number } - } -} - -impl

HashedCursorFactory for OpProofsHashedAccountCursorFactory

-where - P: OpProofsProviderRO, -{ - type AccountCursor<'a> - = OpProofsHashedAccountCursor> - where - Self: 'a; - type StorageCursor<'a> - = OpProofsHashedStorageCursor> - where - Self: 'a; - - fn hashed_account_cursor(&self) -> Result, DatabaseError> { - Ok(OpProofsHashedAccountCursor::new( - self.provider - .account_hashed_cursor(self.block_number) - .map_err(Into::::into)?, - )) - } - - fn hashed_storage_cursor( - &self, - hashed_address: B256, - ) -> Result, DatabaseError> { - Ok(OpProofsHashedStorageCursor::new( - self.provider - .storage_hashed_cursor(hashed_address, self.block_number) - .map_err(Into::::into)?, - )) - } -} - -/// Factory for creating hashed leaf cursors backed by the snapshot tables. -/// -/// Mirrors [`SnapshotTrieCursorFactory`]'s role for hashed leaves: reads -/// directly from [`crate::db::V2HashedAccountsSnapshot`] and -/// [`crate::db::V2HashedStoragesSnapshot`] without history merges. Valid only -/// when the snapshot is `Ready` at the anchor the caller is reading. -#[derive(Debug, Clone)] -pub struct SnapshotHashedCursorFactory

{ - reader: P, -} - -impl SnapshotHashedCursorFactory

{ - /// Create a new snapshot-backed hashed cursor factory. - pub const fn new(reader: P) -> Self { - Self { reader } - } -} - -impl

HashedCursorFactory for SnapshotHashedCursorFactory

-where - P: OpProofsSnapshotProviderRO, -{ - type AccountCursor<'a> - = P::SnapshotHashedAccountCursor<'a> - where - Self: 'a; - type StorageCursor<'a> - = P::SnapshotHashedStorageCursor<'a> - where - Self: 'a; - - fn hashed_account_cursor(&self) -> Result, DatabaseError> { - self.reader.snapshot_hashed_account_cursor().map_err(Into::::into) - } - - fn hashed_storage_cursor( - &self, - hashed_address: B256, - ) -> Result, DatabaseError> { - self.reader - .snapshot_hashed_storage_cursor(hashed_address) - .map_err(Into::::into) - } -} diff --git a/vendor/reth-optimism-trie/src/db/cursor.rs b/vendor/reth-optimism-trie/src/db/cursor.rs deleted file mode 100644 index 1477cc9..0000000 --- a/vendor/reth-optimism-trie/src/db/cursor.rs +++ /dev/null @@ -1,1465 +0,0 @@ -use std::marker::PhantomData; - -use crate::{ - OpProofsStorageResult, - db::{ - AccountTrieHistory, HashedAccountHistory, HashedStorageHistory, HashedStorageKey, - MaybeDeleted, StorageTrieHistory, StorageTrieKey, VersionedValue, - }, -}; -use alloy_primitives::{B256, U256}; -use reth_db::{ - DatabaseError, - cursor::{DbCursorRO, DbDupCursorRO}, - table::{DupSort, Table}, -}; -use reth_primitives_traits::Account; -use reth_trie::{ - hashed_cursor::{HashedCursor, HashedStorageCursor}, - trie_cursor::{TrieCursor, TrieStorageCursor}, -}; -use reth_trie_common::{BranchNodeCompact, Nibbles, StoredNibbles}; - -/// Iterates versioned dup-sorted rows and returns the latest value (<= `max_block_number`), -/// skipping tombstones. -#[derive(Debug, Clone)] -pub struct BlockNumberVersionedCursor { - _table: PhantomData, - cursor: Cursor, - max_block_number: u64, -} - -impl BlockNumberVersionedCursor -where - T: Table> + DupSort, - Cursor: DbCursorRO + DbDupCursorRO, -{ - /// Initializes new [`BlockNumberVersionedCursor`]. - pub const fn new(cursor: Cursor, max_block_number: u64) -> Self { - Self { _table: PhantomData, cursor, max_block_number } - } - - /// Check if the cursor is currently positioned at a valid row. - fn is_positioned(&mut self) -> OpProofsStorageResult { - Ok(self.cursor.current()?.is_some()) - } - - /// Resolve the latest version for `key` with `block_number` <= `max_block_number`. - /// Strategy: - /// - `seek_by_key_subkey(key, max)` gives first dup >= max. - /// - if exactly == max → it's our latest - /// - if > max → `prev_dup()` is latest < max (or None) - /// - if no dup >= max: - /// - if key exists → `last_dup()` is latest < max - /// - else → None - fn latest_version_for_key( - &mut self, - key: T::Key, - ) -> OpProofsStorageResult> { - // First dup with subkey >= max_block_number - let seek_res = self.cursor.seek_by_key_subkey(key.clone(), self.max_block_number)?; - - if let Some(vv) = seek_res { - if vv.block_number > self.max_block_number { - // step back to the last dup < max - return Ok(self.cursor.prev_dup()?); - } - // already at the dup = max - return Ok(Some((key, vv))); - } - - // No dup >= max ⇒ either key absent or all dups < max. Check if key exists: - if self.cursor.seek_exact(key.clone())?.is_none() { - return Ok(None); - } - - // Key exists ⇒ take last dup (< max). - if let Some(vv) = self.cursor.last_dup()? { - return Ok(Some((key, vv))); - } - Ok(None) - } - - /// Returns a non-deleted latest version for exactly `key`, if any. - fn seek_exact(&mut self, key: T::Key) -> OpProofsStorageResult> { - if let Some((latest_key, latest_value)) = self.latest_version_for_key(key)? && - let MaybeDeleted(Some(v)) = latest_value.value - { - return Ok(Some((latest_key, v))); - } - Ok(None) - } - - /// Walk forward from `first_key` (inclusive) until we find a *live* latest-≤-max value. - /// `first_key` must already be a *real key* in the table. - fn next_live_from( - &mut self, - mut first_key: T::Key, - ) -> OpProofsStorageResult> { - loop { - // Compute latest version ≤ max for this key - if let Some((k, v)) = self.seek_exact(first_key.clone())? { - return Ok(Some((k, v))); - } - - // Move to next distinct key, or EOF - let Some((next_key, _)) = self.cursor.next_no_dup()? else { - return Ok(None); - }; - - first_key = next_key; - } - } - - /// Seek to the first non-deleted latest version at or after `start_key`. - /// Logic: - /// - Try exact key first (above). If alive, return it. - /// - Otherwise hop to next distinct key and repeat until we find a live version or hit EOF. - fn seek(&mut self, start_key: T::Key) -> OpProofsStorageResult> { - // Position MDBX at first key >= start_key - if let Some((first_key, _)) = self.cursor.seek(start_key)? { - return self.next_live_from(first_key); - } - Ok(None) - } - - /// Advance to the next distinct key from the current MDBX position - /// and return its non-deleted latest version, if any. - /// Next distinct key; if not positioned, start from `T::Key::default()`. - fn next(&mut self) -> OpProofsStorageResult> - where - T::Key: Default, - { - // If not positioned, start from the beginning (default key). - if self.cursor.current()?.is_none() { - let Some((first_key, _)) = self.cursor.seek(T::Key::default())? else { - return Ok(None); - }; - return self.next_live_from(first_key); - } - - // Otherwise advance to next distinct key and resume the walk. - let Some((next_key, _)) = self.cursor.next_no_dup()? else { - return Ok(None); - }; - self.next_live_from(next_key) - } -} - -/// MDBX implementation of [`TrieCursor`]. -#[derive(Debug)] -pub struct MdbxTrieCursor { - inner: BlockNumberVersionedCursor, - hashed_address: Option, -} - -impl< - V, - T: Table> + DupSort, - Cursor: DbCursorRO + DbDupCursorRO, -> MdbxTrieCursor -{ - /// Initializes new [`MdbxTrieCursor`]. - pub const fn new(cursor: Cursor, max_block_number: u64, hashed_address: Option) -> Self { - Self { inner: BlockNumberVersionedCursor::new(cursor, max_block_number), hashed_address } - } -} - -impl TrieCursor for MdbxTrieCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - fn seek_exact( - &mut self, - path: Nibbles, - ) -> Result, DatabaseError> { - Ok(self - .inner - .seek_exact(StoredNibbles(path)) - .map(|opt| opt.map(|(StoredNibbles(n), node)| (n, node)))?) - } - - fn seek( - &mut self, - path: Nibbles, - ) -> Result, DatabaseError> { - Ok(self - .inner - .seek(StoredNibbles(path)) - .map(|opt| opt.map(|(StoredNibbles(n), node)| (n, node)))?) - } - - fn next(&mut self) -> Result, DatabaseError> { - Ok(self.inner.next().map(|opt| opt.map(|(StoredNibbles(n), node)| (n, node)))?) - } - - fn current(&mut self) -> Result, DatabaseError> { - self.inner.cursor.current().map(|opt| opt.map(|(StoredNibbles(n), _)| n)) - } - - fn reset(&mut self) { - // Database cursors are stateless, no reset needed - } -} - -impl TrieCursor for MdbxTrieCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - fn seek_exact( - &mut self, - path: Nibbles, - ) -> Result, DatabaseError> { - if let Some(address) = self.hashed_address { - let key = StorageTrieKey::new(address, StoredNibbles(path)); - return Ok(self.inner.seek_exact(key).map(|opt| { - opt.and_then(|(k, node)| (k.hashed_address == address).then_some((k.path.0, node))) - })?); - } - Ok(None) - } - - fn seek( - &mut self, - path: Nibbles, - ) -> Result, DatabaseError> { - if let Some(address) = self.hashed_address { - let key = StorageTrieKey::new(address, StoredNibbles(path)); - return Ok(self.inner.seek(key).map(|opt| { - opt.and_then(|(k, node)| (k.hashed_address == address).then_some((k.path.0, node))) - })?); - } - Ok(None) - } - - fn next(&mut self) -> Result, DatabaseError> { - if let Some(address) = self.hashed_address { - // If the cursor is not positioned, we need to seek to the first key for our bound - // address to ensure we start iterating from the correct position in the - // table. This is necessary because BlockNumberVersionedCursor::next() would - // otherwise start from T::Key::default() (the beginning of the entire - // table), which would cause us to miss entries for non-first addresses. - if !self.inner.is_positioned()? { - return self.seek(Nibbles::default()); - } - - return Ok(self.inner.next().map(|opt| { - opt.and_then(|(k, node)| (k.hashed_address == address).then_some((k.path.0, node))) - })?); - } - Ok(None) - } - - fn current(&mut self) -> Result, DatabaseError> { - if let Some(address) = self.hashed_address { - return self.inner.cursor.current().map(|opt| { - opt.and_then(|(k, _)| (k.hashed_address == address).then_some(k.path.0)) - }); - } - Ok(None) - } - - fn reset(&mut self) { - // Database cursors are stateless, no reset needed - } -} - -impl TrieStorageCursor for MdbxTrieCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - fn set_hashed_address(&mut self, hashed_address: B256) { - self.hashed_address = Some(hashed_address); - } -} - -/// MDBX implementation of [`HashedCursor`] for storage state. -#[derive(Debug)] -pub struct MdbxStorageCursor { - inner: BlockNumberVersionedCursor, - hashed_address: B256, -} - -impl MdbxStorageCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - /// Initializes new [`MdbxStorageCursor`] - pub const fn new(cursor: Cursor, block_number: u64, hashed_address: B256) -> Self { - Self { inner: BlockNumberVersionedCursor::new(cursor, block_number), hashed_address } - } -} - -impl HashedCursor for MdbxStorageCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - type Value = U256; - - fn seek(&mut self, key: B256) -> Result, DatabaseError> { - let storage_key = HashedStorageKey::new(self.hashed_address, key); - - // hashed storage values can be zero, which means the storage slot is deleted, so we should - // skip those - let result = self.inner.seek(storage_key).map(|opt| { - opt.and_then(|(k, v)| { - // Only return entries that belong to the bound address - (k.hashed_address == self.hashed_address).then_some((k.hashed_storage_key, v.0)) - }) - })?; - - if let Some((_, v)) = result && - v.is_zero() - { - return self.next(); - } - - Ok(result) - } - - fn next(&mut self) -> Result, DatabaseError> { - // If the cursor is not positioned, we need to seek to the first key for our bound address - // to ensure we start iterating from the correct position in the table. - // This is necessary because BlockNumberVersionedCursor::next() would otherwise start - // from T::Key::default() (the beginning of the entire table), which would cause us - // to miss entries for non-first addresses. - if !self.inner.is_positioned()? { - return self.seek(B256::ZERO); - } - - loop { - let result = self.inner.next().map(|opt| { - opt.and_then(|(k, v)| { - // Only return entries that belong to the bound address - (k.hashed_address == self.hashed_address).then_some((k.hashed_storage_key, v.0)) - }) - })?; - - // hashed storage values can be zero, which means the storage slot is deleted, so we - // should skip those - if let Some((_, v)) = result && - v.is_zero() - { - continue; - } - - return Ok(result); - } - } - - fn reset(&mut self) { - // Database cursors are stateless, no reset needed - } -} - -impl HashedStorageCursor for MdbxStorageCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - fn is_storage_empty(&mut self) -> Result { - Ok(self.seek(B256::ZERO)?.is_none()) - } - - fn set_hashed_address(&mut self, hashed_address: B256) { - self.hashed_address = hashed_address - } -} - -/// MDBX implementation of [`HashedCursor`] for account state. -#[derive(Debug)] -pub struct MdbxAccountCursor { - inner: BlockNumberVersionedCursor, -} - -impl MdbxAccountCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - /// Initializes new `MdbxAccountCursor` - pub const fn new(cursor: Cursor, block_number: u64) -> Self { - Self { inner: BlockNumberVersionedCursor::new(cursor, block_number) } - } -} - -impl HashedCursor for MdbxAccountCursor -where - Cursor: DbCursorRO + DbDupCursorRO + Send, -{ - type Value = Account; - - fn seek(&mut self, key: B256) -> Result, DatabaseError> { - Ok(self.inner.seek(key)?) - } - - fn next(&mut self) -> Result, DatabaseError> { - Ok(self.inner.next()?) - } - - fn reset(&mut self) { - // Database cursors are stateless, no reset needed - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::{StorageValue, models}; - use reth_db::{ - Database, DatabaseEnv, - mdbx::{DatabaseArguments, init_db_for}, - }; - use reth_db_api::{ - cursor::DbDupCursorRW, - transaction::{DbTx, DbTxMut}, - }; - - type Dup<'tx, T> = <::TX as DbTx>::DupCursor; - use reth_trie::{BranchNodeCompact, Nibbles, StoredNibbles}; - use tempfile::TempDir; - - fn setup_db() -> DatabaseEnv { - let tmp = TempDir::new().expect("create tmpdir"); - init_db_for::<_, models::Tables>(tmp, DatabaseArguments::default()).expect("init db") - } - - fn stored(path: Nibbles) -> StoredNibbles { - StoredNibbles(path) - } - - fn node() -> BranchNodeCompact { - BranchNodeCompact::default() - } - - fn append_account_trie( - wtx: &::TXMut, - key: StoredNibbles, - block: u64, - val: Option, - ) { - let mut c = wtx.cursor_dup_write::().expect("dup write cursor"); - let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) }; - c.append_dup(key, vv).expect("append dup"); - } - - fn append_storage_trie( - wtx: &::TXMut, - address: B256, - path: Nibbles, - block: u64, - val: Option, - ) { - let mut c = wtx.cursor_dup_write::().expect("dup write cursor"); - let key = StorageTrieKey::new(address, StoredNibbles(path)); - let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) }; - c.append_dup(key, vv).expect("append dup"); - } - - fn append_hashed_storage( - wtx: &::TXMut, - addr: B256, - slot: B256, - block: u64, - val: Option, - ) { - let mut c = wtx.cursor_dup_write::().expect("dup write"); - let key = HashedStorageKey::new(addr, slot); - let vv = VersionedValue { block_number: block, value: MaybeDeleted(val.map(StorageValue)) }; - c.append_dup(key, vv).expect("append dup"); - } - - fn append_hashed_account( - wtx: &::TXMut, - key: B256, - block: u64, - val: Option, - ) { - let mut c = wtx.cursor_dup_write::().expect("dup write"); - let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) }; - c.append_dup(key, vv).expect("append dup"); - } - - // Open a dup-RO cursor and wrap it in a BlockNumberVersionedCursor with a given bound. - fn version_cursor( - tx: &::TX, - max_block: u64, - ) -> BlockNumberVersionedCursor> { - let cur = tx.cursor_dup_read::().expect("dup ro cursor"); - BlockNumberVersionedCursor::new(cur, max_block) - } - - fn account_trie_cursor( - tx: &'_ ::TX, - max_block: u64, - ) -> MdbxTrieCursor> { - let c = tx.cursor_dup_read::().expect("dup ro cursor"); - // For account trie the address is not used; pass None. - MdbxTrieCursor::new(c, max_block, None) - } - - // Helper: build a Storage trie cursor bound to an address - fn storage_trie_cursor( - tx: &'_ ::TX, - max_block: u64, - address: B256, - ) -> MdbxTrieCursor> { - let c = tx.cursor_dup_read::().expect("dup ro cursor"); - MdbxTrieCursor::new(c, max_block, Some(address)) - } - - fn storage_cursor( - tx: &'_ ::TX, - max_block: u64, - address: B256, - ) -> MdbxStorageCursor> { - let c = tx.cursor_dup_read::().expect("dup ro cursor"); - MdbxStorageCursor::new(c, max_block, address) - } - - fn account_cursor( - tx: &'_ ::TX, - max_block: u64, - ) -> MdbxAccountCursor> { - let c = tx.cursor_dup_read::().expect("dup ro cursor"); - MdbxAccountCursor::new(c, max_block) - } - - // Assert helper: ensure the chosen VersionedValue has the expected block and deletion flag. - fn assert_block( - got: Option<(StoredNibbles, VersionedValue)>, - expected_block: u64, - expect_deleted: bool, - ) { - let (_, vv) = got.expect("expected Some(..)"); - assert_eq!(vv.block_number, expected_block, "wrong block chosen"); - let is_deleted = matches!(vv.value, MaybeDeleted(None)); - assert_eq!(is_deleted, expect_deleted, "tombstone mismatch"); - } - - /// No entry for key → None. - #[test] - fn latest_version_for_key_none_when_key_absent() { - let db = setup_db(); - let tx = db.tx().expect("ro tx"); - let mut cursor = version_cursor(&tx, 100); - - let out = cursor - .latest_version_for_key(stored(Nibbles::default())) - .expect("should not return error"); - assert!(out.is_none(), "absent key must return None"); - } - - /// Exact match at max (live) → pick it. - #[test] - fn latest_version_for_key_picks_value_at_max_if_present() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 10, Some(node())); - append_account_trie(&wtx, k.clone(), 50, Some(node())); // == max - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 50); - - let out = core.latest_version_for_key(k).expect("ok"); - assert_block(out, 50, false); - } - - /// When `seek_by_key_subkey` points to the subkey > max - fallback to the prev. - #[test] - fn latest_version_for_key_picks_latest_below_max_when_next_is_above() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 10, Some(node())); - append_account_trie(&wtx, k.clone(), 30, Some(node())); // expected - append_account_trie(&wtx, k.clone(), 70, Some(node())); // > max - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 50); - - let out = core.latest_version_for_key(k).expect("ok"); - assert_block(out, 30, false); - } - - /// No ≥ max but key exists → use last < max. - #[test] - fn latest_version_for_key_picks_last_below_max_when_none_at_or_above() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 10, Some(node())); - append_account_trie(&wtx, k.clone(), 40, Some(node())); // expected (max=100) - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 100); - - let out = core.latest_version_for_key(k).expect("ok"); - assert_block(out, 40, false); - } - - /// All entries are > max → None. - #[test] - fn latest_version_for_key_none_when_everything_is_above_max() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1.clone(), 60, Some(node())); - append_account_trie(&wtx, k1.clone(), 70, Some(node())); - append_account_trie(&wtx, k2, 40, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 50); - - let out = core.latest_version_for_key(k1).expect("ok"); - assert!(out.is_none(), "no dup ≤ max ⇒ None"); - } - - /// Single dup < max → pick it. - #[test] - fn latest_version_for_key_picks_single_below_max() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 25, Some(node())); // < max - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 50); - - let out = core.latest_version_for_key(k).expect("ok"); - assert_block(out, 25, false); - } - - /// Single dup == max → pick it. - #[test] - fn latest_version_for_key_picks_single_at_max() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 50, Some(node())); // == max - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 50); - - let out = core.latest_version_for_key(k).expect("ok"); - assert_block(out, 50, false); - } - - /// Latest ≤ max is a tombstone → return it (this API doesn't filter). - #[test] - fn latest_version_for_key_returns_tombstone_if_latest_is_deleted() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 10, Some(node())); - append_account_trie(&wtx, k.clone(), 90, None); // latest ≤ max, but deleted - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 100); - - let out = core.latest_version_for_key(k).expect("ok"); - assert_block(out, 90, true); - } - - /// Should skip tombstones and return None when the latest ≤ max is deleted. - #[test] - fn seek_exact_skips_tombstone_returns_none() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 10, Some(node())); - append_account_trie(&wtx, k.clone(), 90, None); // latest ≤ max is tombstoned - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut core = version_cursor(&tx, 100); - - let out = core.seek_exact(k).expect("ok"); - assert!(out.is_none(), "seek_exact must filter out deleted latest value"); - } - - /// Empty table → None. - #[test] - fn seek_empty_returns_none() { - let db = setup_db(); - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 100); - - let out = cur.seek(stored(Nibbles::from_nibbles([0x0A]))).expect("ok"); - assert!(out.is_none()); - } - - /// Start at an existing key whose latest ≤ max is live → returns that key. - #[test] - fn seek_at_live_key_returns_it() { - let db = setup_db(); - let k = stored(Nibbles::from_nibbles([0x0A])); - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k.clone(), 10, Some(node())); - append_account_trie(&wtx, k.clone(), 20, Some(node())); // latest ≤ max - wtx.commit().expect("commit"); - } - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 50); - - let out = cur.seek(k.clone()).expect("ok").expect("some"); - assert_eq!(out.0, k); - } - - /// Start at an existing key whose latest ≤ max is tombstoned → skip to next key with live - /// value. - #[test] - fn seek_skips_tombstoned_key_to_next_live_key() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - // Key 0x10 latest ≤ max is deleted - append_account_trie(&wtx, k1.clone(), 10, Some(node())); - append_account_trie(&wtx, k1.clone(), 20, None); // tombstone at latest ≤ max - // Next key has live - append_account_trie(&wtx, k2.clone(), 5, Some(node())); - wtx.commit().expect("commit"); - } - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 50); - - let out = cur.seek(k1).expect("ok").expect("some"); - assert_eq!(out.0, k2); - } - - /// Start between keys → returns the next key’s live latest ≤ max. - #[test] - fn seek_between_keys_returns_next_key() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0C])); - let k3 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1, 10, Some(node())); - append_account_trie(&wtx, k2.clone(), 10, Some(node())); - wtx.commit().expect("commit"); - } - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 100); - - // Start at 0x15 (between 0x10 and 0x20) - - let out = cur.seek(k3).expect("ok").expect("some"); - assert_eq!(out.0, k2); - } - - /// Start after the last key → None. - #[test] - fn seek_after_last_returns_none() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - let k3 = stored(Nibbles::from_nibbles([0x0C])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1, 10, Some(node())); - append_account_trie(&wtx, k2, 10, Some(node())); - wtx.commit().expect("commit"); - } - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 100); - - let out = cur.seek(k3).expect("ok"); - assert!(out.is_none()); - } - - /// If the first key at-or-after has only versions > max, it is effectively not visible → skip - /// to next. - #[test] - fn seek_skips_keys_with_only_versions_above_max() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1.clone(), 60, Some(node())); - append_account_trie(&wtx, k2.clone(), 40, Some(node())); - wtx.commit().expect("commit"); - } - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 50); - - let out = cur.seek(k1).expect("ok").expect("some"); - assert_eq!(out.0, k2); - } - - /// Start at a key with mixed versions; latest ≤ max is tombstone → skip to next key with live. - #[test] - fn seek_mixed_versions_tombstone_latest_skips_to_next_key() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1.clone(), 10, Some(node())); - append_account_trie(&wtx, k1.clone(), 30, None); - append_account_trie(&wtx, k2.clone(), 5, Some(node())); - wtx.commit().expect("commit"); - } - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 30); - - let out = cur.seek(k1).expect("ok").expect("some"); - assert_eq!(out.0, k2); - } - - /// When not positioned should start from default key and return the first live key. - #[test] - fn next_unpositioned_starts_from_default_returns_first_live() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1.clone(), 10, Some(node())); // first live - append_account_trie(&wtx, k2, 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - // Unpositioned cursor - let mut cur = version_cursor(&tx, 100); - - let out = cur.next().expect("ok").expect("some"); - assert_eq!(out.0, k1); - } - - /// After positioning on a live key via `seek()`, `next()` should advance to the next live key. - #[test] - fn next_advances_from_current_live_to_next_live() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1.clone(), 10, Some(node())); // live - append_account_trie(&wtx, k2.clone(), 10, Some(node())); // next live - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 100); - - // Position at k1 - let _ = cur.seek(k1).expect("ok").expect("some"); - // Next should yield k2 - let out = cur.next().expect("ok").expect("some"); - assert_eq!(out.0, k2); - } - - /// If the next key's latest ≤ max is tombstone, `next()` should skip to the next live key. - #[test] - fn next_skips_tombstoned_key_to_next_live() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); // will be tombstoned at latest ≤ max - let k3 = stored(Nibbles::from_nibbles([0x0C])); // next live - - { - let wtx = db.tx_mut().expect("rw tx"); - // k1 live - append_account_trie(&wtx, k1.clone(), 10, Some(node())); - // k2: latest ≤ max is tombstone - append_account_trie(&wtx, k2.clone(), 10, Some(node())); - append_account_trie(&wtx, k2, 20, None); - // k3 live - append_account_trie(&wtx, k3.clone(), 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 50); - - // Position at k1 - let _ = cur.seek(k1).expect("ok").expect("some"); - // next should skip k2 (tombstoned latest) and return k3 - let out = cur.next().expect("ok").expect("some"); - assert_eq!(out.0, k3); - } - - /// If positioned on the last live key, `next()` should return None (EOF). - #[test] - fn next_returns_none_at_eof() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); - let k2 = stored(Nibbles::from_nibbles([0x0B])); // last key - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, k1, 10, Some(node())); - append_account_trie(&wtx, k2.clone(), 10, Some(node())); // last live - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 100); - - // Position at the last key k2 - let _ = cur.seek(k2).expect("ok").expect("some"); - // `next()` should hit EOF - let out = cur.next().expect("ok"); - assert!(out.is_none()); - } - - /// If the first key has only versions > max, `next()` should skip it and return the next live - /// key. - #[test] - fn next_skips_keys_with_only_versions_above_max() { - let db = setup_db(); - let k1 = stored(Nibbles::from_nibbles([0x0A])); // only > max - let k2 = stored(Nibbles::from_nibbles([0x0B])); // ≤ max live - - { - let wtx = db.tx_mut().expect("rw tx"); - // k1 only above max (max=50) - append_account_trie(&wtx, k1, 60, Some(node())); - // k2 within max - append_account_trie(&wtx, k2.clone(), 40, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - // Unpositioned; `next()` will start from default and walk - let mut cur = version_cursor(&tx, 50); - - let out = cur.next().expect("ok").expect("some"); - assert_eq!(out.0, k2); - } - - /// Empty table: `next()` should return None. - #[test] - fn next_on_empty_returns_none() { - let db = setup_db(); - let tx = db.tx().expect("ro tx"); - let mut cur = version_cursor(&tx, 100); - - let out = cur.next().expect("ok"); - assert!(out.is_none()); - } - - // ----------------- Account trie cursor thin-wrapper checks ----------------- - - #[test] - fn account_seek_exact_live_maps_key_and_value() { - let db = setup_db(); - let k = Nibbles::from_nibbles([0x0A]); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, StoredNibbles(k), 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - - // Build wrapper - let mut cur = account_trie_cursor(&tx, 100); - - // Wrapper should return (Nibbles, BranchNodeCompact) - let out = TrieCursor::seek_exact(&mut cur, k).expect("ok").expect("some"); - assert_eq!(out.0, k); - } - - #[test] - fn account_seek_exact_filters_tombstone() { - let db = setup_db(); - let k = Nibbles::from_nibbles([0x0B]); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, StoredNibbles(k), 5, Some(node())); - append_account_trie(&wtx, StoredNibbles(k), 9, None); // latest ≤ max tombstone - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur = account_trie_cursor(&tx, 10); - - let out = TrieCursor::seek_exact(&mut cur, k).expect("ok"); - assert!(out.is_none(), "account seek_exact must filter tombstone"); - } - - #[test] - fn account_seek_and_next_and_current_roundtrip() { - let db = setup_db(); - let k1 = Nibbles::from_nibbles([0x01]); - let k2 = Nibbles::from_nibbles([0x02]); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_account_trie(&wtx, StoredNibbles(k1), 10, Some(node())); - append_account_trie(&wtx, StoredNibbles(k2), 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur = account_trie_cursor(&tx, 100); - - // seek at k1 - let out1 = TrieCursor::seek(&mut cur, k1).expect("ok").expect("some"); - assert_eq!(out1.0, k1); - - // current should be k1 - let cur_k = TrieCursor::current(&mut cur).expect("ok").expect("some"); - assert_eq!(cur_k, k1); - - // next should move to k2 - let out2 = TrieCursor::next(&mut cur).expect("ok").expect("some"); - assert_eq!(out2.0, k2); - } - - // ----------------- Storage trie cursor thin-wrapper checks ----------------- - - #[test] - fn storage_seek_exact_respects_address_filter() { - let db = setup_db(); - - let addr_a = B256::from([0xAA; 32]); - let addr_b = B256::from([0xBB; 32]); - - let path = Nibbles::from_nibbles([0x0D]); - - { - let wtx = db.tx_mut().expect("rw tx"); - // insert only under B - append_storage_trie(&wtx, addr_b, path, 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - - // Cursor bound to A must not see B’s data - let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); - let out_a = TrieCursor::seek_exact(&mut cur_a, path).expect("ok"); - assert!(out_a.is_none(), "no data for addr A"); - - // Cursor bound to B should see it - let mut cur_b = storage_trie_cursor(&tx, 100, addr_b); - let out_b = TrieCursor::seek_exact(&mut cur_b, path).expect("ok").expect("some"); - assert_eq!(out_b.0, path); - } - - #[test] - fn storage_seek_returns_first_key_for_bound_address() { - let db = setup_db(); - - let addr_a = B256::from([0x11; 32]); - let addr_b = B256::from([0x22; 32]); - - let p1 = Nibbles::from_nibbles([0x01]); - let p2 = Nibbles::from_nibbles([0x02]); - let p3 = Nibbles::from_nibbles([0x03]); - - { - let wtx = db.tx_mut().expect("rw tx"); - // For A: only p2 - append_storage_trie(&wtx, addr_a, p2, 10, Some(node())); - // For B: p1 - append_storage_trie(&wtx, addr_b, p1, 10, Some(node())); - wtx.commit().expect("commit"); - } - - // test seek behaviour - { - let tx = db.tx().expect("ro tx"); - let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); - - // seek at p1: for A there is no p1; the next key >= p1 under A is p2 - let out = TrieCursor::seek(&mut cur_a, p1).expect("ok").expect("some"); - assert_eq!(out.0, p2); - - // seek at p2: exact match - let out = TrieCursor::seek(&mut cur_a, p2).expect("ok").expect("some"); - assert_eq!(out.0, p2); - - // seek at p3: no p3 under A; no next key ≥ p3 under A → None - let out = TrieCursor::seek(&mut cur_a, p3).expect("ok"); - assert!(out.is_none(), "no key ≥ p3 under A"); - } - - // test next behaviour - { - let tx = db.tx().expect("ro tx"); - let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); - - let out = TrieCursor::next(&mut cur_a).expect("ok").expect("some"); - assert_eq!(out.0, p2); - - // next should yield None as there is no further key under A - let out = TrieCursor::next(&mut cur_a).expect("ok"); - assert!(out.is_none(), "no more keys under A"); - - // current should return None - let out = TrieCursor::current(&mut cur_a).expect("ok"); - assert!(out.is_none(), "no current key after EOF"); - } - - // test seek_exact behaviour - { - let tx = db.tx().expect("ro tx"); - let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); - - // seek_exact at p1: no exact match - let out = TrieCursor::seek_exact(&mut cur_a, p1).expect("ok"); - assert!(out.is_none(), "no exact p1 under A"); - - // seek_exact at p2: exact match - let out = TrieCursor::seek_exact(&mut cur_a, p2).expect("ok").expect("some"); - assert_eq!(out.0, p2); - - // seek_exact at p3: no exact match - let out = TrieCursor::seek_exact(&mut cur_a, p3).expect("ok"); - assert!(out.is_none(), "no exact p3 under A"); - } - } - - #[test] - fn storage_next_stops_at_address_boundary() { - let db = setup_db(); - - let addr_a = B256::from([0x33; 32]); - let addr_b = B256::from([0x44; 32]); - - let p1 = Nibbles::from_nibbles([0x05]); // under A - let p2 = Nibbles::from_nibbles([0x06]); // under B (next key overall) - - { - let wtx = db.tx_mut().expect("rw tx"); - append_storage_trie(&wtx, addr_a, p1, 10, Some(node())); - append_storage_trie(&wtx, addr_b, p2, 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur_a = storage_trie_cursor(&tx, 100, addr_a); - - // position at p1 (A) - let _ = TrieCursor::seek_exact(&mut cur_a, p1).expect("ok").expect("some"); - - // next should reach boundary; impl filters different address and returns None - let out = TrieCursor::next(&mut cur_a).expect("ok"); - assert!(out.is_none(), "next() should stop when next key is a different address"); - } - - #[test] - fn storage_current_maps_key() { - let db = setup_db(); - - let addr = B256::from([0x55; 32]); - let p = Nibbles::from_nibbles([0x09]); - - { - let wtx = db.tx_mut().expect("rw tx"); - append_storage_trie(&wtx, addr, p, 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro tx"); - let mut cur = storage_trie_cursor(&tx, 100, addr); - - let _ = TrieCursor::seek_exact(&mut cur, p).expect("ok").expect("some"); - - let now = TrieCursor::current(&mut cur).expect("ok").expect("some"); - assert_eq!(now, p); - } - - #[test] - fn hashed_storage_seek_maps_slot_and_value() { - let db = setup_db(); - let addr = B256::from([0xAA; 32]); - let slot = B256::from([0x10; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_storage(&wtx, addr, slot, 10, Some(U256::from(7))); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = storage_cursor(&tx, 100, addr); - - let (got_slot, got_val) = cur.seek(slot).expect("ok").expect("some"); - assert_eq!(got_slot, slot); - assert_eq!(got_val, U256::from(7)); - } - - #[test] - fn hashed_storage_seek_filters_tombstone() { - let db = setup_db(); - let addr = B256::from([0xAB; 32]); - let slot = B256::from([0x11; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_storage(&wtx, addr, slot, 5, Some(U256::from(1))); - append_hashed_storage(&wtx, addr, slot, 9, None); // latest ≤ max is tombstone - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = storage_cursor(&tx, 10, addr); - - let out = cur.seek(slot).expect("ok"); - assert!(out.is_none(), "wrapper must filter tombstoned latest"); - } - - #[test] - fn hashed_storage_seek_and_next_roundtrip() { - let db = setup_db(); - let addr = B256::from([0xAC; 32]); - let s1 = B256::from([0x01; 32]); - let s2 = B256::from([0x02; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_storage(&wtx, addr, s1, 10, Some(U256::from(11))); - append_hashed_storage(&wtx, addr, s2, 10, Some(U256::from(22))); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = storage_cursor(&tx, 100, addr); - - let (k1, v1) = cur.seek(s1).expect("ok").expect("some"); - assert_eq!((k1, v1), (s1, U256::from(11))); - - let (k2, v2) = cur.next().expect("ok").expect("some"); - assert_eq!((k2, v2), (s2, U256::from(22))); - } - - #[test] - fn hashed_storage_address_boundary() { - let db = setup_db(); - let addr1 = B256::from([0xAC; 32]); - let addr2 = B256::from([0xAD; 32]); - let s1 = B256::from([0x01; 32]); - let s2 = B256::from([0x02; 32]); - let s3 = B256::from([0x03; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_storage(&wtx, addr1, s1, 10, Some(U256::from(11))); - append_hashed_storage(&wtx, addr1, s2, 10, Some(U256::from(22))); - wtx.commit().expect("commit"); - } - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_storage(&wtx, addr2, s1, 10, Some(U256::from(33))); - append_hashed_storage(&wtx, addr2, s2, 10, Some(U256::from(44))); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = storage_cursor(&tx, 100, addr1); - - let (k1, v1) = cur.next().expect("ok").expect("some"); - assert_eq!((k1, v1), (s1, U256::from(11))); - - let (k2, v2) = cur.next().expect("ok").expect("some"); - assert_eq!((k2, v2), (s2, U256::from(22))); - - let out = cur.next().expect("ok"); - assert!(out.is_none(), "should stop at address boundary"); - - let (k1, v1) = cur.seek(s1).expect("ok").expect("some"); - assert_eq!((k1, v1), (s1, U256::from(11))); - - let (k2, v2) = cur.seek(s2).expect("ok").expect("some"); - assert_eq!((k2, v2), (s2, U256::from(22))); - - let out = cur.seek(s3).expect("ok"); - assert!(out.is_none(), "should not see keys from other address"); - } - - #[test] - fn hashed_account_seek_maps_key_and_value() { - let db = setup_db(); - let key = B256::from([0x20; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_account(&wtx, key, 10, Some(Account::default())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = account_cursor(&tx, 100); - - let (got_key, _acc) = cur.seek(key).expect("ok").expect("some"); - assert_eq!(got_key, key); - } - - #[test] - fn hashed_account_seek_filters_tombstone() { - let db = setup_db(); - let key = B256::from([0x21; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_account(&wtx, key, 5, Some(Account::default())); - append_hashed_account(&wtx, key, 9, None); // latest ≤ max is tombstone - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = account_cursor(&tx, 10); - - let out = cur.seek(key).expect("ok"); - assert!(out.is_none(), "wrapper must filter tombstoned latest"); - } - - #[test] - fn hashed_account_seek_and_next_roundtrip() { - let db = setup_db(); - let k1 = B256::from([0x01; 32]); - let k2 = B256::from([0x02; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - append_hashed_account(&wtx, k1, 10, Some(Account::default())); - append_hashed_account(&wtx, k2, 10, Some(Account::default())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - let mut cur = account_cursor(&tx, 100); - - let (got1, _) = cur.seek(k1).expect("ok").expect("some"); - assert_eq!(got1, k1); - - let (got2, _) = cur.next().expect("ok").expect("some"); - assert_eq!(got2, k2); - } - - /// Regression test: `MdbxStorageCursor` `next()` should work without explicit `seek()` - /// when cursor is constructed for a non-first key. - /// - /// Bug: When a storage cursor is created for a specific address (e.g., 0x02), - /// calling `next()` without first calling `seek()` returns None instead of the first - /// slot for that address. This only manifests when the address is not the first - /// in the table. - #[test] - fn storage_cursor_next_without_seek_for_non_first_address() { - let db = setup_db(); - let addr1 = B256::from([0x01; 32]); // First address - let addr2 = B256::from([0x02; 32]); // Second address (non-first) - let slot1 = B256::from([0x11; 32]); - let slot2 = B256::from([0x12; 32]); - - { - let wtx = db.tx_mut().expect("rw"); - // Add storage for first address - append_hashed_storage(&wtx, addr1, slot1, 10, Some(U256::from(100))); - - // Add storage for second address - append_hashed_storage(&wtx, addr2, slot2, 10, Some(U256::from(200))); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - - // Test with addr1 (first address) - this typically works - let mut cur1 = storage_cursor(&tx, 100, addr1); - let result1 = cur1.next().expect("ok"); - assert!(result1.is_some(), "next() should return data for first address without seek()"); - if let Some((key, val)) = result1 { - assert_eq!(key, slot1); - assert_eq!(val, U256::from(100)); - } - - // Test with addr2 (non-first address) - this demonstrates the bug fix - let mut cur2 = storage_cursor(&tx, 100, addr2); - let result2_without_seek = cur2.next().expect("ok"); - - assert!( - result2_without_seek.is_some(), - "next() should return data for non-first address without seek()" - ); - if let Some((key, val)) = result2_without_seek { - assert_eq!(key, slot2); - assert_eq!(val, U256::from(200)); - } - - // Verify that seek() works correctly - let mut cur3 = storage_cursor(&tx, 100, addr2); - let result3_with_seek = cur3.seek(slot2).expect("ok"); - assert!(result3_with_seek.is_some(), "seek() should find the slot for addr2"); - if let Some((key, val)) = result3_with_seek { - assert_eq!(key, slot2); - assert_eq!(val, U256::from(200)); - } - } - - /// Regression test: `MdbxTrieCursor` `next()` should work without `seek()` - /// for non-first addresses. - #[test] - fn storage_trie_cursor_next_without_seek_for_non_first_address() { - let db = setup_db(); - let addr1 = B256::from([0x01; 32]); - let addr2 = B256::from([0x02; 32]); - let path1 = Nibbles::from_nibbles([0x0A]); - let path2 = Nibbles::from_nibbles([0x0B]); - - { - let wtx = db.tx_mut().expect("rw"); - append_storage_trie(&wtx, addr1, path1, 10, Some(node())); - append_storage_trie(&wtx, addr2, path2, 10, Some(node())); - wtx.commit().expect("commit"); - } - - let tx = db.tx().expect("ro"); - - // Test addr1 (first) - works - let mut cur1 = storage_trie_cursor(&tx, 100, addr1); - let result1 = TrieCursor::next(&mut cur1).expect("ok"); - assert!(result1.is_some()); - assert_eq!(result1.unwrap().0, path1); - - // Test addr2 (non-first) - should also work now - let mut cur2 = storage_trie_cursor(&tx, 100, addr2); - let result2 = TrieCursor::next(&mut cur2).expect("ok"); - assert!(result2.is_some(), "next() should work for non-first address without seek()"); - assert_eq!(result2.unwrap().0, path2); - } -} diff --git a/vendor/reth-optimism-trie/src/db/mod.rs b/vendor/reth-optimism-trie/src/db/mod.rs deleted file mode 100644 index 5f611e9..0000000 --- a/vendor/reth-optimism-trie/src/db/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! MDBX implementation of [`OpProofsStore`](crate::OpProofsStore). -//! -//! This module provides a complete MDBX implementation of the -//! [`OpProofsStore`](crate::OpProofsStore) trait. It uses the [`reth_db`] -//! crate for database interactions and defines the necessary tables and models for storing trie -//! branches, accounts, and storage leaves. - -mod models; -pub use models::*; - -mod store; -pub use store::{MdbxProofsProvider, MdbxProofsStorage}; - -mod cursor; -pub use cursor::{ - BlockNumberVersionedCursor, MdbxAccountCursor, MdbxStorageCursor, MdbxTrieCursor, -}; - -mod store_v2; -pub use store_v2::{ - MdbxProofsProviderV2, MdbxProofsStorageV2, V2AccountCursor, V2AccountTrieCursor, - V2AccountTrieSnapshotCursor, V2HashedAccountSnapshotCursor, V2HashedStorageSnapshotCursor, - V2StorageCursor, V2StorageTrieCursor, V2StorageTrieSnapshotCursor, -}; diff --git a/vendor/reth-optimism-trie/src/db/models/block.rs b/vendor/reth-optimism-trie/src/db/models/block.rs deleted file mode 100644 index d2aeff2..0000000 --- a/vendor/reth-optimism-trie/src/db/models/block.rs +++ /dev/null @@ -1,79 +0,0 @@ -use alloy_eips::BlockNumHash; -use alloy_primitives::B256; -use bytes::BufMut; -use derive_more::{From, Into}; -use reth_codecs::DecompressError; -use reth_db::{ - DatabaseError, - table::{Compress, Decompress}, -}; -use serde::{Deserialize, Serialize}; - -/// Wrapper for block number and block hash tuple to implement [`Compress`]/[`Decompress`]. -/// -/// Used for storing block metadata (number + hash). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, From, Into)] -pub struct BlockNumberHash(BlockNumHash); - -impl Compress for BlockNumberHash { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - // Encode block number (8 bytes, big-endian) + hash (32 bytes) = 40 bytes total - buf.put_u64(self.0.number); - buf.put_slice(self.0.hash.as_slice()); - } -} - -impl Decompress for BlockNumberHash { - fn decompress(value: &[u8]) -> Result { - if value.len() != 40 { - return Err(DecompressError::new(DatabaseError::Decode)); - } - - let number = u64::from_be_bytes( - value[..8].try_into().map_err(|_| DecompressError::new(DatabaseError::Decode))?, - ); - let hash = B256::from_slice(&value[8..40]); - - Ok(Self(BlockNumHash { number, hash })) - } -} - -impl BlockNumberHash { - /// Create new instance. - pub const fn new(number: u64, hash: B256) -> Self { - Self(BlockNumHash { number, hash }) - } - - /// Get the block number. - pub const fn number(&self) -> u64 { - self.0.number - } - - /// Get the block hash. - pub const fn hash(&self) -> &B256 { - &self.0.hash - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::B256; - - #[test] - fn test_block_number_hash_roundtrip() { - let test_cases = vec![ - BlockNumberHash::new(0, B256::ZERO), - BlockNumberHash::new(42, B256::repeat_byte(0xaa)), - BlockNumberHash::new(u64::MAX, B256::repeat_byte(0xff)), - ]; - - for original in test_cases { - let compressed = original.compress(); - let decompressed = BlockNumberHash::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/change_set.rs b/vendor/reth-optimism-trie/src/db/models/change_set.rs deleted file mode 100644 index 248d060..0000000 --- a/vendor/reth-optimism-trie/src/db/models/change_set.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::db::{HashedStorageKey, StorageTrieKey}; -use alloy_primitives::B256; -use reth_db::{ - DatabaseError, - table::{self, Decode, Encode}, -}; -use reth_trie_common::StoredNibbles; -use serde::{Deserialize, Serialize}; - -/// The keys of the entries in the history tables. -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct ChangeSet { - /// Keys changed in [`AccountTrieHistory`](super::AccountTrieHistory) table. - pub account_trie_keys: Vec, - /// Keys changed in [`StorageTrieHistory`](super::StorageTrieHistory) table. - pub storage_trie_keys: Vec, - /// Keys changed in [`HashedAccountHistory`](super::HashedAccountHistory) table. - pub hashed_account_keys: Vec, - /// Keys changed in [`HashedStorageHistory`](super::HashedStorageHistory) table. - pub hashed_storage_keys: Vec, -} - -impl table::Encode for ChangeSet { - type Encoded = Vec; - - fn encode(self) -> Self::Encoded { - bincode::serde::encode_to_vec(&self, bincode::config::standard()) - .expect("ChangeSet serialization should not fail") - } -} - -impl table::Decode for ChangeSet { - fn decode(value: &[u8]) -> Result { - bincode::serde::decode_from_slice(value, bincode::config::standard()) - .map(|(v, _)| v) - .map_err(|_| DatabaseError::Decode) - } -} - -impl table::Compress for ChangeSet { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - let encoded = self.clone().encode(); - buf.put_slice(&encoded); - } -} - -impl table::Decompress for ChangeSet { - fn decompress(value: &[u8]) -> Result { - Self::decode(value).map_err(reth_codecs::DecompressError::new) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::B256; - use reth_db::table::{Compress, Decompress}; - - #[test] - fn test_encode_decode_empty_change_set() { - let change_set = ChangeSet { - account_trie_keys: vec![], - storage_trie_keys: vec![], - hashed_account_keys: vec![], - hashed_storage_keys: vec![], - }; - - let encoded = change_set.clone().encode(); - let decoded = ChangeSet::decode(&encoded).expect("Failed to decode"); - assert_eq!(change_set, decoded); - } - - #[test] - fn test_encode_decode_populated_change_set() { - let account_key = StoredNibbles::from(vec![1, 2, 3, 4]); - let storage_key = StorageTrieKey { - hashed_address: B256::repeat_byte(0x11), - path: StoredNibbles::from(vec![5, 6, 7, 8]), - }; - let hashed_storage_key = HashedStorageKey { - hashed_address: B256::repeat_byte(0x22), - hashed_storage_key: B256::repeat_byte(0x33), - }; - - let change_set = ChangeSet { - account_trie_keys: vec![account_key], - storage_trie_keys: vec![storage_key], - hashed_account_keys: vec![B256::repeat_byte(0x44)], - hashed_storage_keys: vec![hashed_storage_key], - }; - - let encoded = change_set.clone().encode(); - let decoded = ChangeSet::decode(&encoded).expect("Failed to decode"); - assert_eq!(change_set, decoded); - } - - #[test] - fn test_decode_invalid_data() { - let invalid_data = vec![0xFF; 32]; - let result = ChangeSet::decode(&invalid_data); - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), DatabaseError::Decode)); - } - - #[test] - fn test_compress_decompress() { - let change_set = ChangeSet { - account_trie_keys: vec![StoredNibbles::from(vec![1, 2, 3])], - storage_trie_keys: vec![StorageTrieKey { - hashed_address: B256::ZERO, - path: StoredNibbles::from(vec![4, 5, 6]), - }], - hashed_account_keys: vec![B256::ZERO], - hashed_storage_keys: vec![HashedStorageKey { - hashed_address: B256::ZERO, - hashed_storage_key: B256::repeat_byte(0x42), - }], - }; - - let mut buf = Vec::new(); - change_set.compress_to_buf(&mut buf); - - let decompressed = ChangeSet::decompress(&buf).expect("Failed to decompress"); - assert_eq!(change_set, decompressed); - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/key.rs b/vendor/reth-optimism-trie/src/db/models/key.rs deleted file mode 100644 index 1a414f3..0000000 --- a/vendor/reth-optimism-trie/src/db/models/key.rs +++ /dev/null @@ -1,476 +0,0 @@ -use alloy_primitives::{B256, BlockNumber}; -use reth_db::{ - DatabaseError, - models::sharded_key::ShardedKey, - table::{Decode, Encode}, -}; -use reth_trie_common::{Nibbles, StoredNibbles}; -use serde::{Deserialize, Serialize}; - -/// Nibble-subkey layout shared by [`AccountTrieShardedKey`] and [`StorageTrieShardedKey`]: 64 -/// nibble bytes right-padded with `0x00`, followed by a 1-byte length suffix. Padding the path -/// to a fixed width and placing nibble bytes ahead of the length byte makes MDBX's byte-wise -/// sort agree with `Nibbles`' lex-by-nibble order. -const NIBBLE_SUBKEY_LEN: usize = 65; -/// Byte length of an encoded [`AccountTrieShardedKey`]: nibble subkey + 8 block number bytes. -const ACCOUNT_TRIE_SHARDED_KEY_LEN: usize = NIBBLE_SUBKEY_LEN + 8; -/// Byte length of an encoded [`StorageTrieShardedKey`]: hashed address + nibble subkey + block. -const STORAGE_TRIE_SHARDED_KEY_LEN: usize = 32 + NIBBLE_SUBKEY_LEN + 8; - -/// Encode a nibble path into the fixed-size `[u8; 65]` subkey layout: 64 path bytes right-padded -/// with `0x00`, followed by the actual nibble count in byte 64. -fn encode_nibble_subkey(nibbles: &StoredNibbles) -> [u8; NIBBLE_SUBKEY_LEN] { - debug_assert!(nibbles.0.len() <= 64, "nibble path exceeds 64"); - let mut buf = [0u8; NIBBLE_SUBKEY_LEN]; - for (i, nibble) in nibbles.0.iter().enumerate() { - buf[i] = nibble; - } - buf[64] = nibbles.0.len() as u8; - buf -} - -/// Inverse of [`encode_nibble_subkey`]: read the length byte at position 64 and reconstruct the -/// [`StoredNibbles`] from the first `len` path bytes. -fn decode_nibble_subkey(buf: &[u8; NIBBLE_SUBKEY_LEN]) -> StoredNibbles { - let len = buf[64] as usize; - StoredNibbles::from(Nibbles::from_nibbles_unchecked(&buf[..len])) -} - -/// Sharded key for hashed accounts history, keyed by `(hashed_address, block)`. -/// -/// Encoded as a fixed-size 40-byte buffer: -/// -/// ```text -/// [hashed_address: 32 bytes] ++ [block_number: 8 BE bytes] -/// ``` -/// -/// MDBX's byte-wise sort groups entries by address (full-width hash, so no padding needed), -/// then orders ascending by block number. -#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] -pub struct HashedAccountShardedKey(pub ShardedKey); - -impl HashedAccountShardedKey { - /// Create a new sharded key for a hashed account. - pub const fn new(key: B256, highest_block_number: u64) -> Self { - Self(ShardedKey::new(key, highest_block_number)) - } -} - -impl Encode for HashedAccountShardedKey { - type Encoded = [u8; 40]; // 32 (B256) + 8 (BlockNumber) - - fn encode(self) -> Self::Encoded { - let mut buf = [0u8; 40]; - buf[..32].copy_from_slice(self.0.key.as_slice()); - buf[32..].copy_from_slice(&self.0.highest_block_number.to_be_bytes()); - buf - } -} - -impl Decode for HashedAccountShardedKey { - fn decode(value: &[u8]) -> Result { - if value.len() != 40 { - return Err(DatabaseError::Decode); - } - let key = B256::from_slice(&value[..32]); - let highest_block_number = - u64::from_be_bytes(value[32..].try_into().map_err(|_| DatabaseError::Decode)?); - Ok(Self(ShardedKey::new(key, highest_block_number))) - } -} - -/// Sharded key for hashed storage history, keyed by `(hashed_address, storage_key, block)`. -/// -/// Encoded as a 72-byte buffer: -/// -/// ```text -/// [hashed_address: 32 bytes] ++ [storage_key: 32 bytes] ++ [block_number: 8 BE bytes] -/// ``` -/// -/// MDBX cursor walks group entries by address, then by storage key, then ascending by block. -#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] -pub struct HashedStorageShardedKey { - /// The hashed address of the account owning the storage. - pub hashed_address: B256, - /// The sharded key combining the storage key and sharded block number. - pub sharded_key: ShardedKey, -} - -impl Encode for HashedStorageShardedKey { - type Encoded = Vec; - fn encode(self) -> Self::Encoded { - let mut buf = Vec::with_capacity(32 + 32 + 8); - buf.extend_from_slice(self.hashed_address.as_slice()); - // ShardedKey: Key (32 bytes) + BlockNumber (8 bytes BE) - buf.extend_from_slice(self.sharded_key.key.as_slice()); - buf.extend_from_slice(&self.sharded_key.highest_block_number.to_be_bytes()); - buf - } -} - -impl Decode for HashedStorageShardedKey { - fn decode(value: &[u8]) -> Result { - // 32 (Addr) + 32 (Key) + 8 (Block) = 72 bytes - if value.len() < 72 { - return Err(DatabaseError::Decode); - } - let (addr, rest) = value.split_at(32); - let hashed_address = B256::from_slice(addr); - let key = B256::from_slice(&rest[..32]); - let highest_block_number = - u64::from_be_bytes(rest[32..40].try_into().map_err(|_| DatabaseError::Decode)?); - Ok(Self { hashed_address, sharded_key: ShardedKey::new(key, highest_block_number) }) - } -} - -/// Sharded key for account trie history. -/// -/// Encoded as a fixed-size 73-byte buffer. The nibble portion is right-padded with `0x00` and -/// followed by a length byte so MDBX's byte-wise sort agrees with `Nibbles`' lex-by-nibble -/// order: -/// -/// ```text -/// [nibbles: 64 bytes, right-padded 0x00] ++ [length: 1 byte] ++ [block_number: 8 BE bytes] -/// ``` -/// -/// See [`StorageTrieShardedKey`] for the same layout extended with a per-account address. -#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] -pub struct AccountTrieShardedKey { - /// Trie path as nibbles. - pub key: StoredNibbles, - /// Highest block number in this shard (or `u64::MAX` for the sentinel). - pub highest_block_number: u64, -} - -impl AccountTrieShardedKey { - /// Create a new sharded key for an account trie path. - pub const fn new(key: StoredNibbles, highest_block_number: u64) -> Self { - Self { key, highest_block_number } - } -} - -impl Encode for AccountTrieShardedKey { - type Encoded = [u8; ACCOUNT_TRIE_SHARDED_KEY_LEN]; - - fn encode(self) -> Self::Encoded { - let mut buf = [0u8; ACCOUNT_TRIE_SHARDED_KEY_LEN]; - buf[..NIBBLE_SUBKEY_LEN].copy_from_slice(&encode_nibble_subkey(&self.key)); - buf[NIBBLE_SUBKEY_LEN..].copy_from_slice(&self.highest_block_number.to_be_bytes()); - buf - } -} - -impl Decode for AccountTrieShardedKey { - fn decode(value: &[u8]) -> Result { - let bytes: &[u8; ACCOUNT_TRIE_SHARDED_KEY_LEN] = - value.try_into().map_err(|_| DatabaseError::Decode)?; - let nibble_buf: &[u8; NIBBLE_SUBKEY_LEN] = - bytes[..NIBBLE_SUBKEY_LEN].try_into().map_err(|_| DatabaseError::Decode)?; - let key = decode_nibble_subkey(nibble_buf); - let highest_block_number = u64::from_be_bytes( - bytes[NIBBLE_SUBKEY_LEN..].try_into().map_err(|_| DatabaseError::Decode)?, - ); - Ok(Self { key, highest_block_number }) - } -} - -/// Sharded key for storage trie history, keyed by `(hashed_address, nibble_path, block)`. -/// -/// Encoded as a fixed-size 105-byte buffer; the 32-byte hashed address sits at position 0 so -/// MDBX cursor walks naturally group all entries for the same account, then sort by trie path -/// (lex-by-nibble) and block number — see [`AccountTrieShardedKey`] for the nibble-subkey -/// rationale. -/// -/// ```text -/// [hashed_address: 32 bytes] ++ [nibbles: 64 bytes, right-padded 0x00] -/// ++ [length: 1 byte] -/// ++ [block_number: 8 BE bytes] -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] -pub struct StorageTrieShardedKey { - /// The hashed address of the account owning the storage trie. - pub hashed_address: B256, - /// The trie path (nibbles). - pub key: StoredNibbles, - /// Highest block number in this shard (or `u64::MAX` for the sentinel). - pub highest_block_number: u64, -} - -impl StorageTrieShardedKey { - /// Create a new storage trie sharded key. - pub const fn new(hashed_address: B256, key: StoredNibbles, highest_block_number: u64) -> Self { - Self { hashed_address, key, highest_block_number } - } -} - -impl Encode for StorageTrieShardedKey { - type Encoded = [u8; STORAGE_TRIE_SHARDED_KEY_LEN]; - - fn encode(self) -> Self::Encoded { - let mut buf = [0u8; STORAGE_TRIE_SHARDED_KEY_LEN]; - buf[..32].copy_from_slice(self.hashed_address.as_slice()); - buf[32..32 + NIBBLE_SUBKEY_LEN].copy_from_slice(&encode_nibble_subkey(&self.key)); - buf[32 + NIBBLE_SUBKEY_LEN..].copy_from_slice(&self.highest_block_number.to_be_bytes()); - buf - } -} - -impl Decode for StorageTrieShardedKey { - fn decode(value: &[u8]) -> Result { - let bytes: &[u8; STORAGE_TRIE_SHARDED_KEY_LEN] = - value.try_into().map_err(|_| DatabaseError::Decode)?; - let hashed_address = B256::from_slice(&bytes[..32]); - let nibble_buf: &[u8; NIBBLE_SUBKEY_LEN] = - bytes[32..32 + NIBBLE_SUBKEY_LEN].try_into().map_err(|_| DatabaseError::Decode)?; - let key = decode_nibble_subkey(nibble_buf); - let highest_block_number = u64::from_be_bytes( - bytes[32 + NIBBLE_SUBKEY_LEN..].try_into().map_err(|_| DatabaseError::Decode)?, - ); - Ok(Self { hashed_address, key, highest_block_number }) - } -} - -/// Key for the storage `ChangeSets` table, keyed by `(block, hashed_address)`. -/// -/// Encoded as a fixed-size 40-byte buffer: -/// -/// ```text -/// [block_number: 8 BE bytes] ++ [hashed_address: 32 bytes] -/// ``` -/// -/// Block goes first so MDBX cursor walks iterate change sets in block order, with -/// address-grouping within each block. Replaces upstream `BlockNumberAddress`, which keyed by -/// the unhashed account address. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] -pub struct BlockNumberHashedAddress(pub (BlockNumber, B256)); - -impl Encode for BlockNumberHashedAddress { - type Encoded = [u8; 40]; // 8 + 32 - fn encode(self) -> Self::Encoded { - let mut buf = [0u8; 40]; - buf[..8].copy_from_slice(&self.0.0.to_be_bytes()); - buf[8..].copy_from_slice(self.0.1.as_slice()); - buf - } -} - -impl Decode for BlockNumberHashedAddress { - fn decode(value: &[u8]) -> Result { - if value.len() < 40 { - return Err(DatabaseError::Decode); - } - let block_num = u64::from_be_bytes(value[..8].try_into().unwrap()); - let hash = B256::from_slice(&value[8..40]); - Ok(Self((block_num, hash))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_db::table::{Decode, Encode}; - use reth_trie_common::Nibbles; - - #[test] - fn hashed_account_sharded_key_roundtrip() { - let original = HashedAccountShardedKey::new(B256::repeat_byte(0xaa), 42); - let decoded = HashedAccountShardedKey::decode(&original.clone().encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn hashed_storage_sharded_key_roundtrip() { - let original = HashedStorageShardedKey { - hashed_address: B256::repeat_byte(0xaa), - sharded_key: ShardedKey::new(B256::repeat_byte(0xbb), 100), - }; - let decoded = HashedStorageShardedKey::decode(&original.clone().encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn account_trie_sharded_key_roundtrip() { - let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0a, 0x0b, 0x0c])); - let original = AccountTrieShardedKey::new(nibbles, 500); - let decoded = AccountTrieShardedKey::decode(&original.clone().encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn account_trie_sharded_key_roundtrip_empty_nibbles() { - let original = - AccountTrieShardedKey::new(StoredNibbles::from(Nibbles::default()), u64::MAX); - let decoded = AccountTrieShardedKey::decode(&original.clone().encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn storage_trie_sharded_key_roundtrip() { - let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x02])); - let original = StorageTrieShardedKey::new(B256::repeat_byte(0xcc), nibbles, 999); - let decoded = StorageTrieShardedKey::decode(&original.clone().encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn storage_trie_sharded_key_roundtrip_empty_nibbles() { - let original = StorageTrieShardedKey::new( - B256::repeat_byte(0xdd), - StoredNibbles::from(Nibbles::default()), - 0, - ); - let decoded = StorageTrieShardedKey::decode(&original.clone().encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn block_number_hashed_address_roundtrip() { - let original = BlockNumberHashedAddress((42, B256::repeat_byte(0xdd))); - let decoded = BlockNumberHashedAddress::decode(&original.encode()).unwrap(); - assert_eq!(original, decoded); - } - - #[test] - fn account_trie_shorter_nibbles_sort_before_longer() { - let key_a = AccountTrieShardedKey::new( - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01])), - 256, - ); - let key_b = AccountTrieShardedKey::new( - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x00])), - 1, - ); - - assert!( - key_a.encode() < key_b.encode(), - "shorter nibble path must sort before longer in encoded form" - ); - } - - #[test] - fn account_trie_same_nibbles_ordered_by_block() { - let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0a, 0x0b])); - - let lo = AccountTrieShardedKey::new(nibbles.clone(), 10); - let hi = AccountTrieShardedKey::new(nibbles, 20); - - assert!( - lo.encode() < hi.encode(), - "same nibbles: lower block must sort before higher block" - ); - } - - #[test] - fn account_trie_nibbles_resembling_block_bytes_are_unambiguous() { - let key_a = AccountTrieShardedKey::new( - StoredNibbles::from(Nibbles::from_nibbles_unchecked([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - ])), - 1, - ); - let key_b = AccountTrieShardedKey::new(StoredNibbles::from(Nibbles::default()), 5); - - let enc_a = key_a.encode(); - let enc_b = key_b.encode(); - assert_ne!(enc_a, enc_b, "different logical keys must never produce identical encodings"); - // Empty nibbles (all-zero padded path) must sort before non-empty paths whose first - // non-zero nibble is greater than zero. - assert!(enc_b < enc_a, "empty nibbles must sort before non-empty nibbles"); - } - - /// Regression: under the old length-prefixed encoding, the length byte at position 0 - /// dominated MDBX's byte-wise sort, so `[0x05]` (len=1) would have sorted before - /// `[0x01, 0x05]` (len=2) — opposite of the logical nibble lex order. The fixed-size - /// layout places nibble bytes first, so the actual path content drives ordering. - #[test] - fn account_trie_sort_follows_nibble_lex_order_not_length() { - let key_short = AccountTrieShardedKey::new( - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x05])), - 0, - ); - let key_long = AccountTrieShardedKey::new( - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x05])), - 0, - ); - - assert!( - key_long.encode() < key_short.encode(), - "[0x01, 0x05] must sort before [0x05] because nibble 0x01 < 0x05", - ); - } - - /// Storage-trie variant of the same regression. - #[test] - fn storage_trie_sort_follows_nibble_lex_order_not_length() { - let addr = B256::repeat_byte(0x44); - let key_short = StorageTrieShardedKey::new( - addr, - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x05])), - 0, - ); - let key_long = StorageTrieShardedKey::new( - addr, - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x01, 0x05])), - 0, - ); - - assert!( - key_long.encode() < key_short.encode(), - "[0x01, 0x05] must sort before [0x05] within the same address", - ); - } - - #[test] - fn storage_trie_shorter_nibbles_sort_before_longer() { - let addr = B256::repeat_byte(0x11); - - let key_a = StorageTrieShardedKey::new( - addr, - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0f])), - 256, - ); - let key_b = StorageTrieShardedKey::new( - addr, - StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0f, 0x00])), - 1, - ); - - assert!( - key_a.encode() < key_b.encode(), - "shorter nibble path must sort before longer in encoded form" - ); - } - - #[test] - fn storage_trie_same_nibbles_ordered_by_block() { - let addr = B256::repeat_byte(0x22); - let nibbles = StoredNibbles::from(Nibbles::from_nibbles_unchecked([0x0a])); - - let lo = StorageTrieShardedKey::new(addr, nibbles.clone(), 10); - let hi = StorageTrieShardedKey::new(addr, nibbles, 20); - - assert!( - lo.encode() < hi.encode(), - "same nibbles: lower block must sort before higher block" - ); - } - - #[test] - fn storage_trie_nibbles_resembling_block_bytes_are_unambiguous() { - let addr = B256::repeat_byte(0x33); - - let key_a = StorageTrieShardedKey::new( - addr, - StoredNibbles::from(Nibbles::from_nibbles_unchecked([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - ])), - 1, - ); - let key_b = StorageTrieShardedKey::new(addr, StoredNibbles::from(Nibbles::default()), 5); - - let enc_a = key_a.encode(); - let enc_b = key_b.encode(); - assert_ne!(enc_a, enc_b, "different logical keys must never produce identical encodings"); - assert!(enc_b < enc_a, "empty nibbles must sort before non-empty nibbles"); - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/kv.rs b/vendor/reth-optimism-trie/src/db/models/kv.rs deleted file mode 100644 index e42bea5..0000000 --- a/vendor/reth-optimism-trie/src/db/models/kv.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! KV conversion helpers for trie history tables. - -use crate::db::{ - AccountTrieHistory, HashedAccountHistory, HashedStorageHistory, HashedStorageKey, MaybeDeleted, - StorageTrieHistory, StorageTrieKey, StorageValue, VersionedValue, -}; -use alloy_primitives::B256; -use reth_db::table::{DupSort, Table}; -use reth_primitives_traits::Account; -use reth_trie_common::{BranchNodeCompact, Nibbles, StoredNibbles}; - -/// Helper to convert inputs into a table key or kv pair. -pub trait IntoKV { - /// Convert `self` into the table key. - fn into_key(self) -> Tab::Key; - - /// Convert `self` into kv for the given `block_number`. - fn into_kv(self, block_number: u64) -> (Tab::Key, Tab::Value); -} - -impl IntoKV for (Nibbles, Option) { - fn into_key(self) -> StoredNibbles { - StoredNibbles::from(self.0) - } - - fn into_kv(self, block_number: u64) -> (StoredNibbles, VersionedValue) { - let (path, node) = self; - (StoredNibbles::from(path), VersionedValue { block_number, value: MaybeDeleted(node) }) - } -} - -impl IntoKV for (B256, Nibbles, Option) { - fn into_key(self) -> StorageTrieKey { - let (hashed_address, path, _) = self; - StorageTrieKey::new(hashed_address, StoredNibbles::from(path)) - } - fn into_kv(self, block_number: u64) -> (StorageTrieKey, VersionedValue) { - let (hashed_address, path, node) = self; - ( - StorageTrieKey::new(hashed_address, StoredNibbles::from(path)), - VersionedValue { block_number, value: MaybeDeleted(node) }, - ) - } -} - -impl IntoKV for (B256, Option) { - fn into_key(self) -> B256 { - self.0 - } - fn into_kv(self, block_number: u64) -> (B256, VersionedValue) { - let (hashed_address, account) = self; - (hashed_address, VersionedValue { block_number, value: MaybeDeleted(account) }) - } -} - -impl IntoKV for (B256, B256, Option) { - fn into_key(self) -> HashedStorageKey { - let (hashed_address, hashed_storage_key, _) = self; - HashedStorageKey::new(hashed_address, hashed_storage_key) - } - fn into_kv(self, block_number: u64) -> (HashedStorageKey, VersionedValue) { - let (hashed_address, hashed_storage_key, value) = self; - ( - HashedStorageKey::new(hashed_address, hashed_storage_key), - VersionedValue { block_number, value: MaybeDeleted(value) }, - ) - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/mod.rs b/vendor/reth-optimism-trie/src/db/models/mod.rs deleted file mode 100644 index 9ab3e0a..0000000 --- a/vendor/reth-optimism-trie/src/db/models/mod.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! MDBX implementation of [`OpProofsStore`](crate::OpProofsStore). -//! -//! This module provides a complete MDBX implementation of the -//! [`OpProofsStore`](crate::OpProofsStore) trait. It uses the [`reth_db`] crate for -//! database interactions and defines the necessary tables and models for storing trie branches, -//! accounts, and storage leaves. - -mod block; -pub use block::*; -mod version; -pub use version::*; -mod storage; -pub use storage::*; -mod change_set; -pub use change_set::*; -pub mod kv; -pub use kv::*; -mod key; -pub use key::*; -mod value; -pub use value::*; -mod snapshot; -pub use snapshot::*; - -use alloy_primitives::{B256, BlockNumber}; -use reth_db::{ - BlockNumberList, TableSet, TableType, TableViewer, - table::{DupSort, TableInfo}, - tables, -}; -use reth_primitives_traits::{Account, StorageEntry}; -use reth_trie_common::{BranchNodeCompact, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey}; -use std::fmt; - -tables! { - /// Stores historical branch nodes for the account state trie. - /// - /// Each entry maps a compact-encoded trie path (`StoredNibbles`) to its versioned branch node. - /// Multiple versions of the same node are stored using the block number as a subkey. - table AccountTrieHistory { - type Key = StoredNibbles; - type Value = VersionedValue; - type SubKey = u64; // block number - } - - /// Stores historical branch nodes for the storage trie of each account. - /// - /// Each entry is identified by a composite key combining the account’s hashed address and the - /// compact-encoded trie path. Versions are tracked using block numbers as subkeys. - table StorageTrieHistory { - type Key = StorageTrieKey; - type Value = VersionedValue; - type SubKey = u64; // block number - } - - /// Stores versioned account state across block history. - /// - /// Each entry maps a hashed account address to its serialized account data (balance, nonce, - /// code hash, storage root). - table HashedAccountHistory { - type Key = B256; - type Value = VersionedValue; - type SubKey = u64; // block number - } - - /// Stores versioned storage state across block history. - /// - /// Each entry maps a composite key of (hashed address, storage key) to its stored value. - /// Used for reconstructing contract storage at any historical block height. - table HashedStorageHistory { - type Key = HashedStorageKey; - type Value = VersionedValue; - type SubKey = u64; // block number - } - - /// Tracks the active proof window in the external historical storage. - /// - /// Stores the earliest and latest block numbers (and corresponding hashes) - /// for which historical trie data is retained. - table ProofWindow { - type Key = ProofWindowKey; - type Value = BlockNumberHash; - } - - /// A reverse mapping of block numbers to a keys of the tables. - /// This is used for efficiently locating data by block number. - table BlockChangeSet { - type Key = u64; // Block number - type Value = ChangeSet; - } - - // ==================== V2 Tables ==================== - // - // The v2 schema uses the 3-table-per-data-type pattern. All v2 tables are - // prefixed with `V2` to clearly distinguish them from v1 tables and to ensure - // each store only reads/writes its own tables. - // - // - **Current state** tables hold the latest values for fast reads. - // - **ChangeSet** tables group changes by block number for efficient pruning/unwinding. - // - **History** tables store sharded bitmaps for historical lookups. - // - - // -------------------- Proof Window -------------------- - - /// V2 proof window tracking (independent of the v1 [`ProofWindow`] table). - table V2ProofWindow { - type Key = ProofWindowKey; - type Value = BlockNumberHash; - } - - // -------------------- Hashed Accounts -------------------- - - /// Sharded history index for hashed accounts. - /// - /// Maps `ShardedKey` (hashed address + highest block number in shard) - /// to a bitmap of block numbers that modified this account. Used for historical - /// lookups: find the relevant block in the bitmap, then read the changeset. - table V2HashedAccountsHistory { - type Key = HashedAccountShardedKey; - type Value = BlockNumberList; - } - - /// Account changesets grouped by block number. - /// - /// Each entry stores the hashed address and the account state **before** the - /// block was applied (`None` if the account didn't exist). Grouped by block - /// number for efficient pruning (delete all entries for a block in one - /// operation) and unwinding (restore old values on reorg). - table V2HashedAccountChangeSets { - type Key = BlockNumber; - type Value = HashedAccountBeforeTx; - type SubKey = B256; - } - - /// Current state of all accounts, keyed by `keccak256(address)`. - /// - /// Holds the latest account data (nonce, balance, code hash, storage root). - /// Primary read target for state root computation and proof generation — - /// no version lookup needed. - table V2HashedAccounts { - type Key = B256; - type Value = Account; - } - - // -------------------- Hashed Storages -------------------- - - /// Sharded history index for storage slots. - /// - /// Composite key of `(hashed_address, hashed_storage_key, highest_block_number)`. - /// Maps to a bitmap of block numbers that modified this storage slot. - table V2HashedStoragesHistory { - type Key = HashedStorageShardedKey; - type Value = BlockNumberList; - } - - /// Storage changesets grouped by block number and account. - /// - /// Composite key of `(block_number, hashed_address)`. Each entry stores the - /// hashed storage key and value **before** the block was applied. - /// A value of [`U256::ZERO`](alloy_primitives::U256::ZERO) means the slot - /// did not exist (needs to be removed on unwind). - table V2HashedStorageChangeSets { - type Key = BlockNumberHashedAddress; - type Value = StorageEntry; - type SubKey = B256; - } - - /// Current storage values, keyed by hashed address with hashed storage key - /// as the `DupSort` subkey. - /// - /// Holds the latest storage slot values for each account. Primary read target - /// for storage proof generation. - table V2HashedStorages { - type Key = B256; - type Value = StorageEntry; - type SubKey = B256; - } - - // -------------------- Account Trie -------------------- - - /// Sharded history index for the account state trie. - /// - /// Maps `ShardedKey` (trie path + highest block number in shard) - /// to a bitmap of block numbers that modified this path. - table V2AccountsTrieHistory { - type Key = AccountTrieShardedKey; - type Value = BlockNumberList; - } - - /// Account trie changesets grouped by block number. - /// - /// Each entry stores the trie path and the branch node value **before** the - /// block was applied (`None` if the node didn't exist). Enables efficient - /// pruning and unwinding of trie state. - table V2AccountTrieChangeSets { - type Key = BlockNumber; - type Value = TrieChangeSetsEntry; - type SubKey = StoredNibblesSubKey; - } - - /// Current state of the account Merkle Patricia Trie. - /// - /// Maps trie paths to the latest branch node. Primary read target during - /// proof generation — no version lookup needed. - table V2AccountsTrie { - type Key = StoredNibbles; - type Value = BranchNodeCompact; - } - - // -------------------- Storage Trie -------------------- - - /// Sharded history index for per-account storage tries. - /// - /// Composite key of `(hashed_address, trie_path, highest_block_number)`. - /// Maps to a bitmap of block numbers that modified this storage trie node. - table V2StoragesTrieHistory { - type Key = StorageTrieShardedKey; - type Value = BlockNumberList; - } - - /// Storage trie changesets grouped by block number and account. - /// - /// Composite key of `(block_number, hashed_address)`. Each entry stores the - /// trie path and the branch node value **before** the block was applied. - table V2StorageTrieChangeSets { - type Key = BlockNumberHashedAddress; - type Value = TrieChangeSetsEntry; - type SubKey = StoredNibblesSubKey; - } - - /// Current state of each account's storage Merkle Patricia Trie. - /// - /// Keyed by hashed account address, with the trie path as the `DupSort` subkey. - /// Holds the latest branch node for each path in each account's storage trie. - table V2StoragesTrie { - type Key = B256; - type Value = StorageTrieEntry; - type SubKey = StoredNibblesSubKey; - } - - /// Snapshot of [`V2AccountsTrie`] reflecting trie state at block x. - /// - /// Same schema as [`V2AccountsTrie`]. Populated by `SnapshotInitJob`. - table V2AccountsTrieSnapshot { - type Key = StoredNibbles; - type Value = BranchNodeCompact; - } - - /// Snapshot of [`V2StoragesTrie`] reflecting trie state at block x. - /// - /// Same schema as [`V2StoragesTrie`]. - table V2StoragesTrieSnapshot { - type Key = B256; - type Value = StorageTrieEntry; - type SubKey = StoredNibblesSubKey; - } - - /// Snapshot of [`V2HashedAccounts`] reflecting hashed-account leaves at the - /// snapshot anchor block. - table V2HashedAccountsSnapshot { - type Key = B256; - type Value = Account; - } - - /// Snapshot of [`V2HashedStorages`] reflecting hashed-storage leaves at the - /// snapshot anchor block. Same shape as [`V2HashedStorages`]. - table V2HashedStoragesSnapshot { - type Key = B256; - type Value = StorageEntry; - type SubKey = B256; - } - - /// Single-row metadata for the snapshot: which block its trie state - /// reflects, and whether it's [`SnapshotStatus::Ready`] for reads. - table V2SnapshotMeta { - type Key = SnapshotMetaKey; - type Value = SnapshotMeta; - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/snapshot.rs b/vendor/reth-optimism-trie/src/db/models/snapshot.rs deleted file mode 100644 index 3ef2aec..0000000 --- a/vendor/reth-optimism-trie/src/db/models/snapshot.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Models for the backfill trie-state snapshot (see -//! [`crate::backfill`] for the design rationale). - -use alloy_eips::BlockNumHash; -use alloy_primitives::B256; -use bytes::BufMut; -use reth_codecs::DecompressError; -use reth_db::{ - DatabaseError, - table::{Compress, Decode, Decompress, Encode}, -}; -use serde::{Deserialize, Serialize}; - -/// Single-row key for the snapshot metadata table. -/// -/// There is only ever one snapshot per proofs store, so the table has a -/// fixed singleton key. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[repr(u8)] -pub enum SnapshotMetaKey { - /// The singleton key — there is only ever one snapshot meta row. - Singleton = 0, -} - -impl Encode for SnapshotMetaKey { - type Encoded = [u8; 1]; - - fn encode(self) -> Self::Encoded { - [self as u8] - } -} - -impl Decode for SnapshotMetaKey { - fn decode(value: &[u8]) -> Result { - match value.first() { - Some(&0) => Ok(Self::Singleton), - _ => Err(DatabaseError::Decode), - } - } -} - -/// Lifecycle status of the trie snapshot. -/// -/// A snapshot's invariant is that it reflects trie state at -/// [`SnapshotMeta::anchor`]. Either it's being built and not yet -/// trustworthy ([`Self::Building`]), or it's done and reflects that block -/// exactly ([`Self::Ready`]). If a snapshot ever falls out of sync it is -/// dropped via -/// [`OpProofsBackfillProvider::clear_snapshot`](crate::api::OpProofsBackfillProvider::clear_snapshot), -/// not left around in a third state. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[repr(u8)] -pub enum SnapshotStatus { - /// Snapshot is being constructed by [`crate::snapshot::SnapshotInitJob`]. - /// Reads must be refused until status transitions to [`Self::Ready`]. - Building = 0, - /// Snapshot is consistent and reflects trie state at [`SnapshotMeta::anchor`]. - Ready = 1, -} - -/// Metadata for the trie-state snapshot. -/// -/// Encoding: `[status: 1B] ‖ [block_number: 8B BE] ‖ [block_hash: 32B]` (= 41 B). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct SnapshotMeta { - /// The block (number + hash) the snapshot's trie state corresponds to. - pub anchor: BlockNumHash, - /// Current lifecycle state. - pub status: SnapshotStatus, -} - -impl SnapshotMeta { - /// Encoded byte length. - pub const ENCODED_LEN: usize = 1 + 8 + 32; - - /// Convenience constructor. - pub const fn new(anchor: BlockNumHash, status: SnapshotStatus) -> Self { - Self { anchor, status } - } -} - -impl Compress for SnapshotMeta { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - buf.put_u8(self.status as u8); - buf.put_u64(self.anchor.number); - buf.put_slice(self.anchor.hash.as_slice()); - } -} - -impl Decompress for SnapshotMeta { - fn decompress(value: &[u8]) -> Result { - if value.len() != Self::ENCODED_LEN { - return Err(DecompressError::new(DatabaseError::Decode)); - } - let status = match value[0] { - 0 => SnapshotStatus::Building, - 1 => SnapshotStatus::Ready, - _ => return Err(DecompressError::new(DatabaseError::Decode)), - }; - let number = u64::from_be_bytes( - value[1..9].try_into().map_err(|_| DecompressError::new(DatabaseError::Decode))?, - ); - let hash = B256::from_slice(&value[9..41]); - Ok(Self { anchor: BlockNumHash::new(number, hash), status }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn snapshot_meta_key_roundtrip() { - let encoded = SnapshotMetaKey::Singleton.encode(); - let decoded = SnapshotMetaKey::decode(&encoded).unwrap(); - assert_eq!(decoded, SnapshotMetaKey::Singleton); - } - - #[test] - fn snapshot_meta_roundtrip_all_statuses() { - let earliest = BlockNumHash::new(12_345_678, B256::repeat_byte(0xab)); - for status in [SnapshotStatus::Building, SnapshotStatus::Ready] { - let original = SnapshotMeta::new(earliest, status); - let compressed = original.compress(); - assert_eq!(compressed.len(), SnapshotMeta::ENCODED_LEN); - let decompressed = SnapshotMeta::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - } - - #[test] - fn snapshot_meta_decompress_rejects_wrong_length() { - assert!(SnapshotMeta::decompress(&[0u8; 10]).is_err()); - assert!(SnapshotMeta::decompress(&[0u8; 41 + 1]).is_err()); - } - - #[test] - fn snapshot_meta_decompress_rejects_invalid_status() { - let mut buf = vec![0xff_u8; SnapshotMeta::ENCODED_LEN]; - // status byte at position 0; 0xff is not a valid SnapshotStatus. - buf[0] = 0xff; - assert!(SnapshotMeta::decompress(&buf).is_err()); - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/storage.rs b/vendor/reth-optimism-trie/src/db/models/storage.rs deleted file mode 100644 index ad520c1..0000000 --- a/vendor/reth-optimism-trie/src/db/models/storage.rs +++ /dev/null @@ -1,258 +0,0 @@ -use alloy_primitives::{B256, U256}; -use derive_more::{Constructor, From, Into}; -use reth_codecs::DecompressError; -use reth_db::{ - DatabaseError, - table::{Compress, Decode, Decompress, Encode}, -}; -use reth_trie_common::StoredNibbles; -use serde::{Deserialize, Serialize}; - -/// Composite key: `(hashed-address, path)` for storage trie branches -/// -/// Used to efficiently index storage branches by both account address and trie path. -/// The encoding ensures lexicographic ordering: first by address, then by path. -#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct StorageTrieKey { - /// Hashed account address - pub hashed_address: B256, - /// Trie path as nibbles - pub path: StoredNibbles, -} - -impl StorageTrieKey { - /// Create a new storage branch key - pub const fn new(hashed_address: B256, path: StoredNibbles) -> Self { - Self { hashed_address, path } - } -} - -impl Encode for StorageTrieKey { - type Encoded = Vec; - - fn encode(self) -> Self::Encoded { - let mut buf = Vec::with_capacity(32 + self.path.0.len()); - // First encode the address (32 bytes) - buf.extend_from_slice(self.hashed_address.as_slice()); - // Then encode the path - buf.extend_from_slice(&self.path.encode()); - buf - } -} - -impl Decode for StorageTrieKey { - fn decode(value: &[u8]) -> Result { - if value.len() < 32 { - return Err(DatabaseError::Decode); - } - - // First 32 bytes are the address - let hashed_address = B256::from_slice(&value[..32]); - - // Remaining bytes are the path - let path = StoredNibbles::decode(&value[32..])?; - - Ok(Self { hashed_address, path }) - } -} - -/// Composite key: (`hashed_address`, `hashed_storage_key`) for hashed storage values -/// -/// Used to efficiently index storage values by both account address and storage key. -/// The encoding ensures lexicographic ordering: first by address, then by storage key. -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct HashedStorageKey { - /// Hashed account address - pub hashed_address: B256, - /// Hashed storage key - pub hashed_storage_key: B256, -} - -impl HashedStorageKey { - /// Create a new hashed storage key - pub const fn new(hashed_address: B256, hashed_storage_key: B256) -> Self { - Self { hashed_address, hashed_storage_key } - } -} - -impl Encode for HashedStorageKey { - type Encoded = [u8; 64]; - - fn encode(self) -> Self::Encoded { - let mut buf = [0u8; 64]; - // First 32 bytes: address - buf[..32].copy_from_slice(self.hashed_address.as_slice()); - // Next 32 bytes: storage key - buf[32..].copy_from_slice(self.hashed_storage_key.as_slice()); - buf - } -} - -impl Decode for HashedStorageKey { - fn decode(value: &[u8]) -> Result { - if value.len() != 64 { - return Err(DatabaseError::Decode); - } - - let hashed_address = B256::from_slice(&value[..32]); - let hashed_storage_key = B256::from_slice(&value[32..64]); - - Ok(Self { hashed_address, hashed_storage_key }) - } -} - -/// Storage value wrapper for U256 values -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, From, Into, Constructor)] -pub struct StorageValue(pub U256); - -impl Compress for StorageValue { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - let be: [u8; 32] = self.0.to_be_bytes::<32>(); - buf.put_slice(&be); - } -} - -impl Decompress for StorageValue { - fn decompress(value: &[u8]) -> Result { - if value.len() != 32 { - return Err(DecompressError::new(DatabaseError::Decode)); - } - let bytes: [u8; 32] = - value.try_into().map_err(|_| DecompressError::new(DatabaseError::Decode))?; - Ok(Self(U256::from_be_bytes(bytes))) - } -} - -/// Proof Window key for tracking active proof window bounds -/// -/// Used to store earliest and latest block numbers in the external storage. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[repr(u8)] -pub enum ProofWindowKey { - /// Earliest block number stored in external storage - EarliestBlock = 0, - /// Latest block number stored in external storage - LatestBlock = 1, - /// Anchor block from where the initial state initialization started - InitialStateAnchor = 2, -} - -impl Encode for ProofWindowKey { - type Encoded = [u8; 1]; - - fn encode(self) -> Self::Encoded { - [self as u8] - } -} - -impl Decode for ProofWindowKey { - fn decode(value: &[u8]) -> Result { - match value.first() { - Some(&0) => Ok(Self::EarliestBlock), - Some(&1) => Ok(Self::LatestBlock), - Some(&2) => Ok(Self::InitialStateAnchor), - _ => Err(DatabaseError::Decode), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_trie::Nibbles; - - #[test] - fn test_storage_branch_subkey_encode_decode() { - let addr = B256::from([1u8; 32]); - let path = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 2, 3, 4])); - let key = StorageTrieKey::new(addr, path.clone()); - - let encoded = key.clone().encode(); - let decoded = StorageTrieKey::decode(&encoded).unwrap(); - - assert_eq!(key, decoded); - assert_eq!(decoded.hashed_address, addr); - assert_eq!(decoded.path, path); - } - - #[test] - fn test_storage_branch_subkey_ordering() { - let addr1 = B256::from([1u8; 32]); - let addr2 = B256::from([2u8; 32]); - let path1 = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 2])); - let path2 = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 3])); - - let key1 = StorageTrieKey::new(addr1, path1.clone()); - let key2 = StorageTrieKey::new(addr1, path2); - let key3 = StorageTrieKey::new(addr2, path1); - - // Encoded bytes should be sortable: first by address, then by path - let enc1 = key1.encode(); - let enc2 = key2.encode(); - let enc3 = key3.encode(); - - assert!(enc1 < enc2, "Same address, path1 < path2"); - assert!(enc1 < enc3, "addr1 < addr2"); - assert!(enc2 < enc3, "addr1 < addr2 (even with larger path)"); - } - - #[test] - fn test_hashed_storage_subkey_encode_decode() { - let addr = B256::from([1u8; 32]); - let storage_key = B256::from([2u8; 32]); - let key = HashedStorageKey::new(addr, storage_key); - - let encoded = key.clone().encode(); - let decoded = HashedStorageKey::decode(&encoded).unwrap(); - - assert_eq!(key, decoded); - assert_eq!(decoded.hashed_address, addr); - assert_eq!(decoded.hashed_storage_key, storage_key); - } - - #[test] - fn test_hashed_storage_subkey_ordering() { - let addr1 = B256::from([1u8; 32]); - let addr2 = B256::from([2u8; 32]); - let storage1 = B256::from([10u8; 32]); - let storage2 = B256::from([20u8; 32]); - - let key1 = HashedStorageKey::new(addr1, storage1); - let key2 = HashedStorageKey::new(addr1, storage2); - let key3 = HashedStorageKey::new(addr2, storage1); - - // Encoded bytes should be sortable: first by address, then by storage key - let enc1 = key1.encode(); - let enc2 = key2.encode(); - let enc3 = key3.encode(); - - assert!(enc1 < enc2, "Same address, storage1 < storage2"); - assert!(enc1 < enc3, "addr1 < addr2"); - assert!(enc2 < enc3, "addr1 < addr2 (even with larger storage key)"); - } - - #[test] - fn test_hashed_storage_subkey_size() { - let addr = B256::from([1u8; 32]); - let storage_key = B256::from([2u8; 32]); - let key = HashedStorageKey::new(addr, storage_key); - - let encoded = key.encode(); - assert_eq!(encoded.len(), 64, "Encoded size should be exactly 64 bytes"); - } - - #[test] - fn test_metadata_key_encode_decode() { - let key = ProofWindowKey::EarliestBlock; - let encoded = key.encode(); - let decoded = ProofWindowKey::decode(&encoded).unwrap(); - assert_eq!(key, decoded); - - let key = ProofWindowKey::LatestBlock; - let encoded = key.encode(); - let decoded = ProofWindowKey::decode(&encoded).unwrap(); - assert_eq!(key, decoded); - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/value.rs b/vendor/reth-optimism-trie/src/db/models/value.rs deleted file mode 100644 index 1b3f680..0000000 --- a/vendor/reth-optimism-trie/src/db/models/value.rs +++ /dev/null @@ -1,210 +0,0 @@ -use alloy_primitives::B256; -use bytes::BufMut; -use reth_codecs::{Compact, DecompressError}; -use reth_db::{ - DatabaseError, - table::{Compress, Decompress}, -}; -use reth_primitives_traits::{Account, ValueWithSubKey}; -use reth_trie_common::{BranchNodeCompact, StoredNibblesSubKey}; -use serde::{Deserialize, Serialize}; - -/// Account state before a block, keyed by hashed address. -/// -/// This is the hashed-address equivalent of reth's -/// `AccountBeforeTx`, designed for our v2 `AccountChangeSets` -/// table where keys are `keccak256(address)`. -/// -/// Layout: `[hashed_address: 32 bytes][account: Compact-encoded or empty]` -/// -/// - The 32-byte hashed address acts as the [`DupSort::SubKey`]. -/// - An empty remainder means the account did not exist before this block (creation). -/// - A non-empty remainder is the [`Account`] state before the block was applied. -/// -/// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey -#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct HashedAccountBeforeTx { - /// Hashed address (`keccak256(address)`). Acts as `DupSort::SubKey`. - pub hashed_address: B256, - /// Account state before the block. `None` means the account didn't exist. - pub info: Option, -} - -impl HashedAccountBeforeTx { - /// Creates a new instance. - pub const fn new(hashed_address: B256, info: Option) -> Self { - Self { hashed_address, info } - } -} - -impl ValueWithSubKey for HashedAccountBeforeTx { - type SubKey = B256; - - fn get_subkey(&self) -> Self::SubKey { - self.hashed_address - } -} - -impl Compress for HashedAccountBeforeTx { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - // SubKey: raw 32 bytes (uncompressed so MDBX can seek by it) - buf.put_slice(self.hashed_address.as_slice()); - // Value: compress the account if present, otherwise write nothing - if let Some(account) = &self.info { - account.compress_to_buf(buf); - } - } -} - -impl Decompress for HashedAccountBeforeTx { - fn decompress(value: &[u8]) -> Result { - if value.len() < 32 { - return Err(DecompressError::new(DatabaseError::Decode)); - } - - let hashed_address = B256::from_slice(&value[..32]); - let info = if value.len() > 32 { Some(Account::decompress(&value[32..])?) } else { None }; - - Ok(Self { hashed_address, info }) - } -} - -/// Trie changeset entry representing the state of a trie node before a block. -/// -/// `nibbles` is the subkey when used as a value in the changeset tables. -/// This is a local definition since the upstream `reth-trie-common` crate does -/// not provide this type. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TrieChangeSetsEntry { - /// The nibbles of the intermediate node - pub nibbles: StoredNibblesSubKey, - /// Node value prior to the block being processed, None indicating it didn't exist. - pub node: Option, -} - -impl ValueWithSubKey for TrieChangeSetsEntry { - type SubKey = StoredNibblesSubKey; - - fn get_subkey(&self) -> Self::SubKey { - self.nibbles.clone() - } -} - -impl Compress for TrieChangeSetsEntry { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - let _ = self.nibbles.to_compact(buf); - if let Some(ref node) = self.node { - let _ = node.to_compact(buf); - } - } -} - -impl Decompress for TrieChangeSetsEntry { - fn decompress(value: &[u8]) -> Result { - if value.is_empty() { - return Ok(Self { - nibbles: StoredNibblesSubKey::from(reth_trie_common::Nibbles::default()), - node: None, - }); - } - - let (nibbles, rest) = StoredNibblesSubKey::from_compact(value, 65); - let node = if rest.is_empty() { - None - } else { - Some(BranchNodeCompact::from_compact(rest, rest.len()).0) - }; - Ok(Self { nibbles, node }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_db::table::{Compress, Decompress}; - - #[test] - fn test_hashed_account_before_tx_roundtrip_some() { - let original = HashedAccountBeforeTx { - hashed_address: B256::repeat_byte(0xaa), - info: Some(Account { - nonce: 42, - balance: alloy_primitives::U256::from(1000u64), - bytecode_hash: None, - }), - }; - - let compressed = original.clone().compress(); - assert!(compressed.len() > 32, "Should contain address + account data"); - - let decompressed = HashedAccountBeforeTx::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - - #[test] - fn test_hashed_account_before_tx_roundtrip_none() { - let original = - HashedAccountBeforeTx { hashed_address: B256::repeat_byte(0xbb), info: None }; - - let compressed = original.clone().compress(); - assert_eq!(compressed.len(), 32, "None account should be just the 32-byte address"); - - let decompressed = HashedAccountBeforeTx::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - - #[test] - fn test_hashed_account_before_tx_subkey() { - let addr = B256::repeat_byte(0xcc); - let entry = HashedAccountBeforeTx::new(addr, None); - assert_eq!(entry.get_subkey(), addr); - } - - #[test] - fn test_trie_changesets_entry_roundtrip_with_node() { - let nibbles = - StoredNibblesSubKey(reth_trie_common::Nibbles::from_nibbles_unchecked([0x0A, 0x0B])); - let node = BranchNodeCompact::new(0b11, 0, 0, vec![], Some(B256::repeat_byte(0xDD))); - let original = TrieChangeSetsEntry { nibbles, node: Some(node) }; - - let compressed = original.clone().compress(); - let decompressed = TrieChangeSetsEntry::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - - #[test] - fn test_trie_changesets_entry_roundtrip_none_node() { - let nibbles = StoredNibblesSubKey(reth_trie_common::Nibbles::from_nibbles_unchecked([ - 0x01, 0x02, 0x03, - ])); - let original = TrieChangeSetsEntry { nibbles, node: None }; - - let compressed = original.clone().compress(); - let decompressed = TrieChangeSetsEntry::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - - #[test] - fn test_trie_changesets_entry_roundtrip_empty() { - let original = TrieChangeSetsEntry { - nibbles: StoredNibblesSubKey(reth_trie_common::Nibbles::default()), - node: None, - }; - - let compressed = original.clone().compress(); - let decompressed = TrieChangeSetsEntry::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - - #[test] - fn test_trie_changesets_entry_subkey() { - let nibbles = - StoredNibblesSubKey(reth_trie_common::Nibbles::from_nibbles_unchecked([0x05, 0x06])); - let entry = TrieChangeSetsEntry { nibbles: nibbles.clone(), node: None }; - assert_eq!(entry.get_subkey(), nibbles); - } -} diff --git a/vendor/reth-optimism-trie/src/db/models/version.rs b/vendor/reth-optimism-trie/src/db/models/version.rs deleted file mode 100644 index 90af120..0000000 --- a/vendor/reth-optimism-trie/src/db/models/version.rs +++ /dev/null @@ -1,192 +0,0 @@ -use bytes::{Buf, BufMut}; -use reth_codecs::DecompressError; -use reth_db::{ - DatabaseError, - table::{Compress, Decompress}, -}; -use reth_primitives_traits::ValueWithSubKey; -use serde::{Deserialize, Serialize}; - -/// Wrapper type for `Option` that implements [`Compress`] and [`Decompress`] -/// -/// Encoding: -/// - `None` => empty byte array (length 0) -/// - `Some(value)` => compressed bytes of value (length > 0) -/// -/// This assumes the inner type `T` always compresses to non-empty bytes when it exists. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct MaybeDeleted(pub Option); - -impl From> for MaybeDeleted { - fn from(opt: Option) -> Self { - Self(opt) - } -} - -impl From> for Option { - fn from(maybe: MaybeDeleted) -> Self { - maybe.0 - } -} - -impl Compress for MaybeDeleted { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - match &self.0 { - None => { - // Empty = deleted, write nothing - } - Some(value) => { - // Compress the inner value to the buffer - value.compress_to_buf(buf); - } - } - } -} - -impl Decompress for MaybeDeleted { - fn decompress(value: &[u8]) -> Result { - if value.is_empty() { - // Empty = deleted - Ok(Self(None)) - } else { - // Non-empty = present - let inner = T::decompress(value)?; - Ok(Self(Some(inner))) - } - } -} - -/// Versioned value wrapper for [`DupSort`] tables -/// -/// For [`DupSort`] tables in MDBX, the Value type must contain the [`DupSort::SubKey`] as a field. -/// This wrapper combines a [`block_number`] (the [`DupSort::SubKey`]) with -/// the actual value. -/// -/// [`DupSort`]: reth_db::table::DupSort -/// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey -/// [`block_number`]: Self::block_number -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct VersionedValue { - /// Block number ([`DupSort::SubKey`] for [`DupSort`]) - /// - /// [`DupSort`]: reth_db::table::DupSort - /// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey - pub block_number: u64, - /// The actual value (may be deleted) - pub value: MaybeDeleted, -} - -impl VersionedValue { - /// Create a new versioned value - pub const fn new(block_number: u64, value: MaybeDeleted) -> Self { - Self { block_number, value } - } -} - -impl Compress for VersionedValue { - type Compressed = Vec; - - fn compress_to_buf>(&self, buf: &mut B) { - // Encode block number first (8 bytes, big-endian) - buf.put_u64(self.block_number); - // Then encode the value - self.value.compress_to_buf(buf); - } -} - -impl Decompress for VersionedValue { - fn decompress(value: &[u8]) -> Result { - if value.len() < 8 { - return Err(DecompressError::new(DatabaseError::Decode)); - } - - let mut buf: &[u8] = value; - let block_number = buf.get_u64(); - let value = MaybeDeleted::::decompress(&value[8..])?; - - Ok(Self { block_number, value }) - } -} - -impl ValueWithSubKey for VersionedValue { - type SubKey = u64; - - fn get_subkey(&self) -> Self::SubKey { - self.block_number - } -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_primitives_traits::Account; - use reth_trie::BranchNodeCompact; - - #[test] - fn test_maybe_deleted_none() { - let none: MaybeDeleted = MaybeDeleted(None); - let compressed = none.compress(); - assert!(compressed.is_empty(), "None should compress to empty bytes"); - - let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); - assert_eq!(decompressed.0, None); - } - - #[test] - fn test_maybe_deleted_some_account() { - let account = Account { - nonce: 42, - balance: alloy_primitives::U256::from(1000u64), - bytecode_hash: None, - }; - let some = MaybeDeleted(Some(account)); - let compressed = some.compress(); - assert!(!compressed.is_empty(), "Some should compress to non-empty bytes"); - - let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); - assert_eq!(decompressed.0, Some(account)); - } - - #[test] - fn test_maybe_deleted_some_branch() { - // Create a simple valid BranchNodeCompact (empty is valid) - let branch = BranchNodeCompact::new( - 0, // state_mask - 0, // tree_mask - 0, // hash_mask - vec![], // hashes - None, // root_hash - ); - let some = MaybeDeleted(Some(branch.clone())); - let compressed = some.compress(); - assert!(!compressed.is_empty(), "Some should compress to non-empty bytes"); - - let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); - assert_eq!(decompressed.0, Some(branch)); - } - - #[test] - fn test_maybe_deleted_roundtrip() { - let test_cases = vec![ - MaybeDeleted(None), - MaybeDeleted(Some(Account { - nonce: 0, - balance: alloy_primitives::U256::ZERO, - bytecode_hash: None, - })), - MaybeDeleted(Some(Account { - nonce: 999, - balance: alloy_primitives::U256::MAX, - bytecode_hash: Some([0xff; 32].into()), - })), - ]; - - for original in test_cases { - let compressed = original.clone().compress(); - let decompressed = MaybeDeleted::::decompress(&compressed).unwrap(); - assert_eq!(original, decompressed); - } - } -} diff --git a/vendor/reth-optimism-trie/src/db/schema.md b/vendor/reth-optimism-trie/src/db/schema.md deleted file mode 100644 index 4c2b04f..0000000 --- a/vendor/reth-optimism-trie/src/db/schema.md +++ /dev/null @@ -1,366 +0,0 @@ -# Proof History Database Schema - -> Location: `crates/optimism/trie/src/db` -> Backend: **MDBX** (via `reth-db`) -> Purpose: Serve **historical `eth_getProof`** by storing versioned trie data in a bounded window. - ---- - -## Design Overview - -This database is a **versioned, append-only history store** for Ethereum state tries. - -Each logical key is stored with **multiple historical versions**, each tagged by a **block number**. Reads select the latest version whose block number is **≤ the requested block**. - -### Core principles - -* History tables are **DupSort** tables -* Each entry is versioned by `block_number` -* Deletions are encoded as **tombstones** -* A reverse index (`BlockChangeSet`) enables **range pruning** -* Proof window bounds are tracked explicitly - ---- - -## Version Encoding - -All historical values are wrapped in `VersionedValue`. - -### `VersionedValue` - -| Field | Type | Encoding | -| -------------- | ----------------- | -------------------- | -| `block_number` | `u64` | big-endian (8 bytes) | -| `value` | `MaybeDeleted` | see below | - -``` -VersionedValue := block_number || maybe_deleted_value -``` - ---- - -### `MaybeDeleted` - -Encodes value presence or deletion: - -| Logical value | Encoding | -| ------------- | ---------------------------- | -| `Some(T)` | `T::compress()` | -| `None` | empty byte slice (`len = 0`) | - -An empty value represents **deletion at that block**. - ---- - -## Tables - ---- - -## 1. `AccountTrieHistory` (DupSort) - -Historical **branch nodes** of the **account trie**. - -### Purpose - -Reconstruct account trie structure at any historical block. - -### Schema - -| Component | Type | -| --------- | ----------------------------------- | -| Key | `StoredNibbles` | -| SubKey | `u64` (block number) | -| Value | `VersionedValue` | - -### Key encoding - -* `StoredNibbles` = compact-encoded trie path - -### Semantics - -For a given trie path: - -* Multiple versions may exist -* Reader selects highest `block_number ≤ target_block` - ---- - -## 2. `StorageTrieHistory` (DupSort) - -Historical **branch nodes** of **per-account storage tries**. - -### Schema - -| Component | Type | -| --------- | ----------------------------------- | -| Key | `StorageTrieKey` | -| SubKey | `u64` | -| Value | `VersionedValue` | - -### `StorageTrieKey` encoding - -``` -StorageTrieKey := - hashed_address (32 bytes) - || StoredNibbles::encode(path) -``` - -Ordering: - -1. `hashed_address` -2. trie path bytes - ---- - -## 3. `HashedAccountHistory` (DupSort) - -Historical **account leaf values**. - -### Schema - -| Component | Type | -| --------- | ------------------------- | -| Key | `B256` (hashed address) | -| SubKey | `u64` | -| Value | `VersionedValue` | - -### Semantics - -Stores nonce, balance, code hash, and storage root per account per block. - ---- - -## 4. `HashedStorageHistory` (DupSort) - -Historical **storage slot values**. - -### Schema - -| Component | Type | -| --------- | ------------------------------ | -| Key | `HashedStorageKey` | -| SubKey | `u64` | -| Value | `VersionedValue` | - -### `HashedStorageKey` encoding - -Fixed 64 bytes: - -``` -hashed_address (32 bytes) || hashed_storage_key (32 bytes) -``` - -### `StorageValue` encoding - -* Wraps `U256` -* Encoded as **32-byte big-endian** - ---- - -## 5. `BlockChangeSet` - -Reverse index of **which keys were modified in a block**. - -### Purpose - -Efficient pruning by block range. - -### Schema - -| Component | Type | -| --------- | -------------------- | -| Key | `u64` (block number) | -| Value | `ChangeSet` | - -### `ChangeSet` structure - -```rust -pub struct ChangeSet { - pub account_trie_keys: Vec, - pub storage_trie_keys: Vec, - pub hashed_account_keys: Vec, - pub hashed_storage_keys: Vec, -} -``` - -### Encoding - -* Serialized using **bincode** - ---- - -## 6. `ProofWindow` - -Tracks active proof window bounds. - -### Schema - -| Component | Type | -| --------- | ----------------- | -| Key | `ProofWindowKey` | -| Value | `BlockNumberHash` | - -### `ProofWindowKey` - -| Variant | Encoding | -| --------------- | -------- | -| `EarliestBlock` | `0u8` | -| `LatestBlock` | `1u8` | - -### `BlockNumberHash` encoding - -``` -block_number (u64 BE, 8 bytes) -|| block_hash (B256, 32 bytes) -``` - -Total size: **40 bytes** - ---- -Here is a **short, clean, professional** version suitable for `SCHEMA.md`: - ---- - -## Reads: Hashed & Trie Cursors - -Historical reads are performed using **hashed cursors** and **trie cursors**, both operating on versioned history tables. - -All reads follow the same rule: - -> Select the newest entry whose block number is **≤ the requested block**. - ---- - -### Hashed Cursors - -Hashed cursors read **leaf values** from: - -* `HashedAccountHistory` -* `HashedStorageHistory` - -They answer: - -> *What was the value of this account or storage slot at block B?* - -For a given key, the cursor scans historical versions and returns the latest valid value. Tombstones indicate deletion and are treated as non-existence. - ---- - -### Trie Cursors - -Trie cursors read **trie branch nodes** from: - -* `AccountTrieHistory` -* `StorageTrieHistory` - -They answer: - -> *Which trie nodes existed at this path at block B?* - -These cursors enable reconstruction of Merkle paths required for proof generation. - ---- - -### Combined Usage - -When serving `eth_getProof`: - -* Trie cursors reconstruct the Merkle path -* Hashed cursors supply the leaf values - -Both are evaluated at the same target explained block to produce deterministic historical proofs. - - ---- -## Writes: `store_trie_updates` (Append-Only) - -`store_trie_updates` persists all state changes introduced by a block using a strictly **append-only** write model. - - -### Purpose - -The function records **historical trie updates** so that state and proofs can be reconstructed at any later block. - ---- - -### What is written - -For a processed block `B`, the following data is appended: - -* Account trie branch nodes → `AccountTrieHistory` -* Storage trie branch nodes → `StorageTrieHistory` -* Account leaf updates → `HashedAccountHistory` -* Storage slot updates → `HashedStorageHistory` -* Modified keys → `BlockChangeSet[B]` - -All entries are tagged with the same `block_number`. - ---- - -### How writes work - -For each updated item: - -1. A `VersionedValue` is created with: - - * `block_number = B` - * the encoded node or value - -2. The entry is appended to the corresponding history table. - -No existing entries are modified or replaced. - - ---- -Here is a **concise, professional, SCHEMA.md-style** explanation aligned exactly with your clarification: - ---- - -## Initial State Backfill - -### Source database (Reth) - -The initial state is sourced from **Reth’s main execution database**, which only contains data for the **current canonical state**. - -The backfill reads from the following Reth tables: - -* `HashedAccounts` – current account leaf values -* `HashedStorages` – current storage slot values -* `AccountsTrie` – current account trie branch nodes -* `StoragesTrie` – current storage trie branch nodes - -These tables do **not** contain historical versions; they represent a single finalized state snapshot. - ---- - -### Destination database (Proofs storage) - -The data is copied into the **proofs history database** (`OpProofsStore`), which is a **versioned, append-only** store designed for historical proof generation. - ---- - -### How the initial state is created - -During backfill: - -1. The current state is fully scanned from Reth tables using read-only cursors. -2. All entries are written into the proofs storage as versioned records. -3. This creates a complete **baseline state** inside the proofs DB. - -The backfill runs only once and is skipped if the proofs DB already has an `earliest_block` set. - ---- - -### Why block `0` is used - -Since Reth tables only represent the **current state**, the copied data must be assigned a synthetic version. - -Block **`0`** is used as the baseline version because: - -* It is ≤ any real block number -* It establishes a stable initial version for all keys -* Later block updates naturally override it using higher block numbers - -This makes block `0` the canonical **initial state anchor** for versioned reads. - ---- diff --git a/vendor/reth-optimism-trie/src/db/store.rs b/vendor/reth-optimism-trie/src/db/store.rs deleted file mode 100644 index e8ad79c..0000000 --- a/vendor/reth-optimism-trie/src/db/store.rs +++ /dev/null @@ -1,4014 +0,0 @@ -use super::{BlockNumberHash, ProofWindow, ProofWindowKey, Tables}; -use crate::{ - BlockStateDiff, OpProofsStorageError, - OpProofsStorageError::NoBlocksFound, - OpProofsStorageResult, - api::{ - InitialStateAnchor, InitialStateStatus, OpProofsInitProvider, OpProofsProviderRO, - OpProofsProviderRw, OpProofsStore, ProofWindowRange, WriteCounts, - }, - db::{ - MdbxAccountCursor, MdbxStorageCursor, MdbxTrieCursor, - models::{ - AccountTrieHistory, BlockChangeSet, ChangeSet, HashedAccountHistory, - HashedStorageHistory, HashedStorageKey, MaybeDeleted, StorageTrieHistory, - StorageTrieKey, StorageValue, VersionedValue, kv::IntoKV, - }, - }, -}; -use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; -use alloy_primitives::{B256, U256, map::HashMap}; -#[cfg(feature = "metrics")] -use metrics::{Label, gauge}; -use reth_db::{ - Database, DatabaseEnv, DatabaseError, - cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, - mdbx::{DatabaseArguments, init_db_for}, - table::{DupSort, Table}, - transaction::{DbTx, DbTxMut}, -}; -use reth_primitives_traits::Account; -use reth_trie::{hashed_cursor::HashedCursor, trie_cursor::TrieCursor}; -use reth_trie_common::{ - BranchNodeCompact, HashedPostState, Nibbles, StoredNibbles, - updates::{StorageTrieUpdates, TrieUpdates}, -}; -use std::{fmt::Debug, ops::RangeBounds, path::Path, sync::Arc}; - -/// Preprocessed prune plan for a target block number -#[derive(Debug, Clone)] -struct PrunePlan { - earliest_block: u64, - acc_survivors: Vec<(StoredNibbles, u64)>, - storage_survivors: Vec<(StorageTrieKey, u64)>, - hashed_acc_survivors: Vec<(B256, u64)>, - hashed_storage_survivors: Vec<(HashedStorageKey, u64)>, -} - -/// Preprocessed delete work for a prune range -#[derive(Debug, Default, Clone)] -struct HistoryDeleteBatch { - account_trie: Vec<(::Key, u64)>, - storage_trie: Vec<(::Key, u64)>, - hashed_account: Vec<(::Key, u64)>, - hashed_storage: Vec<(::Key, u64)>, -} - -/// MDBX implementation of [`OpProofsStore`]. -#[derive(Debug)] -pub struct MdbxProofsStorage { - env: DatabaseEnv, -} - -impl MdbxProofsStorage { - /// Creates a new [`MdbxProofsStorage`] instance with the given path. - pub fn new(path: &Path) -> Result { - let env = init_db_for::<_, Tables>(path, DatabaseArguments::default()) - .map_err(|e| DatabaseError::Other(format!("Failed to open database: {e}")))?; - Ok(Self { env }) - } -} - -/// MDBX provider for proof storage -#[derive(Debug)] -pub struct MdbxProofsProvider { - pub(crate) tx: TX, -} - -impl MdbxProofsProvider { - /// Creates a new `MdbxProofsProvider` instance with the given transaction. - pub const fn new(tx: TX) -> Self { - Self { tx } - } -} - -impl MdbxProofsProvider { - fn get_block_number_hash_inner(&self, key: ProofWindowKey) -> OpProofsStorageResult { - let mut cursor = self.tx.cursor_read::()?; - cursor - .seek_exact(key)? - .map(|(_, val)| NumHash::new(val.number(), *val.hash())) - .ok_or(NoBlocksFound) - } - - /// Phase 1 of pruning: Calculate survivors. - /// Scans change sets to find the LATEST update for every key in the range. - fn calculate_prune_plan(&self, target_block: u64) -> OpProofsStorageResult> { - let earliest = self.get_block_number_hash_inner(ProofWindowKey::EarliestBlock)?.number; - if earliest >= target_block { - return Err(OpProofsStorageError::PruneBeyondEarliest { - target_block_number: target_block, - earliest_block_number: earliest, - }); - } - - let mut acc_candidates: HashMap = HashMap::default(); - let mut storage_candidates: HashMap = HashMap::default(); - let mut hashed_acc_candidates: HashMap = HashMap::default(); - let mut hashed_storage_candidates: HashMap = HashMap::default(); - - let range = (earliest + 1)..=target_block; - let mut cs_cursor = self.tx.cursor_read::()?; - let mut walker = cs_cursor.walk_range(range)?; - - while let Some(Ok((block_number, cs))) = walker.next() { - Self::track_latest(&mut acc_candidates, cs.account_trie_keys, block_number); - Self::track_latest(&mut storage_candidates, cs.storage_trie_keys, block_number); - Self::track_latest(&mut hashed_acc_candidates, cs.hashed_account_keys, block_number); - Self::track_latest( - &mut hashed_storage_candidates, - cs.hashed_storage_keys, - block_number, - ); - } - - Ok(Some(PrunePlan { - earliest_block: earliest, - acc_survivors: Self::flatten_and_sort(acc_candidates), - storage_survivors: Self::flatten_and_sort(storage_candidates), - hashed_acc_survivors: Self::flatten_and_sort(hashed_acc_candidates), - hashed_storage_survivors: Self::flatten_and_sort(hashed_storage_candidates), - })) - } - - /// Records the latest block number for each key in a candidate map. - /// Used during prune planning to identify survivor versions. - fn track_latest( - candidates: &mut HashMap, - keys: impl IntoIterator, - block_number: u64, - ) { - for k in keys { - candidates - .entry(k) - .and_modify(|curr| *curr = (*curr).max(block_number)) - .or_insert(block_number); - } - } - - /// Helper to flatten `HashMap` into a sorted Vector of survivors. - /// Sorting is required to ensure optimal sequential seek performance in MDBX. - fn flatten_and_sort(map: HashMap) -> Vec<(K, u64)> { - let mut v: Vec<_> = map.into_iter().collect(); - v.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - v - } -} - -impl MdbxProofsProvider { - fn set_earliest_block_number_inner( - &self, - block_number: u64, - hash: B256, - ) -> OpProofsStorageResult<()> { - let mut cursor = self.tx.cursor_write::()?; - cursor.upsert(ProofWindowKey::EarliestBlock, &BlockNumberHash::new(block_number, hash))?; - Ok(()) - } - - fn set_latest_block_number_inner( - &self, - block_number: u64, - hash: B256, - ) -> OpProofsStorageResult<()> { - let mut cursor = self.tx.cursor_write::()?; - cursor.upsert(ProofWindowKey::LatestBlock, &BlockNumberHash::new(block_number, hash))?; - Ok(()) - } - - /// Persist a batch of versioned history entries to a dup-sorted table. - /// - /// # Parameters - /// - `block_number`: Target block number for versioning entries - /// - `items`: **Must be sorted** - iterator of entries to persist - /// - `append_mode`: Mode selector for write strategy: - /// - `true` (Append): Appends all entries including tombstones for forward progress - /// - `false` (Prune): Removes tombstones, writes non-tombstones to block 0 - /// - /// The cost of pruning is the cost of (append + deleting tombstones + deleting old block 0). - /// The tombstones deletion is expensive as it requires a seek for each (key + subkey). - /// - /// Uses [`reth_db::mdbx::cursor::Cursor::upsert`] for upsert operation. - fn persist_history_batch( - &self, - block_number: T::SubKey, - items: I, - append_mode: bool, - ) -> OpProofsStorageResult> - where - T: Table> + DupSort, - T::Key: Clone, - I: IntoIterator, - I::Item: IntoKV, - { - let mut cur = self.tx.cursor_dup_write::()?; - let mut keys = Vec::::new(); - - // Materialize iterator to enable partitioning and collect keys - let mut pairs: Vec<(T::Key, T::Value)> = Vec::new(); - for it in items { - let (k, vv) = it.into_kv(block_number); - pairs.push((k.clone(), vv)); - keys.push(k) - } - - if append_mode { - // Append all entries (including tombstones) to preserve full history - for (k, vv) in pairs { - cur.append_dup(k.clone(), vv)?; - } - return Ok(keys); - } - - // Drop current cursor to start clean for Phase 1 - drop(cur); - - // Phase 1: Batch Delete (Sequential) - // Remove all existing state at Block 0 for these keys. - { - let mut del_cur = self.tx.cursor_dup_write::()?; - for (k, _) in &pairs { - // Seek to (Key, Block 0) - if let Some(vv) = del_cur.seek_by_key_subkey(k.clone(), 0)? && - vv.block_number == 0 - { - del_cur.delete_current()?; - } - } - } - - // Phase 2: Batch Write (Sequential) - // Write new values (skipping tombstones). - { - let mut write_cur = self.tx.cursor_dup_write::()?; - for (k, vv) in pairs { - if vv.value.0.is_some() { - write_cur.upsert(k, &vv)?; - } - } - } - - Ok(keys) - } - - /// Delete entries for `items` at exactly `block_number` in a dup-sorted table. - /// Seeks (key, block) and deletes current if the subkey matches. - fn delete_dup_sorted(&self, items: I) -> OpProofsStorageResult<()> - where - T: Table> + DupSort, - T::Key: Clone, - T::SubKey: PartialEq + Clone, - I: IntoIterator, - { - let mut cur = self.tx.cursor_dup_write::()?; - for (key, subkey) in items { - if let Some(vv) = cur.seek_by_key_subkey(key, subkey)? - // ensure we didn't land on a >subkey - && vv.block_number == subkey - { - cur.delete_current()?; - } - } - Ok(()) - } - - /// Delete history versions for `items` that are strictly older than the provided block number. - /// `items` is a list of (Key, `SurvivorBlock`). Everything strictly older than `SurvivorBlock` - /// is deleted. Returns the number of entries deleted. - fn prune_history_preceding( - &self, - cutoff_items: Vec<(T::Key, u64)>, - ) -> OpProofsStorageResult - where - T: Table> + DupSort, - T::Key: Clone + Ord, - { - if cutoff_items.is_empty() { - return Ok(0); - } - - let mut deleted_count = 0; - let mut cur = self.tx.cursor_dup_write::()?; - for (key, survivor_block) in cutoff_items { - // Seek to the start of history for this key (Block 0) - if let Some(mut entry) = cur.seek_by_key_subkey(key.clone(), 0)? { - loop { - if entry.block_number >= survivor_block { - // Reached the survivor version (or newer). Stop deleting for this key. - - // If the survivor is a tombstone (None), delete it too. - // Since we just deleted all older history, a tombstone at the start of - // history is redundant (it implies "does not - // exist"). - if entry.block_number == survivor_block && entry.value.0.is_none() { - cur.delete_current()?; - deleted_count += 1; - } - break; - } - - // Entry is strictly older than survivor. Delete it. - cur.delete_current()?; - deleted_count += 1; - - // MDBX delete_current() automatically advances the cursor to the next item. - // We check if the next item is still the same key. - match cur.current() { - Ok(Some((k, v))) => { - if k != key { - break; // Moved past the key - } - entry = v; - } - _ => break, // End of table or error - } - } - } - } - Ok(deleted_count) - } - - /// Collect versioned history over `block_range` using `BlockChangeSet`. - fn collect_history_ranged( - &self, - block_range: impl RangeBounds, - ) -> OpProofsStorageResult { - let mut history = HistoryDeleteBatch::default(); - let mut change_set_cursor = self.tx.cursor_read::()?; - let mut walker = change_set_cursor.walk_range(block_range)?; - - while let Some(Ok((block_number, change_set))) = walker.next() { - // Push (key, subkey=block_number) pairs - history - .account_trie - .extend(change_set.account_trie_keys.into_iter().map(|k| (k, block_number))); - history - .storage_trie - .extend(change_set.storage_trie_keys.into_iter().map(|k| (k, block_number))); - history - .hashed_account - .extend(change_set.hashed_account_keys.into_iter().map(|k| (k, block_number))); - history - .hashed_storage - .extend(change_set.hashed_storage_keys.into_iter().map(|k| (k, block_number))); - } - - // Sorting by tuple sorts by key first, then by block_number. - history.account_trie.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); - history.storage_trie.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); - history.hashed_account.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); - history.hashed_storage.sort_by(|(k1, b1), (k2, b2)| k1.cmp(k2).then_with(|| b1.cmp(b2))); - - Ok(history) - } - - /// Delete versioned history over `block_range` using history batch. - fn delete_history_ranged( - &self, - block_range: impl RangeBounds, - history: HistoryDeleteBatch, - ) -> OpProofsStorageResult { - let mut change_set_cursor = self.tx.cursor_write::()?; - let mut walker = change_set_cursor.walk_range(block_range)?; - - while let Some(Ok((_, _))) = walker.next() { - walker.delete_current()?; - } - - // Delete using the simplified API: iterator of (key, subkey) - self.delete_dup_sorted::(history.clone().account_trie)?; - self.delete_dup_sorted::(history.clone().storage_trie)?; - self.delete_dup_sorted::(history.clone().hashed_account)?; - self.delete_dup_sorted::(history.clone().hashed_storage)?; - - Ok(WriteCounts { - account_trie_updates_written_total: history.account_trie.len() as u64, - storage_trie_updates_written_total: history.storage_trie.len() as u64, - hashed_accounts_written_total: history.hashed_account.len() as u64, - hashed_storages_written_total: history.hashed_storage.len() as u64, - }) - } - - /// Write trie/state history for `block_number` from `block_state_diff`. - fn store_trie_updates_for_block( - &self, - block_number: u64, - block_state_diff: BlockStateDiff, - append_mode: bool, - ) -> OpProofsStorageResult { - let BlockStateDiff { sorted_trie_updates, sorted_post_state } = block_state_diff; - - let storage_trie_len = sorted_trie_updates.storage_tries_ref().len(); - let hashed_storage_len = sorted_post_state.storages.len(); - - let account_trie_keys = self.persist_history_batch( - block_number, - sorted_trie_updates.account_nodes_ref().iter().cloned(), - append_mode, - )?; - let hashed_account_keys = self.persist_history_batch( - block_number, - sorted_post_state.accounts.iter().copied(), - append_mode, - )?; - - let mut storage_trie_keys = Vec::::with_capacity(storage_trie_len); - for (hashed_address, nodes) in sorted_trie_updates.storage_tries_ref() { - // Handle wiped - mark all storage trie as deleted at the current block number - if nodes.is_deleted && append_mode { - let cursor = self.tx.cursor_dup_read::()?; - let mut ro = MdbxTrieCursor::new(cursor, block_number - 1, Some(*hashed_address)); - - // Merge old tombstones with new nodes in sorted key order. - // A BTreeMap ensures each path appears once and append_dup - // sees strictly increasing keys. - let mut merged: std::collections::BTreeMap> = - std::collections::BTreeMap::new(); - - // Old paths → tombstones - while let Some((path, _node)) = ro.next()? { - merged.insert(path, None); - } - - // New nodes override tombstones or add fresh entries - for (path, node) in nodes.storage_nodes_ref().iter().cloned() { - merged.insert(path, node); - } - - let mut cur = self.tx.cursor_dup_write::()?; - for (path, value) in merged { - let key = StorageTrieKey::new(*hashed_address, StoredNibbles::from(path)); - let vv = VersionedValue { block_number, value: MaybeDeleted(value) }; - cur.append_dup(key.clone(), vv)?; - storage_trie_keys.push(key); - } - - continue; - } - - let keys = self.persist_history_batch( - block_number, - nodes - .storage_nodes_ref() - .iter() - .cloned() - .map(|(path, node)| (*hashed_address, path, node)), - append_mode, - )?; - storage_trie_keys.extend(keys); - } - - let mut hashed_storage_keys = Vec::::with_capacity(hashed_storage_len); - for (hashed_address, storage) in sorted_post_state.storages { - if append_mode && storage.is_wiped() { - let cursor = self.tx.cursor_dup_read::()?; - let mut ro = MdbxStorageCursor::new(cursor, block_number - 1, hashed_address); - - // Merge old tombstones with new slot values in sorted key - // order. A BTreeMap ensures each slot appears once and - // append_dup sees strictly increasing keys. - let mut merged: std::collections::BTreeMap> = - std::collections::BTreeMap::new(); - - // Old slots → tombstones - while let Some((slot, _val)) = ro.next()? { - merged.insert(slot, None); - } - - // New slots override tombstones or add fresh entries - for (slot, val) in storage.storage_slots_ref() { - merged.insert(*slot, Some(StorageValue(*val))); - } - - let mut cur = self.tx.cursor_dup_write::()?; - for (slot, value) in merged { - let key = HashedStorageKey::new(hashed_address, slot); - let vv = VersionedValue { block_number, value: MaybeDeleted(value) }; - cur.append_dup(key.clone(), vv)?; - hashed_storage_keys.push(key); - } - - continue; - } - let keys = self.persist_history_batch( - block_number, - storage - .storage_slots_ref() - .iter() - .map(|(key, val)| (hashed_address, *key, Some(StorageValue(*val)))), - append_mode, - )?; - hashed_storage_keys.extend(keys); - } - - Ok(ChangeSet { - account_trie_keys, - storage_trie_keys, - hashed_account_keys, - hashed_storage_keys, - }) - } - - fn store_trie_updates_append_only_inner( - &self, - block_ref: BlockWithParent, - block_state_diff: BlockStateDiff, - ) -> OpProofsStorageResult { - let block_number = block_ref.block.number; - - let latest_block_hash = self.get_block_number_hash_inner(ProofWindowKey::LatestBlock)?.hash; - - if latest_block_hash != block_ref.parent { - return Err(OpProofsStorageError::OutOfOrder { - block_number, - parent_block_hash: block_ref.parent, - latest_block_hash, - }); - } - - let change_set = - &self.store_trie_updates_for_block(block_number, block_state_diff, true)?; - - let mut change_set_cursor = self.tx.cursor_write::()?; - change_set_cursor.append(block_number, change_set)?; - - self.set_latest_block_number_inner(block_number, block_ref.block.hash)?; - - Ok(WriteCounts { - account_trie_updates_written_total: change_set.account_trie_keys.len() as u64, - storage_trie_updates_written_total: change_set.storage_trie_keys.len() as u64, - hashed_accounts_written_total: change_set.hashed_account_keys.len() as u64, - hashed_storages_written_total: change_set.hashed_storage_keys.len() as u64, - }) - } - - fn get_initial_state_anchor_inner(&self) -> OpProofsStorageResult> { - let mut cur = self.tx.cursor_read::()?; - Ok(cur.seek_exact(ProofWindowKey::InitialStateAnchor)?.map(|(_k, v)| v.into())) - } - - fn get_latest_key_inner(&self) -> OpProofsStorageResult> - where - T: Table, - { - let mut cursor = self.tx.cursor_read::()?; - Ok(cursor.last()?.map(|(k, _)| k)) - } -} - -impl OpProofsProviderRO for MdbxProofsProvider { - type StorageTrieCursor<'tx> - = MdbxTrieCursor> - where - Self: 'tx, - TX: 'tx; - - type AccountTrieCursor<'tx> - = MdbxTrieCursor> - where - Self: 'tx, - TX: 'tx; - - type StorageCursor<'tx> - = MdbxStorageCursor> - where - Self: 'tx, - TX: 'tx; - - type AccountHashedCursor<'tx> - = MdbxAccountCursor> - where - Self: 'tx, - TX: 'tx; - - fn get_earliest_block(&self) -> OpProofsStorageResult { - self.get_block_number_hash_inner(ProofWindowKey::EarliestBlock) - } - - fn get_latest_block(&self) -> OpProofsStorageResult { - self.get_block_number_hash_inner(ProofWindowKey::LatestBlock) - } - - fn get_proof_window(&self) -> OpProofsStorageResult { - let mut cursor = self.tx.cursor_read::()?; - let earliest = cursor - .seek_exact(ProofWindowKey::EarliestBlock)? - .map(|(_, val)| NumHash::new(val.number(), *val.hash())) - .ok_or(NoBlocksFound)?; - let latest = cursor - .seek_exact(ProofWindowKey::LatestBlock)? - .map(|(_, val)| NumHash::new(val.number(), *val.hash())) - .ok_or(NoBlocksFound)?; - Ok(ProofWindowRange { earliest, latest }) - } - - fn storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.tx.cursor_dup_read::()?; - Ok(MdbxTrieCursor::new(cursor, max_block_number, Some(hashed_address))) - } - - fn account_trie_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.tx.cursor_dup_read::()?; - Ok(MdbxTrieCursor::new(cursor, max_block_number, None)) - } - - fn storage_hashed_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.tx.cursor_dup_read::()?; - Ok(MdbxStorageCursor::new(cursor, max_block_number, hashed_address)) - } - - fn account_hashed_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.tx.cursor_dup_read::()?; - Ok(MdbxAccountCursor::new(cursor, max_block_number)) - } - - fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { - let mut change_set_cursor = self.tx.cursor_read::()?; - let (_, change_set) = change_set_cursor - .seek_exact(block_number)? - .ok_or(OpProofsStorageError::NoChangeSetForBlock(block_number))?; - - let mut account_trie_cursor = self.tx.cursor_dup_read::()?; - let mut storage_trie_cursor = self.tx.cursor_dup_read::()?; - let mut hashed_account_cursor = self.tx.cursor_dup_read::()?; - let mut hashed_storage_cursor = self.tx.cursor_dup_read::()?; - - let mut trie_updates = TrieUpdates::default(); - for key in change_set.account_trie_keys { - let entry = match account_trie_cursor.seek_by_key_subkey(key.clone(), block_number)? { - Some(v) if v.block_number == block_number => v.value.0, - _ => { - return Err(OpProofsStorageError::MissingAccountTrieHistory( - key.0, - block_number, - )); - } - }; - - if let Some(value) = entry { - trie_updates.account_nodes.insert(key.0, value); - } else { - trie_updates.removed_nodes.insert(key.0); - } - } - - for key in change_set.storage_trie_keys { - let entry = match storage_trie_cursor.seek_by_key_subkey(key.clone(), block_number)? { - Some(v) if v.block_number == block_number => v.value.0, - _ => { - return Err(OpProofsStorageError::MissingStorageTrieHistory( - key.hashed_address, - key.path.0, - block_number, - )); - } - }; - - let stu = trie_updates - .storage_tries - .entry(key.hashed_address) - .or_insert_with(StorageTrieUpdates::default); - - if let Some(value) = entry { - stu.storage_nodes.insert(key.path.0, value); - } else { - stu.removed_nodes.insert(key.path.0); - } - } - - let mut post_state = HashedPostState::with_capacity(change_set.hashed_account_keys.len()); - for key in change_set.hashed_account_keys { - let entry = match hashed_account_cursor.seek_by_key_subkey(key, block_number)? { - Some(v) if v.block_number == block_number => v.value.0, - _ => { - return Err(OpProofsStorageError::MissingHashedAccountHistory( - key, - block_number, - )); - } - }; - - post_state.accounts.insert(key, entry); - } - - for key in change_set.hashed_storage_keys { - let entry = match hashed_storage_cursor.seek_by_key_subkey(key.clone(), block_number)? { - Some(v) if v.block_number == block_number => v.value.0, - _ => { - return Err(OpProofsStorageError::MissingHashedStorageHistory { - hashed_address: key.hashed_address, - hashed_storage_key: key.hashed_storage_key, - block_number, - }); - } - }; - - let hs = post_state.storages.entry(key.hashed_address).or_default(); - - if let Some(value) = entry { - hs.storage.insert(key.hashed_storage_key, value.0); - } else { - hs.storage.insert(key.hashed_storage_key, U256::ZERO); - } - } - - Ok(BlockStateDiff { - sorted_trie_updates: trie_updates.into_sorted(), - sorted_post_state: post_state.into_sorted(), - }) - } -} - -impl OpProofsProviderRw - for MdbxProofsProvider -{ - fn store_trie_updates( - &self, - block_ref: BlockWithParent, - block_state_diff: BlockStateDiff, - ) -> OpProofsStorageResult { - self.store_trie_updates_append_only_inner(block_ref, block_state_diff) - } - - fn store_trie_updates_batch( - &self, - updates: Vec<(BlockWithParent, BlockStateDiff)>, - ) -> OpProofsStorageResult { - let mut total_counts = WriteCounts::default(); - for (block_ref, block_state_diff) in updates { - let counts = self.store_trie_updates_append_only_inner(block_ref, block_state_diff)?; - - total_counts.account_trie_updates_written_total += - counts.account_trie_updates_written_total; - total_counts.storage_trie_updates_written_total += - counts.storage_trie_updates_written_total; - total_counts.hashed_accounts_written_total += counts.hashed_accounts_written_total; - total_counts.hashed_storages_written_total += counts.hashed_storages_written_total; - } - - Ok(total_counts) - } - - fn prune_earliest_state( - &self, - new_earliest_block_ref: BlockWithParent, - ) -> OpProofsStorageResult { - let target_block = new_earliest_block_ref.block.number; - - let plan = self.calculate_prune_plan(target_block)?; - let Some(plan) = plan else { - return Ok(WriteCounts::default()); - }; - - let acc_deleted = - self.prune_history_preceding::(plan.acc_survivors)?; - - let st_deleted = - self.prune_history_preceding::(plan.storage_survivors)?; - - let ha_deleted = - self.prune_history_preceding::(plan.hashed_acc_survivors)?; - - let hs_deleted = - self.prune_history_preceding::(plan.hashed_storage_survivors)?; - - let counts = WriteCounts { - account_trie_updates_written_total: acc_deleted, - storage_trie_updates_written_total: st_deleted, - hashed_accounts_written_total: ha_deleted, - hashed_storages_written_total: hs_deleted, - }; - - let range = (plan.earliest_block + 1)..=target_block; - let mut cs_cursor = self.tx.cursor_write::()?; - let mut walker = cs_cursor.walk_range(range)?; - while walker.next().is_some() { - walker.delete_current()?; - } - - self.set_earliest_block_number_inner(target_block, new_earliest_block_ref.block.hash)?; - - Ok(counts) - } - - fn unwind_history(&self, to: BlockWithParent) -> OpProofsStorageResult<()> { - let history_to_delete = self.collect_history_ranged(to.block.number..)?; - - let proof_window = self.get_proof_window()?; - - if to.block.number > proof_window.latest.number { - return Ok(()); - } - - if to.block.number <= proof_window.earliest.number { - return Err(OpProofsStorageError::UnwindBeyondEarliest { - unwind_block_number: to.block.number, - earliest_block_number: proof_window.earliest.number, - }); - } - - self.delete_history_ranged(to.block.number.., history_to_delete)?; - - let new_latest_block = BlockNumberHash::new(to.block.number.saturating_sub(1), to.parent); - - self.set_latest_block_number_inner(new_latest_block.number(), *new_latest_block.hash())?; - - Ok(()) - } - - fn replace_updates( - &self, - latest_common_block: BlockNumHash, - mut blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)>, - ) -> OpProofsStorageResult<()> { - let proof_window = self.get_proof_window()?; - - if latest_common_block.number <= proof_window.earliest.number || - latest_common_block.number > proof_window.latest.number - { - return Err(OpProofsStorageError::ReorgBaseOutOfWindow { - block_number: latest_common_block.number, - earliest_block_number: proof_window.earliest.number, - latest_block_number: proof_window.latest.number, - }); - } - - blocks_to_add.sort_unstable_by_key(|(bwp, _)| bwp.block.number); - - let history_to_delete = self.collect_history_ranged(latest_common_block.number + 1..)?; - self.delete_history_ranged(latest_common_block.number + 1.., history_to_delete)?; - - self.set_latest_block_number_inner(latest_common_block.number, latest_common_block.hash)?; - - for (block_with_parent, diff) in blocks_to_add { - self.store_trie_updates_append_only_inner(block_with_parent, diff)?; - } - Ok(()) - } - - fn commit(self) -> OpProofsStorageResult<()> { - self.tx.commit()?; - Ok(()) - } -} - -impl OpProofsInitProvider - for MdbxProofsProvider -{ - fn initial_state_anchor(&self) -> OpProofsStorageResult { - let Some(block) = self.get_initial_state_anchor_inner()? else { - return Ok(InitialStateAnchor::default()); - }; - - let status = match self.get_block_number_hash_inner(ProofWindowKey::EarliestBlock) { - Ok(_) => InitialStateStatus::Completed, - Err(NoBlocksFound) => InitialStateStatus::InProgress, - Err(err) => return Err(err), - }; - - Ok(InitialStateAnchor { - block: Some(block), - status, - latest_account_trie_key: self.get_latest_key_inner::()?, - latest_storage_trie_key: self.get_latest_key_inner::()?, - latest_hashed_account_key: self.get_latest_key_inner::()?, - latest_hashed_storage_key: self.get_latest_key_inner::()?, - }) - } - - fn set_initial_state_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()> { - let mut cur = self.tx.cursor_write::()?; - cur.insert(ProofWindowKey::InitialStateAnchor, &anchor.into())?; - Ok(()) - } - - fn store_account_branches( - &self, - account_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()> { - if account_nodes.is_empty() { - return Ok(()); - } - self.persist_history_batch(0, account_nodes, true)?; - Ok(()) - } - - fn store_storage_branches( - &self, - hashed_address: B256, - storage_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()> { - if storage_nodes.is_empty() { - return Ok(()); - } - self.persist_history_batch( - 0, - storage_nodes.into_iter().map(|(key, val)| (hashed_address, key, val)), - true, - )?; - Ok(()) - } - - fn store_hashed_accounts( - &self, - accounts: Vec<(B256, Option)>, - ) -> OpProofsStorageResult<()> { - if accounts.is_empty() { - return Ok(()); - } - self.persist_history_batch(0, accounts, true)?; - Ok(()) - } - - fn store_hashed_storages( - &self, - hashed_address: B256, - storages: Vec<(B256, U256)>, - ) -> OpProofsStorageResult<()> { - if storages.is_empty() { - return Ok(()); - } - self.persist_history_batch( - 0, - storages.into_iter().map(|(key, val)| (hashed_address, key, Some(StorageValue(val)))), - true, - )?; - Ok(()) - } - - fn commit_initial_state(&self) -> OpProofsStorageResult { - let anchor = self.get_initial_state_anchor_inner()?.ok_or(NoBlocksFound)?; - self.set_earliest_block_number_inner(anchor.number, anchor.hash)?; - self.set_latest_block_number_inner(anchor.number, anchor.hash)?; - Ok(anchor) - } - - fn commit(self) -> OpProofsStorageResult<()> { - self.tx.commit()?; - Ok(()) - } -} - -impl OpProofsStore for MdbxProofsStorage { - type ProviderRO<'a> = Arc::TX>>; - type ProviderRw<'a> = MdbxProofsProvider<::TXMut>; - type Initializer<'a> = MdbxProofsProvider<::TXMut>; - - fn provider_ro<'a>(&'a self) -> OpProofsStorageResult> { - Ok(Arc::new(MdbxProofsProvider::new(self.env.tx()?))) - } - - fn provider_rw<'a>(&'a self) -> OpProofsStorageResult> { - Ok(MdbxProofsProvider::new(self.env.tx_mut()?)) - } - - fn initialization_provider<'a>(&'a self) -> OpProofsStorageResult> { - Ok(MdbxProofsProvider::new(self.env.tx_mut()?)) - } -} - -/// This implementation is copied from the -/// [`DatabaseMetrics`](reth_db::database_metrics::DatabaseMetrics) implementation for -/// [`DatabaseEnv`]. As the implementation hard-coded the table name, we need to reimplement it. -#[cfg(feature = "metrics")] -impl reth_db::database_metrics::DatabaseMetrics for MdbxProofsStorage { - fn report_metrics(&self) { - for (name, value, labels) in self.gauge_metrics() { - gauge!(name, labels).set(value); - } - } - - fn gauge_metrics(&self) -> Vec<(&'static str, f64, Vec

{ - provider: P, - metrics: Arc, -} - -impl OpProofsProviderRO for OpProofsProviderROWithMetrics

{ - type StorageTrieCursor<'tx> - = OpProofsTrieCursorWithMetrics> - where - Self: 'tx; - type AccountTrieCursor<'tx> - = OpProofsTrieCursorWithMetrics> - where - Self: 'tx; - type StorageCursor<'tx> - = OpProofsHashedCursorWithMetrics> - where - Self: 'tx; - type AccountHashedCursor<'tx> - = OpProofsHashedCursorWithMetrics> - where - Self: 'tx; - - #[inline] - fn get_earliest_block(&self) -> OpProofsStorageResult { - let result = self.provider.get_earliest_block()?; - self.metrics.proof_window.earliest.set(result.number as f64); - Ok(result) - } - - #[inline] - fn get_latest_block(&self) -> OpProofsStorageResult { - let result = self.provider.get_latest_block()?; - self.metrics.proof_window.latest.set(result.number as f64); - Ok(result) - } - - #[inline] - fn get_proof_window(&self) -> OpProofsStorageResult { - let result = self.provider.get_proof_window()?; - self.metrics.proof_window.earliest.set(result.earliest.number as f64); - self.metrics.proof_window.latest.set(result.latest.number as f64); - Ok(result) - } - - #[inline] - fn storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.storage_trie_cursor(hashed_address, max_block_number)?; - Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn account_trie_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.account_trie_cursor(max_block_number)?; - Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn storage_hashed_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.storage_hashed_cursor(hashed_address, max_block_number)?; - Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn account_hashed_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.account_hashed_cursor(max_block_number)?; - Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { - self.provider.fetch_trie_updates(block_number) - } -} - -/// Wrapper for [`OpProofsProviderRw`] that records metrics. -#[derive(Debug, Constructor, Clone)] -pub struct OpProofsProviderRwWithMetrics

{ - provider: P, - metrics: Arc, -} - -impl OpProofsProviderRO for OpProofsProviderRwWithMetrics

{ - type StorageTrieCursor<'tx> - = OpProofsTrieCursorWithMetrics> - where - Self: 'tx; - type AccountTrieCursor<'tx> - = OpProofsTrieCursorWithMetrics> - where - Self: 'tx; - type StorageCursor<'tx> - = OpProofsHashedCursorWithMetrics> - where - Self: 'tx; - type AccountHashedCursor<'tx> - = OpProofsHashedCursorWithMetrics> - where - Self: 'tx; - - #[inline] - fn get_earliest_block(&self) -> OpProofsStorageResult { - let result = self.provider.get_earliest_block()?; - self.metrics.proof_window.earliest.set(result.number as f64); - Ok(result) - } - - #[inline] - fn get_latest_block(&self) -> OpProofsStorageResult { - let result = self.provider.get_latest_block()?; - self.metrics.proof_window.latest.set(result.number as f64); - Ok(result) - } - - #[inline] - fn get_proof_window(&self) -> OpProofsStorageResult { - let result = self.provider.get_proof_window()?; - self.metrics.proof_window.earliest.set(result.earliest.number as f64); - self.metrics.proof_window.latest.set(result.latest.number as f64); - Ok(result) - } - - #[inline] - fn storage_trie_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.storage_trie_cursor(hashed_address, max_block_number)?; - Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn account_trie_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.account_trie_cursor(max_block_number)?; - Ok(OpProofsTrieCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn storage_hashed_cursor<'tx>( - &self, - hashed_address: B256, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.storage_hashed_cursor(hashed_address, max_block_number)?; - Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn account_hashed_cursor<'tx>( - &self, - max_block_number: u64, - ) -> OpProofsStorageResult> { - let cursor = self.provider.account_hashed_cursor(max_block_number)?; - Ok(OpProofsHashedCursorWithMetrics::new(cursor, self.metrics.clone())) - } - - #[inline] - fn fetch_trie_updates(&self, block_number: u64) -> OpProofsStorageResult { - self.provider.fetch_trie_updates(block_number) - } -} - -impl OpProofsProviderRw for OpProofsProviderRwWithMetrics

{ - #[inline] - fn store_trie_updates( - &self, - block_ref: BlockWithParent, - block_state_diff: BlockStateDiff, - ) -> OpProofsStorageResult { - let result = self.provider.store_trie_updates(block_ref, block_state_diff)?; - self.metrics.proof_window.latest.set(block_ref.block.number as f64); - Ok(result) - } - - #[inline] - fn store_trie_updates_batch( - &self, - updates: Vec<(BlockWithParent, BlockStateDiff)>, - ) -> OpProofsStorageResult { - let result = self.provider.store_trie_updates_batch(updates.clone())?; - if let Some((latest_block_ref, _)) = updates.last() { - self.metrics.proof_window.latest.set(latest_block_ref.block.number as f64); - } - Ok(result) - } - - #[inline] - fn prune_earliest_state( - &self, - new_earliest_block_ref: BlockWithParent, - ) -> OpProofsStorageResult { - self.metrics.proof_window.earliest.set(new_earliest_block_ref.block.number as f64); - self.provider.prune_earliest_state(new_earliest_block_ref) - } - - #[inline] - fn unwind_history(&self, to: BlockWithParent) -> OpProofsStorageResult<()> { - self.provider.unwind_history(to) - } - - #[inline] - fn replace_updates( - &self, - latest_common_block: BlockNumHash, - blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)>, - ) -> OpProofsStorageResult<()> { - self.provider.replace_updates(latest_common_block, blocks_to_add) - } - - #[inline] - fn commit(self) -> OpProofsStorageResult<()> { - self.provider.commit() - } -} - -/// Wrapper for [`OpProofsInitProvider`] that records metrics. -#[derive(Debug, Constructor)] -pub struct OpProofsInitProviderWithMetrics

{ - provider: P, - metrics: Arc, -} - -impl OpProofsInitProvider for OpProofsInitProviderWithMetrics

{ - #[inline] - fn initial_state_anchor(&self) -> OpProofsStorageResult { - self.provider.initial_state_anchor() - } - - #[inline] - fn set_initial_state_anchor(&self, anchor: BlockNumHash) -> OpProofsStorageResult<()> { - self.provider.set_initial_state_anchor(anchor) - } - - #[inline] - fn store_account_branches( - &self, - account_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()> { - let count = account_nodes.len(); - let start = Instant::now(); - let result = self.provider.store_account_branches(account_nodes); - let duration = start.elapsed(); - - // Record per-item duration - if count > 0 { - self.metrics.record_duration_per_item( - StorageOperation::StoreAccountBranch, - duration, - count, - ); - } - - result - } - - #[inline] - fn store_storage_branches( - &self, - hashed_address: B256, - storage_nodes: Vec<(Nibbles, Option)>, - ) -> OpProofsStorageResult<()> { - let count = storage_nodes.len(); - let start = Instant::now(); - let result = self.provider.store_storage_branches(hashed_address, storage_nodes); - let duration = start.elapsed(); - - // Record per-item duration - if count > 0 { - self.metrics.record_duration_per_item( - StorageOperation::StoreStorageBranch, - duration, - count, - ); - } - - result - } - - #[inline] - fn store_hashed_accounts( - &self, - accounts: Vec<(B256, Option)>, - ) -> OpProofsStorageResult<()> { - let count = accounts.len(); - let start = Instant::now(); - let result = self.provider.store_hashed_accounts(accounts); - let duration = start.elapsed(); - - // Record per-item duration - if count > 0 { - self.metrics.record_duration_per_item( - StorageOperation::StoreHashedAccount, - duration, - count, - ); - } - - result - } - - #[inline] - fn store_hashed_storages( - &self, - hashed_address: B256, - storages: Vec<(B256, U256)>, - ) -> OpProofsStorageResult<()> { - let count = storages.len(); - let start = Instant::now(); - let result = self.provider.store_hashed_storages(hashed_address, storages); - let duration = start.elapsed(); - - // Record per-item duration - if count > 0 { - self.metrics.record_duration_per_item( - StorageOperation::StoreHashedStorage, - duration, - count, - ); - } - - result - } - - #[inline] - fn commit_initial_state(&self) -> OpProofsStorageResult { - let block = self.provider.commit_initial_state()?; - self.metrics.proof_window.earliest.set(block.number as f64); - Ok(block) - } - - #[inline] - fn commit(self) -> OpProofsStorageResult<()> { - self.provider.commit() - } -} - -impl From for OpProofsStoreWithMetrics -where - S: OpProofsStore + Clone + 'static, -{ - fn from(storage: S) -> Self { - Self::new(storage) - } -} diff --git a/vendor/reth-optimism-trie/src/proof.rs b/vendor/reth-optimism-trie/src/proof.rs deleted file mode 100644 index 075ac6e..0000000 --- a/vendor/reth-optimism-trie/src/proof.rs +++ /dev/null @@ -1,403 +0,0 @@ -//! Provides proof operation implementations for [`crate::OpProofsStorage`]. - -use crate::{ - OpProofsHashedAccountCursorFactory, OpProofsTrieCursorFactory, api::OpProofsProviderRO, -}; -use alloy_primitives::{ - Address, B256, Bytes, keccak256, - map::{B256Map, HashMap}, -}; -use reth_execution_errors::{StateProofError, StateRootError, StorageRootError, TrieWitnessError}; -use reth_trie::{ - StateRoot, StorageRoot, TrieType, - hashed_cursor::HashedPostStateCursorFactory, - metrics::TrieRootMetrics, - proof::{self, Proof}, - trie_cursor::InMemoryTrieCursorFactory, - witness::TrieWitness, -}; -use reth_trie_common::{ - AccountProof, HashedPostState, HashedPostStateSorted, HashedStorage, MultiProof, - MultiProofTargets, StorageMultiProof, StorageProof, TrieInput, updates::TrieUpdates, -}; - -/// Extends [`Proof`] with operations specific for working with [`crate::OpProofsStorage`]. -pub trait DatabaseProof

{ - /// Creates a new `DatabaseProof` instance from external storage. - fn from_provider(provider: P, block_number: u64) -> Self; - - /// Generates the state proof for target account based on [`TrieInput`]. - fn overlay_account_proof( - provider: P, - block_number: u64, - input: TrieInput, - address: Address, - slots: &[B256], - ) -> Result; - - /// Generates the state [`MultiProof`] for target hashed account and storage keys. - fn overlay_multiproof( - provider: P, - block_number: u64, - input: TrieInput, - targets: MultiProofTargets, - ) -> Result; -} - -impl

DatabaseProof

- for Proof, OpProofsHashedAccountCursorFactory

> -where - P: OpProofsProviderRO + Clone, -{ - /// Create a new [`Proof`] instance from [`crate::OpProofsStorage`]. - fn from_provider(provider: P, block_number: u64) -> Self { - Self::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - OpProofsHashedAccountCursorFactory::new(provider, block_number), - ) - } - - /// Generates the state proof for target account based on [`TrieInput`]. - fn overlay_account_proof( - provider: P, - block_number: u64, - input: TrieInput, - address: Address, - slots: &[B256], - ) -> Result { - let nodes_sorted = input.nodes.into_sorted(); - let state_sorted = input.state.into_sorted(); - Self::from_provider(provider.clone(), block_number) - .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - &nodes_sorted, - )) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - )) - .with_prefix_sets_mut(input.prefix_sets) - .account_proof(address, slots) - } - - /// Generates the state [`MultiProof`] for target hashed account and storage keys. - fn overlay_multiproof( - provider: P, - block_number: u64, - input: TrieInput, - targets: MultiProofTargets, - ) -> Result { - let nodes_sorted = input.nodes.into_sorted(); - let state_sorted = input.state.into_sorted(); - Self::from_provider(provider.clone(), block_number) - .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - &nodes_sorted, - )) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - )) - .with_prefix_sets_mut(input.prefix_sets) - .multiproof(targets) - } -} - -/// Extends [`StorageProof`] with operations specific for working with [`crate::OpProofsStorage`]. -pub trait DatabaseStorageProof

{ - /// Create a new [`StorageProof`] from [`crate::OpProofsStorage`] and account address. - fn from_provider(provider: P, block_number: u64, address: Address) -> Self; - - /// Generates the storage proof for target slot based on [`TrieInput`]. - fn overlay_storage_proof( - provider: P, - block_number: u64, - address: Address, - slot: B256, - storage: HashedStorage, - ) -> Result; - - /// Generates the storage multiproof for target slots based on [`TrieInput`]. - fn overlay_storage_multiproof( - provider: P, - block_number: u64, - address: Address, - slots: &[B256], - storage: HashedStorage, - ) -> Result; -} - -impl

DatabaseStorageProof

- for proof::StorageProof< - 'static, - OpProofsTrieCursorFactory

, - OpProofsHashedAccountCursorFactory

, - > -where - P: OpProofsProviderRO + Clone, -{ - /// Create a new [`StorageProof`] from [`crate::OpProofsStorage`] and account address. - fn from_provider(provider: P, block_number: u64, address: Address) -> Self { - Self::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - OpProofsHashedAccountCursorFactory::new(provider, block_number), - address, - ) - } - - fn overlay_storage_proof( - provider: P, - block_number: u64, - address: Address, - slot: B256, - hashed_storage: HashedStorage, - ) -> Result { - let hashed_address = keccak256(address); - let prefix_set = hashed_storage.construct_prefix_set(); - let state_sorted = HashedPostStateSorted::new( - Default::default(), - HashMap::from_iter([(hashed_address, hashed_storage.into_sorted())]), - ); - Self::from_provider(provider.clone(), block_number, address) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - )) - .with_prefix_set_mut(prefix_set) - .storage_proof(slot) - } - - fn overlay_storage_multiproof( - provider: P, - block_number: u64, - address: Address, - slots: &[B256], - hashed_storage: HashedStorage, - ) -> Result { - let hashed_address = keccak256(address); - let targets = slots.iter().map(keccak256).collect(); - let prefix_set = hashed_storage.construct_prefix_set(); - let state_sorted = HashedPostStateSorted::new( - Default::default(), - HashMap::from_iter([(hashed_address, hashed_storage.into_sorted())]), - ); - Self::from_provider(provider.clone(), block_number, address) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - )) - .with_prefix_set_mut(prefix_set) - .storage_multiproof(targets) - } -} - -/// Extends [`StateRoot`] with operations specific for working with [`crate::OpProofsStorage`]. -pub trait DatabaseStateRoot

: Sized { - /// Calculate the state root for this [`HashedPostState`]. - /// Internally, this method retrieves prefixsets and uses them - /// to calculate incremental state root. - /// - /// # Returns - /// - /// The state root for this [`HashedPostState`]. - fn overlay_root( - provider: P, - block_number: u64, - post_state: HashedPostState, - ) -> Result; - - /// Calculates the state root for this [`HashedPostState`] and returns it alongside trie - /// updates. See [`Self::overlay_root`] for more info. - fn overlay_root_with_updates( - provider: P, - block_number: u64, - post_state: HashedPostState, - ) -> Result<(B256, TrieUpdates), StateRootError>; - - /// Calculates the state root for provided [`HashedPostState`] using cached intermediate nodes. - fn overlay_root_from_nodes( - provider: P, - block_number: u64, - input: TrieInput, - ) -> Result; - - /// Calculates the state root and trie updates for provided [`HashedPostState`] using - /// cached intermediate nodes. - fn overlay_root_from_nodes_with_updates( - provider: P, - block_number: u64, - input: TrieInput, - ) -> Result<(B256, TrieUpdates), StateRootError>; -} - -impl

DatabaseStateRoot

- for StateRoot, OpProofsHashedAccountCursorFactory

> -where - P: OpProofsProviderRO + Clone, -{ - fn overlay_root( - provider: P, - block_number: u64, - post_state: HashedPostState, - ) -> Result { - let prefix_sets = post_state.construct_prefix_sets().freeze(); - let state_sorted = post_state.into_sorted(); - StateRoot::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - ), - ) - .with_prefix_sets(prefix_sets) - .root() - } - - fn overlay_root_with_updates( - provider: P, - block_number: u64, - post_state: HashedPostState, - ) -> Result<(B256, TrieUpdates), StateRootError> { - let prefix_sets = post_state.construct_prefix_sets().freeze(); - let state_sorted = post_state.into_sorted(); - StateRoot::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - ), - ) - .with_prefix_sets(prefix_sets) - .root_with_updates() - } - - fn overlay_root_from_nodes( - provider: P, - block_number: u64, - input: TrieInput, - ) -> Result { - let state_sorted = input.state.into_sorted(); - let nodes_sorted = input.nodes.into_sorted(); - StateRoot::new( - InMemoryTrieCursorFactory::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - &nodes_sorted, - ), - HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - ), - ) - .with_prefix_sets(input.prefix_sets.freeze()) - .root() - } - - fn overlay_root_from_nodes_with_updates( - provider: P, - block_number: u64, - input: TrieInput, - ) -> Result<(B256, TrieUpdates), StateRootError> { - let state_sorted = input.state.into_sorted(); - let nodes_sorted = input.nodes.into_sorted(); - StateRoot::new( - InMemoryTrieCursorFactory::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - &nodes_sorted, - ), - HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - ), - ) - .with_prefix_sets(input.prefix_sets.freeze()) - .root_with_updates() - } -} - -/// Extends [`StorageRoot`] with operations specific for working with [`crate::OpProofsStorage`]. -pub trait DatabaseStorageRoot

{ - /// Calculates the storage root for provided [`HashedStorage`]. - fn overlay_root( - provider: P, - block_number: u64, - address: Address, - hashed_storage: HashedStorage, - ) -> Result; -} - -impl

DatabaseStorageRoot

- for StorageRoot, OpProofsHashedAccountCursorFactory

> -where - P: OpProofsProviderRO + Clone, -{ - fn overlay_root( - provider: P, - block_number: u64, - address: Address, - hashed_storage: HashedStorage, - ) -> Result { - let prefix_set = hashed_storage.construct_prefix_set().freeze(); - let state_sorted = - HashedPostState::from_hashed_storage(keccak256(address), hashed_storage).into_sorted(); - StorageRoot::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - ), - address, - prefix_set, - TrieRootMetrics::new(TrieType::Custom("op_historical_proofs_storage")), - ) - .root() - } -} - -/// Extends [`TrieWitness`] with operations specific for working with [`crate::OpProofsStorage`]. -pub trait DatabaseTrieWitness

{ - /// Creates a new [`TrieWitness`] instance from [`crate::OpProofsStorage`]. - fn from_provider(provider: P, block_number: u64) -> Self; - - /// Generates the trie witness for the target state based on [`TrieInput`]. - fn overlay_witness( - provider: P, - block_number: u64, - input: TrieInput, - target: HashedPostState, - ) -> Result, TrieWitnessError>; -} - -impl

DatabaseTrieWitness

- for TrieWitness, OpProofsHashedAccountCursorFactory

> -where - P: OpProofsProviderRO + Clone, -{ - fn from_provider(provider: P, block_number: u64) -> Self { - Self::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - OpProofsHashedAccountCursorFactory::new(provider, block_number), - ) - } - - fn overlay_witness( - provider: P, - block_number: u64, - input: TrieInput, - target: HashedPostState, - ) -> Result, TrieWitnessError> { - let nodes_sorted = input.nodes.into_sorted(); - let state_sorted = input.state.into_sorted(); - Self::from_provider(provider.clone(), block_number) - .with_trie_cursor_factory(InMemoryTrieCursorFactory::new( - OpProofsTrieCursorFactory::new(provider.clone(), block_number), - &nodes_sorted, - )) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - OpProofsHashedAccountCursorFactory::new(provider, block_number), - &state_sorted, - )) - .with_prefix_sets_mut(input.prefix_sets) - .always_include_root_node() - .compute(target) - } -} diff --git a/vendor/reth-optimism-trie/src/provider.rs b/vendor/reth-optimism-trie/src/provider.rs deleted file mode 100644 index 17af634..0000000 --- a/vendor/reth-optimism-trie/src/provider.rs +++ /dev/null @@ -1,257 +0,0 @@ -//! Provider for external proofs storage - -use crate::{ - OpProofsProviderRO, OpProofsStorageError, - proof::{ - DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot, - DatabaseTrieWitness, - }, -}; -use alloy_primitives::keccak256; -use derive_more::Constructor; -use reth_primitives_traits::{Account, Bytecode}; -use reth_provider::{ - AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, ProviderError, - ProviderResult, StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, -}; -use reth_revm::{ - db::BundleState, - primitives::{Address, B256, Bytes, StorageValue, alloy_primitives::BlockNumber}, -}; -use reth_trie::{ - ExecutionWitnessMode, StateRoot, StorageRoot, - hashed_cursor::HashedCursor, - proof::{self, Proof}, - witness::TrieWitness, -}; -use reth_trie_common::{ - AccountProof, HashedPostState, HashedStorage, KeccakKeyHasher, MultiProof, MultiProofTargets, - StorageMultiProof, StorageProof, TrieInput, updates::TrieUpdates, -}; -use std::fmt::Debug; - -/// State provider for external proofs storage. -#[derive(Constructor)] -pub struct OpProofsStateProviderRef<'a, P> { - /// Historical state provider for non-state related tasks. - latest: Box, - - /// Storage provider for state lookups. - provider: P, - - /// Max block number that can be used for state lookups. - block_number: BlockNumber, -} - -impl<'a, P> Debug for OpProofsStateProviderRef<'a, P> -where - P: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("OpProofsStateProviderRef") - .field("provider", &self.provider) - .field("block_number", &self.block_number) - .finish() - } -} - -impl From for ProviderError { - fn from(error: OpProofsStorageError) -> Self { - Self::other(error) - } -} - -impl<'a, P> BlockHashReader for OpProofsStateProviderRef<'a, P> { - fn block_hash(&self, number: BlockNumber) -> ProviderResult> { - self.latest.block_hash(number) - } - - fn canonical_hashes_range( - &self, - start: BlockNumber, - end: BlockNumber, - ) -> ProviderResult> { - self.latest.canonical_hashes_range(start, end) - } -} - -impl<'a, P> StateRootProvider for OpProofsStateProviderRef<'a, P> -where - P: OpProofsProviderRO + Clone, -{ - fn state_root(&self, state: HashedPostState) -> ProviderResult { - Ok(StateRoot::overlay_root(self.provider.clone(), self.block_number, state)?) - } - - fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult { - Ok(StateRoot::overlay_root_from_nodes(self.provider.clone(), self.block_number, input)?) - } - - fn state_root_with_updates( - &self, - state: HashedPostState, - ) -> ProviderResult<(B256, TrieUpdates)> { - Ok(StateRoot::overlay_root_with_updates(self.provider.clone(), self.block_number, state)?) - } - - fn state_root_from_nodes_with_updates( - &self, - input: TrieInput, - ) -> ProviderResult<(B256, TrieUpdates)> { - Ok(StateRoot::overlay_root_from_nodes_with_updates( - self.provider.clone(), - self.block_number, - input, - )?) - } -} - -impl<'a, P> StorageRootProvider for OpProofsStateProviderRef<'a, P> -where - P: OpProofsProviderRO + Clone, -{ - fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult { - StorageRoot::overlay_root(self.provider.clone(), self.block_number, address, storage) - .map_err(|err| ProviderError::Database(err.into())) - } - - fn storage_proof( - &self, - address: Address, - slot: B256, - storage: HashedStorage, - ) -> ProviderResult { - proof::StorageProof::overlay_storage_proof( - self.provider.clone(), - self.block_number, - address, - slot, - storage, - ) - .map_err(ProviderError::from) - } - - fn storage_multiproof( - &self, - address: Address, - slots: &[B256], - storage: HashedStorage, - ) -> ProviderResult { - proof::StorageProof::overlay_storage_multiproof( - self.provider.clone(), - self.block_number, - address, - slots, - storage, - ) - .map_err(ProviderError::from) - } -} - -impl<'a, P> StateProofProvider for OpProofsStateProviderRef<'a, P> -where - P: OpProofsProviderRO + Clone, -{ - fn proof( - &self, - input: TrieInput, - address: Address, - slots: &[B256], - ) -> ProviderResult { - Proof::overlay_account_proof( - self.provider.clone(), - self.block_number, - input, - address, - slots, - ) - .map_err(ProviderError::from) - } - - fn multiproof( - &self, - input: TrieInput, - targets: MultiProofTargets, - ) -> ProviderResult { - Proof::overlay_multiproof(self.provider.clone(), self.block_number, input, targets) - .map_err(ProviderError::from) - } - - fn witness( - &self, - input: TrieInput, - target: HashedPostState, - _mode: ExecutionWitnessMode, - ) -> ProviderResult> { - TrieWitness::overlay_witness(self.provider.clone(), self.block_number, input, target) - .map_err(ProviderError::from) - .map(|hm| hm.into_values().collect()) - } -} - -impl<'a, P> HashedPostStateProvider for OpProofsStateProviderRef<'a, P> { - fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState { - HashedPostState::from_bundle_state::(bundle_state.state()) - } -} - -impl<'a, P> AccountReader for OpProofsStateProviderRef<'a, P> -where - P: OpProofsProviderRO, -{ - fn basic_account(&self, address: &Address) -> ProviderResult> { - let hashed_key = keccak256(address.0); - Ok(self - .provider - .account_hashed_cursor(self.block_number) - .map_err(Into::::into)? - .seek(hashed_key) - .map_err(Into::::into)? - .and_then(|(key, account)| (key == hashed_key).then_some(account))) - } -} - -impl<'a, P> StateProvider for OpProofsStateProviderRef<'a, P> -where - P: OpProofsProviderRO + Clone, -{ - fn storage(&self, address: Address, storage_key: B256) -> ProviderResult> { - let hashed_key = keccak256(storage_key); - Ok(self - .provider - .storage_hashed_cursor(keccak256(address.0), self.block_number) - .map_err(Into::::into)? - .seek(hashed_key) - .map_err(Into::::into)? - .and_then(|(key, storage)| (key == hashed_key).then_some(storage))) - } -} - -impl<'a, P> BytecodeReader for OpProofsStateProviderRef<'a, P> { - fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { - self.latest.bytecode_by_hash(code_hash) - } -} - -#[cfg(all(test, not(feature = "metrics")))] -mod tests { - use super::*; - use crate::{InMemoryProofsStorage, api::OpProofsStore}; - use reth_provider::noop::NoopProvider; - - #[test] - fn test_op_proofs_state_provider_ref_debug() { - let latest: Box = Box::new(NoopProvider::default()); - let storage: crate::OpProofsStorage = InMemoryProofsStorage::new(); - // Create a provider from the store (in memory storage implements OpProofsStore) - let provider_ro = storage.provider_ro().unwrap(); - let block_number = 42u64; - - let provider = OpProofsStateProviderRef::new(latest, provider_ro, block_number); - - assert_eq!( - format!("{:?}", provider), - "OpProofsStateProviderRef { provider: InMemoryProofsProvider { inner: RwLock { data: InMemoryStorageInner { account_branches: {}, storage_branches: {}, hashed_accounts: {}, hashed_storages: {}, trie_updates: {}, post_states: {}, earliest_block: None, latest_block: None, anchor_block: None } } }, block_number: 42 }" - ); - } -} diff --git a/vendor/reth-optimism-trie/src/prune/error.rs b/vendor/reth-optimism-trie/src/prune/error.rs deleted file mode 100644 index 965def8..0000000 --- a/vendor/reth-optimism-trie/src/prune/error.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::{OpProofsStorageError, api::WriteCounts}; -use reth_provider::ProviderError; -use std::{ - fmt, - fmt::{Display, Formatter}, - time::Duration, -}; -use strum::Display; -use thiserror::Error; - -/// Result of [`OpProofStoragePruner::run`](crate::OpProofStoragePruner::run) execution. -pub type OpProofStoragePrunerResult = Result; - -/// Successful prune summary. -#[derive(Debug, Clone, Default, Eq, PartialEq)] -pub struct PrunerOutput { - /// Total elapsed wall time for this run (fetch + apply). - pub duration: Duration, - /// Earliest block at the start of the run. - pub start_block: u64, - /// New earliest block at the end of the run. - pub end_block: u64, - /// Number of entries updated/removed per table. - pub write_counts: WriteCounts, -} - -impl Display for PrunerOutput { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let blocks = self.end_block.saturating_sub(self.start_block); - let total_entries = self.write_counts.hashed_accounts_written_total + - self.write_counts.hashed_storages_written_total + - self.write_counts.account_trie_updates_written_total + - self.write_counts.storage_trie_updates_written_total; - write!( - f, - "Pruned {}→{} ({} blocks), entries={}, elapsed={:.3}s", - self.start_block, - self.end_block, - blocks, - total_entries, - self.duration.as_secs_f64(), - ) - } -} - -impl PrunerOutput { - /// extend the current [`PrunerOutput`] with another [`PrunerOutput`] - pub fn extend_ref(&mut self, other: Self) { - self.duration += other.duration; - // take the earliest start block - if self.start_block > other.start_block { - self.start_block = other.start_block; - } - // take the latest end block - if self.end_block < other.end_block { - self.end_block = other.end_block; - } - self.write_counts += other.write_counts; - } -} - -/// Error returned by the pruner. -#[derive(Debug, Error, Display)] -pub enum PrunerError { - /// Wrapped error from the underlying `OpProofStorage` layer. - Storage(#[from] OpProofsStorageError), - - /// Wrapped error from the reth db provider. - Provider(#[from] ProviderError), - - /// Block not found in the underlying reth storage provider. - BlockNotFound(u64), - - /// The pruner timed out before finishing the prune - TimedOut(Duration), -} - -#[cfg(test)] -mod tests { - use super::PrunerOutput; - use crate::api::WriteCounts; - use std::time::Duration; - - #[test] - fn test_pruner_output_display() { - let pruner_output = PrunerOutput { - duration: Duration::from_secs(10), - start_block: 1, - end_block: 2, - write_counts: WriteCounts::new(1, 2, 3, 4), - }; - let formatted_pruner_output = format!("{pruner_output}"); - - assert_eq!(formatted_pruner_output, "Pruned 1→2 (1 blocks), entries=10, elapsed=10.000s"); - } -} diff --git a/vendor/reth-optimism-trie/src/prune/metrics.rs b/vendor/reth-optimism-trie/src/prune/metrics.rs deleted file mode 100644 index 35457c8..0000000 --- a/vendor/reth-optimism-trie/src/prune/metrics.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::PrunerOutput; -use reth_metrics::{ - Metrics, - metrics::{Gauge, Histogram}, -}; - -#[derive(Metrics)] -#[metrics(scope = "optimism_trie.pruner")] -pub(crate) struct Metrics { - /// Pruning duration - pub(crate) total_duration_seconds: Histogram, - /// Number of pruned blocks - pub(crate) pruned_blocks: Gauge, - /// Number of account trie updates written in the prune run - pub(crate) account_trie_updates_written: Gauge, - /// Number of storage trie updates written in the prune run - pub(crate) storage_trie_updates_written: Gauge, - /// Number of hashed accounts written in the prune run - pub(crate) hashed_accounts_written: Gauge, - /// Number of hashed storages written in the prune run - pub(crate) hashed_storages_written: Gauge, -} - -impl Metrics { - pub(crate) fn record_prune_result(&self, result: PrunerOutput) { - let blocks_pruned = result.end_block - result.start_block; - if blocks_pruned > 0 { - self.total_duration_seconds.record(result.duration.as_secs_f64()); - self.pruned_blocks.set(blocks_pruned as f64); - - // Consume write counts - let wc = &result.write_counts; - self.account_trie_updates_written.set(wc.account_trie_updates_written_total as f64); - self.storage_trie_updates_written.set(wc.storage_trie_updates_written_total as f64); - self.hashed_accounts_written.set(wc.hashed_accounts_written_total as f64); - self.hashed_storages_written.set(wc.hashed_storages_written_total as f64); - } - } -} diff --git a/vendor/reth-optimism-trie/src/prune/mod.rs b/vendor/reth-optimism-trie/src/prune/mod.rs deleted file mode 100644 index 8880658..0000000 --- a/vendor/reth-optimism-trie/src/prune/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod error; -pub use error::{OpProofStoragePrunerResult, PrunerError, PrunerOutput}; - -mod pruner; -pub use pruner::OpProofStoragePruner; - -#[cfg(feature = "metrics")] -mod metrics; - -mod task; -pub use task::OpProofStoragePrunerTask; diff --git a/vendor/reth-optimism-trie/src/prune/pruner.rs b/vendor/reth-optimism-trie/src/prune/pruner.rs deleted file mode 100644 index 9ba631f..0000000 --- a/vendor/reth-optimism-trie/src/prune/pruner.rs +++ /dev/null @@ -1,683 +0,0 @@ -#[cfg(feature = "metrics")] -use crate::prune::metrics::Metrics; -use crate::{ - OpProofsProviderRO, OpProofsProviderRw, OpProofsStorageError, OpProofsStore, - prune::error::{OpProofStoragePrunerResult, PrunerError, PrunerOutput}, -}; - -use alloy_eips::{BlockNumHash, eip1898::BlockWithParent}; -use reth_provider::BlockHashReader; -use std::cmp; -use tokio::time::Instant; -use tracing::{debug, error, info, trace}; - -/// Default batch size for pruning operations. -const DEFAULT_PRUNE_BATCH_SIZE: u64 = 50; - -/// Prunes the proof storage by calling `prune_earliest_state` on the storage provider. -#[derive(Debug)] -pub struct OpProofStoragePruner { - /// Storage backend for the prune - store: S, - /// Reader to fetch block hash by block number - block_hash_reader: H, - /// Keep at least these many recent blocks - min_block_interval: u64, - /// Maximum number of blocks to prune in one database transaction - prune_batch_size: u64, - // TODO: add timeout - Maximum time for one pruner run. If `None`, no timeout. - #[doc(hidden)] - #[cfg(feature = "metrics")] - metrics: Metrics, -} - -impl OpProofStoragePruner { - /// Create a new pruner. - pub fn new(store: S, block_hash_reader: H, min_block_interval: u64) -> Self { - Self { - store, - block_hash_reader, - min_block_interval, - prune_batch_size: DEFAULT_PRUNE_BATCH_SIZE, - #[cfg(feature = "metrics")] - metrics: Metrics::default(), - } - } - - /// Set the batch size for pruning operations. The pruner will prune - /// at most this many blocks in one database transaction. - pub const fn with_batch_size(mut self, prune_batch_size: u64) -> Self { - self.prune_batch_size = prune_batch_size; - self - } -} - -impl OpProofStoragePruner -where - S: OpProofsStore, - H: BlockHashReader, -{ - fn run_inner(&self) -> OpProofStoragePrunerResult { - let provider_ro = self.store.provider_ro()?; - let Some((earliest_block, target_earliest_block, mut prune_output)) = - self.resolve_prune_range(&provider_ro)? - else { - return Ok(PrunerOutput::default()); - }; - // Drop the read-only provider before starting write transactions - drop(provider_ro); - - info!( - target: "trie::prune::pruner", - from_block = earliest_block, - to_block = target_earliest_block, - "Starting pruning proof storage", - ); - - // Prune in batches, committing each batch separately to avoid - // holding a large write transaction for the entire prune window. - let mut current_earliest_block = earliest_block; - while current_earliest_block < target_earliest_block { - let batch_end_block = - cmp::min(current_earliest_block + self.prune_batch_size, target_earliest_block); - - let provider_rw = self.store.provider_rw()?; - let batch_output = self.prune_batch_on_provider( - &provider_rw, - current_earliest_block, - batch_end_block, - )?; - provider_rw.commit()?; - - prune_output.extend_ref(batch_output); - current_earliest_block = batch_end_block; - } - - Ok(prune_output) - } - - /// Prune proof storage using the given write provider, without committing. - /// - /// This allows callers to batch pruning with other write operations (e.g., storing - /// new block updates) in a single database transaction. The caller is responsible - /// for committing the transaction. - pub fn prune_with_provider( - &self, - provider_rw: &RW, - ) -> OpProofStoragePrunerResult { - let Some((earliest_block, target_earliest_block, mut prune_output)) = - self.resolve_prune_range(provider_rw)? - else { - debug!(target: "trie::prune::pruner", "Nothing to prune in the given range"); - return Ok(PrunerOutput::default()); - }; - - info!( - target: "trie::prune::pruner", - from_block = earliest_block, - to_block = target_earliest_block, - "Starting pruning proof storage (in-tx)", - ); - - let mut current_earliest_block = earliest_block; - while current_earliest_block < target_earliest_block { - let batch_end_block = - cmp::min(current_earliest_block + self.prune_batch_size, target_earliest_block); - - let batch_output = - self.prune_batch_on_provider(provider_rw, current_earliest_block, batch_end_block)?; - - prune_output.extend_ref(batch_output); - current_earliest_block = batch_end_block; - } - - Ok(prune_output) - } - - /// Resolve the prune range from the given provider. - /// - /// Returns `None` if there is nothing to prune (storage empty or window not exceeded). - fn resolve_prune_range( - &self, - provider: &P, - ) -> Result, PrunerError> { - let window = match provider.get_proof_window() { - Ok(w) => w, - Err(OpProofsStorageError::NoBlocksFound) => { - trace!(target: "trie::prune::pruner", "Proof storage is empty"); - return Ok(None); - } - Err(err) => return Err(err.into()), - }; - let (latest_block, earliest_block) = (window.latest.number, window.earliest.number); - if latest_block.saturating_sub(earliest_block) <= self.min_block_interval { - trace!(target: "trie::prune::pruner", "Nothing to prune"); - return Ok(None); - } - let target_earliest_block = latest_block - self.min_block_interval; - let prune_output = PrunerOutput { - start_block: earliest_block, - end_block: target_earliest_block, - ..Default::default() - }; - Ok(Some((earliest_block, target_earliest_block, prune_output))) - } - - /// Execute a single prune batch on the given write provider without committing. - fn prune_batch_on_provider( - &self, - provider_rw: &RW, - start_block: u64, - end_block: u64, - ) -> Result { - let batch_start_time = Instant::now(); - - let new_earliest_block_hash = self - .block_hash_reader - .block_hash(end_block) - .inspect_err(|err| { - error!( - target: "trie::prune::pruner", - block = end_block, - ?err, - "Failed to fetch block hash for new earliest block during pruning" - ) - })? - .ok_or(PrunerError::BlockNotFound(end_block))?; - - let parent_block_num = end_block - 1; - let parent_block_hash = self - .block_hash_reader - .block_hash(parent_block_num) - .inspect_err(|err| { - error!( - target: "trie::prune::pruner", - block = parent_block_num, - ?err, - "Failed to fetch block hash for parent block during pruning" - ) - })? - .ok_or(PrunerError::BlockNotFound(parent_block_num))?; - - let block_with_parent = BlockWithParent { - parent: parent_block_hash, - block: BlockNumHash { number: end_block, hash: new_earliest_block_hash }, - }; - - let write_counts = provider_rw.prune_earliest_state(block_with_parent)?; - - let duration = batch_start_time.elapsed(); - let batch_output = PrunerOutput { duration, start_block, end_block, write_counts }; - - #[cfg(feature = "metrics")] - self.metrics.record_prune_result(batch_output.clone()); - - info!( - target: "trie::prune::pruner", - ?batch_output, - "Finished pruning batch of proof storage", - ); - Ok(batch_output) - } - - /// Run the pruner - pub fn run(&self) { - let res = self.run_inner(); - if let Err(e) = res { - error!(target: "trie::prune::pruner", err=%e, "Pruner failed"); - return; - } - info!(target: "trie::prune::pruner", result = %res.unwrap(), "Finished pruning proof storage"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{BlockStateDiff, OpProofsInitProvider, OpProofsStorage, db::MdbxProofsStorage}; - use alloy_eips::{BlockHashOrNumber, BlockNumHash, NumHash}; - use alloy_primitives::{B256, BlockNumber, U256}; - use mockall::mock; - use reth_primitives_traits::Account; - use reth_storage_errors::provider::ProviderResult; - use reth_trie::{ - BranchNodeCompact, HashedPostState, HashedStorage, Nibbles, - hashed_cursor::HashedCursor, - trie_cursor::TrieCursor, - updates::{StorageTrieUpdates, TrieUpdates, TrieUpdatesSorted}, - }; - use std::sync::Arc; - use tempfile::TempDir; - - mock! ( - #[derive(Debug)] - pub BlockHashReader {} - - impl BlockHashReader for BlockHashReader { - fn block_hash(&self, number: BlockNumber) -> ProviderResult>; - - fn convert_block_hash( - &self, - _hash_or_number: BlockHashOrNumber, - ) -> ProviderResult>; - - fn canonical_hashes_range( - &self, - _start: BlockNumber, - _end: BlockNumber, - ) -> ProviderResult>; - } - ); - - fn b256(n: u64) -> B256 { - use alloy_primitives::keccak256; - keccak256(n.to_be_bytes()) - } - - /// Build a block-with-parent for number `n` with deterministic hash. - fn block(n: u64, parent: B256) -> BlockWithParent { - BlockWithParent::new(parent, NumHash::new(n, b256(n))) - } - - #[tokio::test] - async fn run_inner_and_and_verify_updated_state() { - // --- env/store --- - let dir = TempDir::new().unwrap(); - let store = Arc::new(MdbxProofsStorage::new(dir.path()).expect("env")); - - { - let init = store.initialization_provider().expect("init"); - init.set_initial_state_anchor(BlockNumHash { number: 0, hash: B256::ZERO }) - .expect("anchor"); - init.commit_initial_state().expect("commit init"); - OpProofsInitProvider::commit(init).expect("commit"); - } - - // --- entities --- - // accounts - let a1 = B256::from([0xA1; 32]); - let a2 = B256::from([0xA2; 32]); - let a3 = B256::from([0xA3; 32]); // introduced later - - // one storage address with 3 slots - let stor_addr = B256::from([0x10; 32]); - let s1 = B256::from([0xB1; 32]); - let s2 = B256::from([0xB2; 32]); - let s3 = B256::from([0xB3; 32]); - - // account-trie paths (p1 gets removed by block 3; p2 remains; p3 added later) - let p1 = Nibbles::from_nibbles_unchecked([0x01, 0x02]); - let p2 = Nibbles::from_nibbles_unchecked([0x03, 0x04]); - let p3 = Nibbles::from_nibbles_unchecked([0x05, 0x06]); - - let node_p1 = BranchNodeCompact::new(0b1, 0, 0, vec![], Some(B256::from([0x11; 32]))); - let node_p2 = BranchNodeCompact::new(0b10, 0, 0, vec![], Some(B256::from([0x22; 32]))); - let node_p3 = BranchNodeCompact::new(0b11, 0, 0, vec![], Some(B256::from([0x33; 32]))); - - // storage-trie paths (st1 removed by block 3; st2 remains; st3 added later) - let st1 = Nibbles::from_nibbles_unchecked([0x0A]); - let st2 = Nibbles::from_nibbles_unchecked([0x0B]); - let st3 = Nibbles::from_nibbles_unchecked([0x0C]); - - let node_st2 = BranchNodeCompact::new(0b101, 0, 0, vec![], Some(B256::from([0x44; 32]))); - let node_st3 = BranchNodeCompact::new(0b110, 0, 0, vec![], Some(B256::from([0x55; 32]))); - - // --- write 5 blocks manually --- - let mut parent = B256::ZERO; - - // Block 1: add a1,a2; s1=100, s2=200; add p1, st1 - { - let b1 = block(1, parent); - - let mut d_trie_updates = TrieUpdates::default(); - let mut d_post_state = HashedPostState::default(); - - d_post_state.accounts.insert( - a1, - Some(Account { nonce: 1, balance: U256::from(1_001), ..Default::default() }), - ); - d_post_state.accounts.insert( - a2, - Some(Account { nonce: 1, balance: U256::from(1_002), ..Default::default() }), - ); - - let mut hs = HashedStorage::default(); - hs.storage.insert(s1, U256::from(100)); - hs.storage.insert(s2, U256::from(200)); - d_post_state.storages.insert(stor_addr, hs); - - d_trie_updates.account_nodes.insert(p1, node_p1); - let e = d_trie_updates.storage_tries.entry(stor_addr).or_default(); - e.storage_nodes.insert(st1, BranchNodeCompact::default()); - - let d = BlockStateDiff { - sorted_post_state: d_post_state.into_sorted(), - sorted_trie_updates: d_trie_updates.into_sorted(), - }; - let provider = store.provider_rw().expect("provider_rw"); - provider.store_trie_updates(b1, d).expect("b1"); - OpProofsProviderRw::commit(provider).expect("commit"); - parent = b256(1); - } - - // Block 2: update a2; add a3; s2=220, s3=300; add p2, st2 - { - let b2 = block(2, parent); - - let mut d_trie_updates = TrieUpdates::default(); - let mut d_post_state = HashedPostState::default(); - - d_post_state.accounts.insert( - a2, - Some(Account { nonce: 2, balance: U256::from(2_002), ..Default::default() }), - ); - d_post_state.accounts.insert( - a3, - Some(Account { nonce: 1, balance: U256::from(1_003), ..Default::default() }), - ); - - let mut hs = HashedStorage::default(); - hs.storage.insert(s2, U256::from(220)); - hs.storage.insert(s3, U256::from(300)); - d_post_state.storages.insert(stor_addr, hs); - - d_trie_updates.account_nodes.insert(p2, node_p2.clone()); - let e = d_trie_updates.storage_tries.entry(stor_addr).or_default(); - e.storage_nodes.insert(st2, node_st2.clone()); - - let d = BlockStateDiff { - sorted_post_state: d_post_state.into_sorted(), - sorted_trie_updates: d_trie_updates.into_sorted(), - }; - let provider = store.provider_rw().expect("provider_rw"); - provider.store_trie_updates(b2, d).expect("b2"); - OpProofsProviderRw::commit(provider).expect("commit"); - parent = b256(2); - } - - // Block 3: delete a1; leave a2,a3; remove p1; remove st1 (storage-trie) - { - let b3 = block(3, parent); - - let mut d_trie_updates = TrieUpdates::default(); - let mut d_post_state = HashedPostState::default(); - - // delete a1, keep a2 & a3 values unchanged for this block - d_post_state.accounts.insert(a1, None); - - // remove account trie node p1 - d_trie_updates.removed_nodes.insert(p1); - - // remove storage-trie node st1 - let mut st_upd = StorageTrieUpdates::default(); - st_upd.removed_nodes.insert(st1); - d_trie_updates.storage_tries.insert(stor_addr, st_upd); - - let d = BlockStateDiff { - sorted_post_state: d_post_state.into_sorted(), - sorted_trie_updates: d_trie_updates.into_sorted(), - }; - let provider = store.provider_rw().expect("provider_rw"); - provider.store_trie_updates(b3, d).expect("b3"); - OpProofsProviderRw::commit(provider).expect("commit"); - parent = b256(3); - } - - // Block 4 (kept): update a2; s1=140; add p3, st3 - { - let b4 = block(4, parent); - - let mut d_trie_updates = TrieUpdates::default(); - let mut d_post_state = HashedPostState::default(); - - d_post_state.accounts.insert( - a2, - Some(Account { nonce: 3, balance: U256::from(3_002), ..Default::default() }), - ); - - let mut hs = HashedStorage::default(); - hs.storage.insert(s1, U256::from(140)); - d_post_state.storages.insert(stor_addr, hs); - d_trie_updates.account_nodes.insert(p3, node_p3.clone()); - let e = d_trie_updates.storage_tries.entry(stor_addr).or_default(); - e.storage_nodes.insert(st3, node_st3.clone()); - - let d = BlockStateDiff { - sorted_post_state: d_post_state.into_sorted(), - sorted_trie_updates: d_trie_updates.into_sorted(), - }; - let provider = store.provider_rw().expect("provider_rw"); - provider.store_trie_updates(b4, d).expect("b4"); - OpProofsProviderRw::commit(provider).expect("commit"); - parent = b256(4); - } - - // Block 5 (kept): update a3; s3=330 - { - let b5 = block(5, parent); - - let mut d_post_state = HashedPostState::default(); - - d_post_state.accounts.insert( - a3, - Some(Account { nonce: 2, balance: U256::from(2_003), ..Default::default() }), - ); - - let mut hs = HashedStorage::default(); - hs.storage.insert(s3, U256::from(330)); - d_post_state.storages.insert(stor_addr, hs); - - let d = BlockStateDiff { - sorted_post_state: d_post_state.into_sorted(), - sorted_trie_updates: TrieUpdatesSorted::default(), - }; - let provider = store.provider_rw().expect("provider_rw"); - provider.store_trie_updates(b5, d).expect("b5"); - OpProofsProviderRw::commit(provider).expect("commit"); - } - - // sanity: earliest=0, latest=5 - { - let provider = store.provider_ro().expect("provider_ro"); - let e = provider.get_earliest_block().expect("earliest"); - let l = provider.get_latest_block().expect("latest"); - assert_eq!(e.number, 0); - assert_eq!(l.number, 5); - } - - // --- prune: remove the first 3 blocks, keep 4 and 5 - // new_earliest = 5-1 = 4 - let mut block_hash_reader = MockBlockHashReader::new(); - block_hash_reader - .expect_block_hash() - .withf(move |block_num| *block_num == 4) - .returning(move |_| Ok(Some(b256(4)))); - - block_hash_reader - .expect_block_hash() - .withf(move |block_num| *block_num == 3) - .returning(move |_| Ok(Some(b256(3)))); - - let pruner = OpProofStoragePruner::new(store.clone(), block_hash_reader, 1); - let out = pruner.run_inner().expect("pruner ok"); - assert_eq!(out.start_block, 0); - assert_eq!(out.end_block, 4, "pruned up to 4 (inclusive); new earliest is 4"); - - // proof window moved: earliest=4, latest=5 - { - let provider = store.provider_ro().expect("provider_ro"); - let e = provider.get_earliest_block().expect("earliest"); - let l = provider.get_latest_block().expect("latest"); - assert_eq!(e, NumHash::new(4, b256(4))); - assert_eq!(l, NumHash::new(5, b256(5))); - } - - // --- DB checks - let provider = store.provider_ro().expect("provider_ro"); - let mut acc_cur = provider.account_hashed_cursor(4).expect("acc cur"); - let mut stor_cur = provider.storage_hashed_cursor(stor_addr, 4).expect("stor cur"); - let mut acc_trie_cur = provider.account_trie_cursor(4).expect("acc trie cur"); - let mut stor_trie_cur = provider.storage_trie_cursor(stor_addr, 4).expect("stor trie cur"); - - // Check these histories have been removed - let pruned_hashed_account = a1; - let pruned_trie_accounts = p1; - let pruned_trie_storage = st1; - - assert_ne!( - acc_cur.seek(pruned_hashed_account).expect("seek").unwrap().0, - pruned_hashed_account, - "deleted account must not exist in earliest snapshot" - ); - assert_ne!( - acc_trie_cur.seek(pruned_trie_accounts).expect("seek").unwrap().0, - pruned_trie_accounts, - "deleted account trie must not exist in earliest snapshot" - ); - assert_ne!( - stor_trie_cur.seek(pruned_trie_storage).expect("seek").unwrap().0, - pruned_trie_storage, - "deleted storage trie must not exist in earliest snapshot" - ); - - // Check these histories have been updated - till block 4 - let updated_hashed_accounts = vec![ - (a2, Account { nonce: 3, balance: U256::from(3_002), ..Default::default() }), /* block 4 */ - (a3, Account { nonce: 1, balance: U256::from(1_003), ..Default::default() }), /* block 2 */ - ]; - let updated_hashed_storage = vec![ - (s1, U256::from(140)), // block 4 - (s2, U256::from(220)), // block 2 - (s3, U256::from(300)), // block 2 - ]; - let updated_trie_accounts = vec![ - (p2, node_p2), // block 2 - (p3, node_p3), // block 4 - ]; - let updated_trie_storage = vec![ - (st2, node_st2), // block 2 - (st3, node_st3), // block 4 - ]; - - for (key, val) in updated_hashed_accounts { - let (k, vv) = acc_cur.seek(key).expect("seek").unwrap(); - assert_eq!(key, k, "key must exist"); - assert_eq!(val, vv, "value must be updated"); - } - - for (key, val) in updated_hashed_storage { - let (k, vv) = stor_cur.seek(key).expect("seek").unwrap(); - assert_eq!(key, k, "key must exist"); - assert_eq!(val, vv, "value must be updated"); - } - - for (key, val) in updated_trie_accounts { - let (k, vv) = acc_trie_cur.seek(key).expect("seek").unwrap(); - assert_eq!(key, k, "key must exist"); - assert_eq!(val, vv, "value must be updated"); - } - for (key, val) in updated_trie_storage { - let (k, vv) = stor_trie_cur.seek(key).expect("seek").unwrap(); - assert_eq!(key, k, "key must exist"); - assert_eq!(val, vv, "value must be updated"); - } - } - - // Both latest and earliest blocks are None -> early return default; DB untouched. - #[tokio::test] - async fn run_inner_where_latest_block_is_none() { - let dir = TempDir::new().unwrap(); - let store: OpProofsStorage> = - OpProofsStorage::from(Arc::new(MdbxProofsStorage::new(dir.path()).expect("env"))); - - { - let provider = store.provider_ro().unwrap(); - let earliest = provider.get_earliest_block(); - let latest = provider.get_latest_block(); - println!("{:?} {:?}", earliest, latest); - assert!(matches!(earliest, Err(OpProofsStorageError::NoBlocksFound))); - assert!(matches!(latest, Err(OpProofsStorageError::NoBlocksFound))); - } - - let block_hash_reader = MockBlockHashReader::new(); - let pruner = OpProofStoragePruner::new(store, block_hash_reader, 10); - let out = pruner.run_inner().expect("ok"); - assert_eq!(out, PrunerOutput::default(), "should early-return default output"); - } - - // When only earliest is set (and latest is absent), latest falls back to earliest. - // This yields interval=0, so pruning should no-op. - #[tokio::test] - async fn run_inner_earliest_none_real_db() { - let dir = TempDir::new().unwrap(); - let store: OpProofsStorage> = - OpProofsStorage::from(Arc::new(MdbxProofsStorage::new(dir.path()).expect("env"))); - - // Bootstrap the chain anchor via the init flow; commit_initial_state sets both - // earliest and latest. - { - let init = store.initialization_provider().expect("init"); - init.set_initial_state_anchor(BlockNumHash { number: 3, hash: b256(3) }) - .expect("anchor"); - init.commit_initial_state().expect("commit init"); - OpProofsInitProvider::commit(init).expect("commit"); - } - - { - let provider = store.provider_ro().unwrap(); - let earliest = provider.get_earliest_block().unwrap(); - let latest = provider.get_latest_block().unwrap(); - assert_eq!(earliest.number, 3); - assert_eq!(latest.number, 3, "commit_initial_state bootstraps both anchors"); - } - - let block_hash_reader = MockBlockHashReader::new(); - let pruner = OpProofStoragePruner::new(store, block_hash_reader, 1); - let out = pruner.run_inner().expect("ok"); - assert_eq!(out, PrunerOutput::default(), "should early-return default output"); - } - - // interval < min_block_interval -> "Nothing to prune" path; default output. - #[tokio::test] - async fn run_inner_interval_too_small_real_db() { - use crate::BlockStateDiff; - - let dir = TempDir::new().unwrap(); - let store: OpProofsStorage> = - OpProofsStorage::from(Arc::new(MdbxProofsStorage::new(dir.path()).expect("env"))); - - // Bootstrap earliest=4 via the init flow. - let earliest_num = 4u64; - let h4 = b256(4); - { - let init = store.initialization_provider().expect("init"); - init.set_initial_state_anchor(BlockNumHash { number: earliest_num, hash: h4 }) - .expect("anchor"); - init.commit_initial_state().expect("commit init"); - OpProofsInitProvider::commit(init).expect("commit"); - } - - // Set latest=5 by storing block 5. - { - let provider = store.provider_rw().expect("provider_rw"); - let b5 = block(5, h4); - provider.store_trie_updates(b5, BlockStateDiff::default()).expect("store b5"); - OpProofsProviderRw::commit(provider).expect("commit"); - } - - // Sanity: earliest=4, latest=5 => interval=1 - { - let provider = store.provider_ro().unwrap(); - let e = provider.get_earliest_block().unwrap(); - let l = provider.get_latest_block().unwrap(); - assert_eq!(e.number, 4); - assert_eq!(l.number, 5); - } - - // Require min_block_interval=2 (or greater) so interval < min - let block_hash_reader = MockBlockHashReader::new(); - let pruner = OpProofStoragePruner::new(store, block_hash_reader, 2); - let out = pruner.run_inner().expect("ok"); - assert_eq!(out, PrunerOutput::default(), "no pruning should occur"); - } -} diff --git a/vendor/reth-optimism-trie/src/prune/task.rs b/vendor/reth-optimism-trie/src/prune/task.rs deleted file mode 100644 index 65df878..0000000 --- a/vendor/reth-optimism-trie/src/prune/task.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::{OpProofsStore, prune::OpProofStoragePruner}; -use reth_provider::BlockHashReader; -use reth_tasks::shutdown::GracefulShutdown; -use tokio::{ - time, - time::{Duration, MissedTickBehavior}, -}; -use tracing::info; - -/// Periodic pruner task: constructs the pruner and runs it every interval. -#[derive(Debug)] -pub struct OpProofStoragePrunerTask { - pruner: OpProofStoragePruner, - min_block_interval: u64, - task_run_interval: Duration, -} - -impl OpProofStoragePrunerTask -where - S: OpProofsStore, - H: BlockHashReader, -{ - /// Initialize a new [`OpProofStoragePrunerTask`] - pub fn new( - store: S, - hash_reader: H, - min_block_interval: u64, - task_run_interval: Duration, - ) -> Self { - let pruner = OpProofStoragePruner::new(store, hash_reader, min_block_interval); - Self { pruner, min_block_interval, task_run_interval } - } - - /// Run forever (until `cancel`), executing one prune pass per `task_run_interval`. - pub async fn run(self, mut signal: GracefulShutdown) { - info!( - target: "trie::prune::task", - min_block_interval = self.min_block_interval, - interval_secs = self.task_run_interval.as_secs(), - "Starting pruner task" - ); - - // Drive pruning with a periodic ticker - let mut interval = time::interval(self.task_run_interval); - interval.set_missed_tick_behavior(MissedTickBehavior::Delay); - - loop { - tokio::select! { - _ = &mut signal => { - info!(target: "trie::prune::task", "Pruner task cancelled; exiting"); - break; - } - _ = interval.tick() => { - self.pruner.run() - } - } - } - - info!(target: "trie::prune::task", "Pruner task stopped"); - } -} diff --git a/vendor/reth-optimism-trie/src/snapshot/error.rs b/vendor/reth-optimism-trie/src/snapshot/error.rs deleted file mode 100644 index fabbbbb..0000000 --- a/vendor/reth-optimism-trie/src/snapshot/error.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Error type for snapshot-init operations. - -use crate::{OpProofsStorageError, api::SnapshotInitStatus}; -use alloy_primitives::B256; -use reth_db::DatabaseError; -use reth_execution_errors::StateRootError; -use reth_provider::ProviderError; - -/// Error type for [`super::SnapshotInitJob`]. -#[derive(Debug, thiserror::Error)] -pub enum SnapshotError { - /// Error bubbled up from proofs storage operations. - #[error(transparent)] - Storage(#[from] OpProofsStorageError), - /// Error from reth provider operations. - #[error(transparent)] - Provider(#[from] ProviderError), - /// State root computation failed. - #[error(transparent)] - StateRoot(#[from] StateRootError), - /// Computed state root does not match the expected root from the header. - #[error( - "State root mismatch at block {block_number}: computed {computed:?}, expected {expected:?}" - )] - StateRootMismatch { - /// Block whose state root was validated. - block_number: u64, - /// Computed root from the snapshot tables + live hashed leaves. - computed: B256, - /// Expected root from reth's block header. - expected: B256, - }, - /// Snapshot init target block is outside the proofs window. - #[error( - "snapshot init target block {target_block} is outside proof window [{earliest}, {latest}]" - )] - SnapshotInitTargetOutsideWindow { - /// The block requested as the snapshot anchor. - target_block: u64, - /// Current earliest persisted block. - earliest: u64, - /// Current latest persisted block. - latest: u64, - }, - /// Resume requested but the existing `Building` anchor doesn't match. - #[error("snapshot resume drift detected at anchor block {anchor_block}: {reason}")] - SnapshotResumeDriftDetected { - /// Anchor block of the partial snapshot already on disk. - anchor_block: u64, - /// Human-readable reason describing the drift. - reason: &'static str, - }, - /// A snapshot already exists at a different anchor; the caller must drop - /// it before requesting a new build. - #[error( - "snapshot already exists at block {existing_block} (status {existing_status:?}); drop it before rebuilding" - )] - SnapshotAlreadyExists { - /// Anchor block of the existing snapshot. - existing_block: u64, - /// Lifecycle status of the existing snapshot. - existing_status: SnapshotInitStatus, - }, -} - -impl From for SnapshotError { - fn from(err: DatabaseError) -> Self { - Self::Storage(OpProofsStorageError::from(err)) - } -} diff --git a/vendor/reth-optimism-trie/src/snapshot/job.rs b/vendor/reth-optimism-trie/src/snapshot/job.rs deleted file mode 100644 index 8c2e679..0000000 --- a/vendor/reth-optimism-trie/src/snapshot/job.rs +++ /dev/null @@ -1,675 +0,0 @@ -//! [`SnapshotInitJob`] — chunked builder for the one-time trie-state snapshot. - -use super::SnapshotError; -use crate::{ - OpProofsProviderRO, OpProofsSnapshotInitProvider, SnapshotHashedCursorFactory, - SnapshotInitAnchor, SnapshotInitStatus, SnapshotTrieCursorFactory, - db::{HashedStorageKey, StorageTrieKey}, - initialize::CompletionEstimatable, -}; -use alloy_eips::BlockNumHash; -use alloy_primitives::{B256, BlockNumber, U256}; -use reth_primitives_traits::{Account, AlloyBlockHeader}; -use reth_provider::{BlockHashReader, HeaderProvider, ProviderError}; -use reth_trie::{ - BranchNodeCompact, HashedPostState, Nibbles, StateRoot, StoredNibbles, - hashed_cursor::{HashedCursor, HashedPostStateCursorFactory}, - trie_cursor::TrieCursor, -}; -use std::time::Instant; -use tracing::info; - -/// Default rows copied per chunked init transaction. Used by -/// [`SnapshotInitJob::new`]; callers can override via -/// [`SnapshotInitJob::with_chunk_size`]. -const SNAPSHOT_INIT_CHUNK_SIZE: usize = 50_000; - -/// Storage-trie chunk grouped by hashed address. Each inner vec is the -/// per-address payload accepted by -/// [`OpProofsSnapshotInitProvider::store_storage_trie_snapshot_branches`]. -type StorageChunk = Vec<(B256, Vec<(Nibbles, Option)>)>; - -/// Hashed-storage chunk grouped by hashed address. Each inner vec is the -/// per-address payload accepted by -/// [`OpProofsSnapshotInitProvider::store_hashed_storages_snapshot`]. -type HashedStorageChunk = Vec<(B256, Vec<(B256, U256)>)>; - -/// Output of a successful [`SnapshotInitJob::run`] call. -/// -/// Shape mirrors [`crate::api::SnapshotInitAnchor`]: the anchor `block` plus -/// the lifecycle `status` (always [`SnapshotInitStatus::Completed`] on success). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SnapshotInitOutcome { - /// Block the snapshot's trie state corresponds to. - pub block: BlockNumHash, - /// Lifecycle status — always [`SnapshotInitStatus::Completed`] for a - /// successful run. - pub status: SnapshotInitStatus, - /// Number of account trie nodes copied during this run (does **not** - /// include rows already present from a prior resumable run). - pub account_nodes_copied: u64, - /// Number of storage trie nodes copied during this run. - pub storage_nodes_copied: u64, - /// Number of hashed account leaves copied during this run. - pub hashed_accounts_copied: u64, - /// Number of hashed storage leaves copied during this run. - pub hashed_storages_copied: u64, -} - -/// Builds the one-time trie-state snapshot. -#[derive(Debug)] -pub struct SnapshotInitJob { - /// Reth DB provider (used to look up the target block's hash + header). - provider: P, - /// Op-reth proofs storage that owns the snapshot tables. - storage: S, - /// Rows committed per chunked rw-tx during the drain phases. Larger - /// values trade peak memory for fewer commits. - chunk_size: usize, -} - -impl SnapshotInitJob { - /// Build a job with the default chunk size. - pub const fn new(provider: P, storage: S) -> Self { - Self { provider, storage, chunk_size: SNAPSHOT_INIT_CHUNK_SIZE } - } - - /// Override the per-tx chunk size. Useful for operators tuning memory - /// usage on small/large databases, or for tests that want to drive the - /// chunk loop in a few iterations. - pub const fn with_chunk_size(mut self, chunk_size: usize) -> Self { - self.chunk_size = chunk_size; - self - } -} - -impl SnapshotInitJob -where - P: HeaderProvider + BlockHashReader + Send, - S: crate::OpProofsBackfillStore + Send, -{ - /// Build a snapshot at `target_block`, validating against the reth header. - /// - /// `target_block` must fall inside the proofs window's `[earliest, latest]`. - /// Auto-resumes a partial `Building` snapshot if the existing anchor - /// matches; refuses to run if a `Ready` snapshot exists at a different - /// anchor (the caller must drop it first). - pub fn run(&self, target_block: BlockNumber) -> Result { - let start = Instant::now(); - - let target = self.prepare_anchor(target_block)?; - // Read the init anchor once: lifecycle classification uses status/block, - // and the drain phases use the destination-table resume keys. - let init_anchor = - self.storage.snapshot_initialization_provider()?.snapshot_init_anchor()?; - self.start_or_resume(target, &init_anchor)?; - - let expected_root = self.expected_state_root(target_block)?; - - let copy_start = Instant::now(); - let account_nodes_copied = - self.drain_account_trie(target_block, init_anchor.last_account_trie_key)?; - let storage_nodes_copied = - self.drain_storage_trie(target_block, init_anchor.last_storage_trie_key)?; - let hashed_accounts_copied = - self.drain_hashed_accounts(target_block, init_anchor.last_hashed_account_key)?; - let hashed_storages_copied = - self.drain_hashed_storages(target_block, init_anchor.last_hashed_storage_key)?; - let copy_elapsed = copy_start.elapsed(); - - let validate_start = Instant::now(); - self.validate_state_root(target_block, expected_root)?; - let validate_elapsed = validate_start.elapsed(); - - self.finalize_ready()?; - - info!( - target: "reth::op-proofs::snapshot-init", - block = target_block, - account_nodes_copied, - storage_nodes_copied, - hashed_accounts_copied, - hashed_storages_copied, - copy_elapsed = ?copy_elapsed, - validate_elapsed = ?validate_elapsed, - total_elapsed = ?start.elapsed(), - "Snapshot init complete" - ); - - Ok(SnapshotInitOutcome { - block: target, - status: SnapshotInitStatus::Completed, - account_nodes_copied, - storage_nodes_copied, - hashed_accounts_copied, - hashed_storages_copied, - }) - } - - /// Validate `target_block` is inside the proofs window and resolve its hash. - fn prepare_anchor(&self, target_block: BlockNumber) -> Result { - let ro = self.storage.provider_ro()?; - let window = ro.get_proof_window()?; - if target_block < window.earliest.number || target_block > window.latest.number { - return Err(SnapshotError::SnapshotInitTargetOutsideWindow { - target_block, - earliest: window.earliest.number, - latest: window.latest.number, - }); - } - - let target_hash = self - .provider - .block_hash(target_block)? - .ok_or_else(|| ProviderError::HeaderNotFound(target_block.into()))?; - Ok(BlockNumHash::new(target_block, target_hash)) - } - - /// Decide whether this run starts fresh or resumes an in-flight build, - /// and plant a new `Building` row when fresh. - /// - /// Errors on drift (`InProgress` at a different target) or refusal - /// (`Completed` snapshot already exists). - fn start_or_resume( - &self, - target: BlockNumHash, - init_anchor: &SnapshotInitAnchor, - ) -> Result<(), SnapshotError> { - // Invariant: `InProgress`/`Completed` always carry an anchor block — - // `set_snapshot_init_anchor` plants status + block atomically. - let resume = match init_anchor.status { - SnapshotInitStatus::NotStarted => false, - SnapshotInitStatus::InProgress => { - let b = init_anchor.block.expect("InProgress implies anchor planted"); - if b == target { - true - } else { - return Err(SnapshotError::SnapshotResumeDriftDetected { - anchor_block: b.number, - reason: "snapshot init target does not match the in-progress snapshot anchor", - }); - } - } - SnapshotInitStatus::Completed => { - let b = init_anchor.block.expect("Completed implies anchor planted"); - return Err(SnapshotError::SnapshotAlreadyExists { - existing_block: b.number, - existing_status: SnapshotInitStatus::Completed, - }); - } - }; - - if !resume { - let sp = self.storage.snapshot_initialization_provider()?; - sp.set_snapshot_init_anchor(target)?; - OpProofsSnapshotInitProvider::commit(sp)?; - } - info!( - target: "reth::op-proofs::snapshot-init", - block = target.number, - resume, - "Starting snapshot init" - ); - Ok(()) - } - - /// Look up the expected state root for `target_block` from reth's headers. - fn expected_state_root(&self, target_block: BlockNumber) -> Result { - Ok(self - .provider - .header_by_number(target_block)? - .ok_or_else(|| ProviderError::HeaderNotFound(target_block.into()))? - .state_root()) - } - - /// Drain the history-aware account trie cursor at `target_block` into - /// `V2AccountsTrieSnapshot`, one chunk per rw-tx. Resumes past whatever's - /// currently in the snapshot. - /// - /// Returns the number of rows copied during *this* call (excluding rows - /// already present from prior runs). - fn drain_account_trie( - &self, - target_block: BlockNumber, - mut resume_after: Option, - ) -> Result { - let phase_start = Instant::now(); - let mut initial_progress: Option = None; - let mut copied = 0u64; - - // `resume_after` is the destination table's last key at run start - // (read once in `run`); thereafter we advance it locally as we - // commit each chunk. - loop { - let chunk = { - let ro = self.storage.provider_ro()?; - let mut cursor = ro.account_trie_cursor(target_block)?; - collect_account_chunk(&mut cursor, resume_after.clone(), self.chunk_size)? - }; - if chunk.is_empty() { - break; - } - let n = chunk.len() as u64; - let last_key = chunk.last().expect("non-empty").0.clone(); - let sp = self.storage.snapshot_initialization_provider()?; - sp.store_account_trie_snapshot_branches(chunk)?; - OpProofsSnapshotInitProvider::commit(sp)?; - copied += n; - log_phase_progress("accounts", &last_key, &mut initial_progress, phase_start, copied); - resume_after = Some(last_key); - } - Ok(copied) - } - - /// Walk hashed accounts at `target_block`, drain each account's historical - /// storage trie cursor into `V2StoragesTrieSnapshot`, one chunk per rw-tx. - /// - /// Resume tracks the last `StorageTrieKey` written. - fn drain_storage_trie( - &self, - target_block: BlockNumber, - mut resume_after: Option, - ) -> Result { - let phase_start = Instant::now(); - let mut initial_progress: Option = None; - let mut copied = 0u64; - - // `resume_after` is the destination table's last key at run start - // (read once in `run`); thereafter we advance it locally as we - // commit each chunk. - loop { - let chunk = { - let ro = self.storage.provider_ro()?; - collect_storage_chunk(&ro, target_block, resume_after.clone(), self.chunk_size)? - }; - if chunk.is_empty() { - break; - } - let n: u64 = chunk.iter().map(|(_, nodes)| nodes.len() as u64).sum(); - // The chunk's last storage-trie key is the last (addr, path) pair we - // just committed. Storage progress tracks the hashed address, so - // this is what feeds `estimate_progress`. - let (last_addr, last_path_group) = chunk.last().expect("non-empty"); - let last_key = StorageTrieKey::new( - *last_addr, - StoredNibbles(last_path_group.last().expect("non-empty group").0), - ); - let sp = self.storage.snapshot_initialization_provider()?; - for (addr, nodes) in chunk { - sp.store_storage_trie_snapshot_branches(addr, nodes)?; - } - OpProofsSnapshotInitProvider::commit(sp)?; - copied += n; - log_phase_progress("storages", &last_key, &mut initial_progress, phase_start, copied); - resume_after = Some(last_key); - } - Ok(copied) - } - - /// Drain the history-aware hashed-account cursor at `target_block` into - /// [`crate::db::V2HashedAccountsSnapshot`], one chunk per rw-tx. - fn drain_hashed_accounts( - &self, - target_block: BlockNumber, - mut resume_after: Option, - ) -> Result { - let phase_start = Instant::now(); - let mut initial_progress: Option = None; - let mut copied = 0u64; - - loop { - let chunk = { - let ro = self.storage.provider_ro()?; - let mut cursor = ro.account_hashed_cursor(target_block)?; - collect_hashed_account_chunk(&mut cursor, resume_after, SNAPSHOT_INIT_CHUNK_SIZE)? - }; - if chunk.is_empty() { - break; - } - let n = chunk.len() as u64; - let last_key = chunk.last().expect("non-empty").0; - let sp = self.storage.snapshot_initialization_provider()?; - sp.store_hashed_accounts_snapshot(chunk)?; - OpProofsSnapshotInitProvider::commit(sp)?; - copied += n; - log_phase_progress( - "hashed_accounts", - &last_key, - &mut initial_progress, - phase_start, - copied, - ); - resume_after = Some(last_key); - } - Ok(copied) - } - - /// Drain hashed storage leaves at `target_block` into - /// [`crate::db::V2HashedStoragesSnapshot`], one chunk per rw-tx. Walks - /// accounts via the history-aware account cursor; for each account, drains - /// its history-aware storage cursor. - fn drain_hashed_storages( - &self, - target_block: BlockNumber, - mut resume_after: Option, - ) -> Result { - let phase_start = Instant::now(); - let mut initial_progress: Option = None; - let mut copied = 0u64; - - loop { - let chunk = { - let ro = self.storage.provider_ro()?; - collect_hashed_storage_chunk( - &ro, - target_block, - resume_after.clone(), - SNAPSHOT_INIT_CHUNK_SIZE, - )? - }; - if chunk.is_empty() { - break; - } - let n: u64 = chunk.iter().map(|(_, entries)| entries.len() as u64).sum(); - let (last_addr, last_group) = chunk.last().expect("non-empty"); - let last_key = - HashedStorageKey::new(*last_addr, last_group.last().expect("non-empty group").0); - let sp = self.storage.snapshot_initialization_provider()?; - for (addr, entries) in chunk { - sp.store_hashed_storages_snapshot(addr, entries)?; - } - OpProofsSnapshotInitProvider::commit(sp)?; - copied += n; - log_phase_progress( - "hashed_storages", - &last_key, - &mut initial_progress, - phase_start, - copied, - ); - resume_after = Some(last_key); - } - Ok(copied) - } - - /// Compute the state root from the snapshot tables and the live hashed - /// leaves and compare against `expected_root`. - /// - /// On mismatch the meta is **not** advanced — it stays at `Building` so - /// a re-run can diagnose / resume / `snapshot-drop`. - fn validate_state_root( - &self, - target_block: BlockNumber, - expected_root: B256, - ) -> Result<(), SnapshotError> { - let sp = self.storage.snapshot_provider_ro()?; - let state_sorted = HashedPostState::default().into_sorted(); - let computed_root = StateRoot::new( - SnapshotTrieCursorFactory::new(sp.clone()), - HashedPostStateCursorFactory::new( - SnapshotHashedCursorFactory::new(sp.clone()), - &state_sorted, - ), - ) - .root()?; - - if computed_root != expected_root { - return Err(SnapshotError::StateRootMismatch { - block_number: target_block, - computed: computed_root, - expected: expected_root, - }); - } - Ok(()) - } - - /// Flip status to `Ready` and commit in a final rw-tx. - fn finalize_ready(&self) -> Result<(), SnapshotError> { - let sp = self.storage.snapshot_initialization_provider()?; - sp.commit_snapshot()?; - OpProofsSnapshotInitProvider::commit(sp)?; - Ok(()) - } -} - -/// Drain up to `max_entries` rows from a `TrieCursor` strictly after `resume_after`. -fn collect_account_chunk( - cursor: &mut C, - resume_after: Option, - max_entries: usize, -) -> Result, SnapshotError> { - if max_entries == 0 { - return Ok(Vec::new()); - } - let mut next = match resume_after { - None => cursor.seek(Nibbles::default())?, - Some(after) => { - // `seek` returns the first key >= `after`. If it matches exactly, - // skip past it; otherwise we're already past. - match cursor.seek(after.0)? { - Some((k, _)) if k == after.0 => cursor.next()?, - other => other, - } - } - }; - let mut out = Vec::with_capacity(max_entries); - while let Some((k, v)) = next { - if out.len() >= max_entries { - break; - } - out.push((StoredNibbles(k), v)); - next = cursor.next()?; - } - Ok(out) -} - -/// Collect a chunk of storage-trie entries grouped by hashed address. -/// -/// Walks hashed accounts at `target_block` and drains each account's storage -/// trie cursor up to `max_entries` total nodes (counted across all groups), -/// returning `Vec<(address, nodes_for_that_address)>`. Each inner vector is -/// the per-address payload accepted by -/// [`OpProofsSnapshotInitProvider::store_storage_trie_snapshot_branches`]. -/// -/// Resume semantics: `resume_after` is the last [`StorageTrieKey`] already -/// written to the snapshot. We seek the account cursor to its address, -/// position that account's storage cursor past its path, drain, then advance -/// to the next account. -fn collect_storage_chunk

( - proofs_ro: &P, - target_block: BlockNumber, - resume_after: Option, - max_entries: usize, -) -> Result -where - P: OpProofsProviderRO, -{ - if max_entries == 0 { - return Ok(Vec::new()); - } - let mut out: StorageChunk = Vec::new(); - let mut total = 0usize; - - let (start_addr, mut path_resume) = match resume_after { - None => (B256::ZERO, None), - Some(k) => (k.hashed_address, Some(k.path)), - }; - - let mut acc_cursor = proofs_ro.account_hashed_cursor(target_block)?; - let mut next_account = acc_cursor.seek(start_addr)?; - - while let Some((addr, _account)) = next_account { - let mut stor_cursor = proofs_ro.storage_trie_cursor(addr, target_block)?; - - // Position past any pending path resume (only applies to the first - // account on this call — subsequent accounts always start at the - // beginning of their trie). - let mut next_stor = match path_resume.take() { - None => stor_cursor.seek(Nibbles::default())?, - Some(p) => match stor_cursor.seek(p.0)? { - Some((k, _)) if k == p.0 => stor_cursor.next()?, - other => other, - }, - }; - - let mut group: Vec<(Nibbles, Option)> = Vec::new(); - while let Some((path, node)) = next_stor { - if total >= max_entries { - if !group.is_empty() { - out.push((addr, group)); - } - return Ok(out); - } - group.push((path, Some(node))); - total += 1; - next_stor = stor_cursor.next()?; - } - if !group.is_empty() { - out.push((addr, group)); - } - - next_account = acc_cursor.next()?; - } - - Ok(out) -} - -/// Drain up to `max_entries` rows from a [`HashedCursor`] -/// strictly after `resume_after`. -fn collect_hashed_account_chunk( - cursor: &mut C, - resume_after: Option, - max_entries: usize, -) -> Result, SnapshotError> -where - C: HashedCursor, -{ - if max_entries == 0 { - return Ok(Vec::new()); - } - let mut next = match resume_after { - None => cursor.seek(B256::ZERO)?, - Some(after) => match cursor.seek(after)? { - Some((k, _)) if k == after => cursor.next()?, - other => other, - }, - }; - let mut out = Vec::with_capacity(max_entries); - while let Some((k, v)) = next { - if out.len() >= max_entries { - break; - } - out.push((k, v)); - next = cursor.next()?; - } - Ok(out) -} - -/// Collect a chunk of hashed-storage entries grouped by hashed address. -/// -/// Mirrors [`collect_storage_chunk`] for leaves: walks hashed accounts at -/// `target_block` and drains each account's storage cursor up to -/// `max_entries` total entries. The returned vec carries the per-address -/// payload accepted by -/// [`OpProofsSnapshotInitProvider::store_hashed_storages_snapshot`]. -fn collect_hashed_storage_chunk

( - proofs_ro: &P, - target_block: BlockNumber, - resume_after: Option, - max_entries: usize, -) -> Result -where - P: OpProofsProviderRO, -{ - if max_entries == 0 { - return Ok(Vec::new()); - } - let mut out: HashedStorageChunk = Vec::new(); - let mut total = 0usize; - - let (start_addr, mut subkey_resume) = - resume_after.map_or((B256::ZERO, None), |k| (k.hashed_address, Some(k.hashed_storage_key))); - - let mut acc_cursor = proofs_ro.account_hashed_cursor(target_block)?; - let mut next_account = acc_cursor.seek(start_addr)?; - - while let Some((addr, _account)) = next_account { - let mut stor_cursor = proofs_ro.storage_hashed_cursor(addr, target_block)?; - - // Position past any pending subkey resume (only applies to the first - // account on this call). - let mut next_stor = match subkey_resume.take() { - None => stor_cursor.seek(B256::ZERO)?, - Some(p) => match stor_cursor.seek(p)? { - Some((k, _)) if k == p => stor_cursor.next()?, - other => other, - }, - }; - - let mut group: Vec<(B256, U256)> = Vec::new(); - while let Some((slot, value)) = next_stor { - if total >= max_entries { - if !group.is_empty() { - out.push((addr, group)); - } - return Ok(out); - } - group.push((slot, value)); - total += 1; - next_stor = stor_cursor.next()?; - } - if !group.is_empty() { - out.push((addr, group)); - } - - next_account = acc_cursor.next()?; - } - - Ok(out) -} - -impl CompletionEstimatable for StorageTrieKey { - /// Address dominates ordering, so progress along the storage-trie scan - /// tracks the hashed address. - fn estimate_progress(&self) -> f64 { - self.hashed_address.estimate_progress() - } -} - -impl CompletionEstimatable for HashedStorageKey { - /// Address dominates ordering, so progress along the hashed-storage scan - /// tracks the hashed address. - fn estimate_progress(&self) -> f64 { - self.hashed_address.estimate_progress() - } -} - -/// Emit an `info!` line with chunk count, cumulative count, progress %, and ETA. -/// -/// Modeled on the loop in [`crate::initialize::InitializationJob`]: `last_key`'s -/// position in keyspace gives a 0–1 progress fraction, and we extrapolate ETA -/// from how far we moved since the phase started. `initial_progress` is -/// captured on the first call so the rate excludes resume offsets. -fn log_phase_progress( - phase: &'static str, - last_key: &K, - initial_progress: &mut Option, - phase_start: Instant, - cumulative: u64, -) { - let progress = last_key.estimate_progress(); - let initial = *initial_progress.get_or_insert(progress); - let elapsed_secs = phase_start.elapsed().as_secs_f64(); - - let rate = if elapsed_secs.is_normal() { (progress - initial) / elapsed_secs } else { 0.0 }; - let eta_secs = if rate.is_normal() && rate > 0.0 { (1.0 - progress) / rate } else { 0.0 }; - - info!( - target: "reth::op-proofs::snapshot-init", - phase, - cumulative, - progress_pct = format_args!("{:.2}", progress * 100.0), - eta_secs = format_args!("{eta_secs:.0}"), - "Snapshot init progress" - ); -} diff --git a/vendor/reth-optimism-trie/src/snapshot/mod.rs b/vendor/reth-optimism-trie/src/snapshot/mod.rs deleted file mode 100644 index 89b0cfc..0000000 --- a/vendor/reth-optimism-trie/src/snapshot/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! One-time trie-state snapshot at a caller-supplied target block. -//! -//! Copies the trie state at the target block into the parallel -//! `V2*TrieSnapshot` tables and marks the meta row `Ready` once the snapshot's -//! computed state root matches reth's header. -//! -//! The driver is [`SnapshotInitJob`]; failures surface as [`SnapshotError`]. -//! -//! ## Restart / resume -//! -//! Each chunked rw-tx commits independently; after a crash the meta stays at -//! [`SnapshotStatus::Building`] with the original anchor. A re-run inspects -//! [`OpProofsSnapshotInitProvider::snapshot_init_anchor`], discovers the -//! resume keys from the partially-populated destination tables, and continues -//! from there. Resume is only safe when the target block matches the existing -//! anchor — otherwise the init aborts with -//! [`SnapshotError::SnapshotResumeDriftDetected`]. -//! -//! [`SnapshotStatus::Building`]: crate::db::SnapshotStatus::Building -//! [`OpProofsSnapshotInitProvider::snapshot_init_anchor`]: crate::OpProofsSnapshotInitProvider::snapshot_init_anchor - -mod error; -mod job; - -pub use error::SnapshotError; -pub use job::{SnapshotInitJob, SnapshotInitOutcome}; - -#[cfg(test)] -mod tests; diff --git a/vendor/reth-optimism-trie/src/snapshot/tests.rs b/vendor/reth-optimism-trie/src/snapshot/tests.rs deleted file mode 100644 index 293dc11..0000000 --- a/vendor/reth-optimism-trie/src/snapshot/tests.rs +++ /dev/null @@ -1,487 +0,0 @@ -//! End-to-end tests for [`SnapshotInitJob`]. -//! -//! Reuses chain-construction helpers from [`crate::backfill::tests`] to -//! produce a real reth-side chain + initialized v2 proofs storage, then drives -//! the snapshot init job and asserts the resulting state. -//! -//! The job's own [`SnapshotInitJob::validate_state_root`] is the strongest -//! correctness check: a successful run means the computed root from the -//! snapshot tables + live hashed leaves matches reth's header at `target`. -//! These tests therefore focus on lifecycle behavior (outcome shape, refusal -//! to redo work, target-window validation) rather than table inspection. -//! -//! [`SnapshotInitJob::validate_state_root`]: super::job - -use super::{SnapshotError, SnapshotInitJob}; -use crate::{ - BackfillJob, InitializationJob, MdbxProofsStorageV2, OpProofsBackfillProvider, - OpProofsBackfillStore, OpProofsProviderRO, OpProofsSnapshotInitProvider, - OpProofsSnapshotProviderRO, OpProofsStore, RethTrieStorageLayout, SnapshotInitStatus, - test_utils::{ - build_chain_and_initialize_storage, build_chain_with_storage_writes_and_initialize_storage, - build_transfer_block, chain_spec_with_address, commit_block_to_database, create_storage, - deterministic_keypair, execute_block, public_key_to_address, - }, -}; -use alloy_eips::BlockNumHash; -use alloy_primitives::{Address, B256}; -use reth_db::Database; -use reth_db_common::init::init_genesis; -use reth_provider::{ - DatabaseProviderFactory, StorageSettingsCache, - test_utils::create_test_provider_factory_with_chain_spec, -}; -use reth_trie::{Nibbles, StoredNibbles, trie_cursor::TrieCursor}; -use std::sync::Arc; - -/// Count rows the history-aware `account_trie_cursor` would yield at -/// `target_block` — i.e., the number of entries the snapshot job's -/// `drain_account_trie` sees as input. -fn count_source_account_trie(storage: &Arc, target_block: u64) -> usize { - let provider = storage.provider_ro().expect("ro"); - let mut cursor = provider.account_trie_cursor(target_block).expect("cursor"); - let mut n = 0usize; - let mut entry = cursor.seek(Nibbles::default()).expect("seek"); - while entry.is_some() { - n += 1; - entry = cursor.next().expect("next"); - } - n -} - -/// Count rows in the destination `V2AccountsTrieSnapshot` table via the -/// snapshot reader cursor. -fn count_snapshot_account_trie(storage: &Arc) -> usize { - let sp = storage.snapshot_provider_ro().expect("ro"); - let mut cursor = sp.snapshot_account_trie_cursor().expect("cursor"); - let mut n = 0usize; - let mut entry = cursor.seek(Nibbles::default()).expect("seek"); - while entry.is_some() { - n += 1; - entry = cursor.next().expect("next"); - } - n -} - -#[test] -fn snapshot_init_at_latest_completes_and_anchor_matches() { - // 3-block chain; storage initialized at block 3 (earliest = latest = 3). - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(3); - let target = BlockNumHash::new(latest_num, latest_hash); - - // Drive the snapshot init job at `target`. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = - SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).expect("snapshot"); - - assert_eq!(outcome.block, target); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); - - // Invariant: the snapshot drained every account-trie row visible to the - // history-aware cursor at `target`, and the destination table now mirrors - // that count exactly. Robust to chain size: if the source trie has zero - // branches (legitimate for a small genesis with random-prefix accounts), - // all three counts are zero; otherwise they're all equal and non-zero. - let source_count = count_source_account_trie(&storage, latest_num); - let dest_count = count_snapshot_account_trie(&storage); - assert_eq!( - outcome.account_nodes_copied as usize, source_count, - "outcome count mismatch (source has {source_count} account-trie rows)" - ); - assert_eq!(dest_count, source_count, "snapshot table doesn't match source"); - - // After completion the snapshot is Ready at `target`. - let sp = storage.snapshot_provider_ro().unwrap(); - let anchor = sp.snapshot_anchor().expect("ready"); - assert_eq!(anchor, target); -} - -#[test] -fn snapshot_init_target_outside_window_errors() { - let (provider_factory, storage, _latest_num, _latest_hash) = - build_chain_and_initialize_storage(3); - - // earliest = latest = 3; target = 4 is past `latest`. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let err = SnapshotInitJob::new(reth_provider, storage).run(4).unwrap_err(); - assert!( - matches!( - err, - SnapshotError::SnapshotInitTargetOutsideWindow { - target_block: 4, - earliest: 3, - latest: 3, - } - ), - "got {err:?}" - ); -} - -#[test] -fn snapshot_init_refuses_second_run_when_completed() { - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_and_initialize_storage(3); - - // First run: succeeds. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).expect("first run"); - - // Second run on the same target: snapshot is already Completed, so the - // job must refuse rather than redo the work. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let err = SnapshotInitJob::new(reth_provider, storage).run(latest_num).unwrap_err(); - match err { - SnapshotError::SnapshotAlreadyExists { existing_block, existing_status } => { - assert_eq!(existing_block, latest_num); - assert_eq!(existing_status, SnapshotInitStatus::Completed); - } - other => panic!("expected SnapshotAlreadyExists, got {other:?}"), - } -} - -#[test] -fn snapshot_init_drift_detection_aborts_run() { - // Build a chain and plant a `Building` meta at a *different* anchor than - // the one the job will compute for `latest`. The classify step must - // notice the mismatch and bail with SnapshotResumeDriftDetected. - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_and_initialize_storage(3); - - // Plant Building meta at a fabricated anchor (different block number, so - // it can't possibly match the target the job derives for `latest_num`). - let planted_anchor = BlockNumHash::new(99, B256::repeat_byte(0xFE)); - { - let sp = storage.snapshot_initialization_provider().expect("init"); - sp.set_snapshot_init_anchor(planted_anchor).expect("plant"); - OpProofsSnapshotInitProvider::commit(sp).expect("commit"); - } - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let err = SnapshotInitJob::new(reth_provider, storage).run(latest_num).unwrap_err(); - match err { - SnapshotError::SnapshotResumeDriftDetected { anchor_block, .. } => { - assert_eq!(anchor_block, planted_anchor.number); - } - other => panic!("expected SnapshotResumeDriftDetected, got {other:?}"), - } -} - -#[test] -fn snapshot_init_succeeds_on_chain_with_storage_writes() { - // Drive the job over a chain whose every block touches a storage slot. - // This exercises the storage-trie phase (`drain_storage_trie` + - // `collect_storage_chunk`) end-to-end, including its interaction with - // `account_hashed_cursor` and per-address `storage_trie_cursor`. - // - // We don't assert `storage_nodes_copied > 0`: each block writes the same - // single slot of one contract, so the storage trie is a single leaf with - // no branch nodes — the snapshot can legitimately be empty. The job's - // internal state-root validation is the real correctness check. - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(3); - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = SnapshotInitJob::new(reth_provider, storage).run(latest_num).expect("snapshot"); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); -} - -#[test] -fn snapshot_init_with_small_chunk_size_drives_multi_chunk_drain() { - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - let target = BlockNumHash::new(latest_num, latest_hash); - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = SnapshotInitJob::new(reth_provider, storage.clone()) - .with_chunk_size(1) - .run(latest_num) - .expect("snapshot"); - - assert_eq!(outcome.block, target); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); - - // Destination must match source row-for-row even across many tiny commits. - let source_count = count_source_account_trie(&storage, latest_num); - let dest_count = count_snapshot_account_trie(&storage); - assert_eq!( - outcome.account_nodes_copied as usize, source_count, - "outcome count mismatch (source has {source_count} account-trie rows)" - ); - assert_eq!(dest_count, source_count, "snapshot table doesn't match source"); -} - -#[test] -fn snapshot_init_clear_then_rebuild_succeeds() { - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_and_initialize_storage(3); - let target = BlockNumHash::new(latest_num, latest_hash); - - // First run: lands a Completed snapshot at `target`. - { - let reth_provider = provider_factory.database_provider_ro().unwrap(); - SnapshotInitJob::new(reth_provider, storage.clone()).run(latest_num).expect("first run"); - } - - // Drop the snapshot — status reverts to NotStarted as far as the init - // anchor is concerned. - { - let sp = storage.backfill_provider().expect("rw"); - sp.clear_snapshot().expect("clear"); - OpProofsBackfillProvider::commit(sp).expect("commit"); - } - - // Second run must succeed (no SnapshotAlreadyExists) and produce a fresh - // Completed snapshot at the same anchor. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = SnapshotInitJob::new(reth_provider, storage).run(latest_num).expect("rebuild"); - assert_eq!(outcome.block, target); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); -} - -/// Negative test for the snapshot's validation safety net. -/// -/// Without this, a bug in `validate_state_root` (wrong cursor factory, wrong -/// target block, inverted compare, …) would let every existing test pass -/// while silently marking a corrupt snapshot `Ready`. -#[test] -fn snapshot_init_aborts_with_state_root_mismatch_when_header_corrupted() { - // Custom chain build — need to perturb the latest block's header before - // commit, so we can't reuse `build_chain_and_initialize_storage`. - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient = Address::repeat_byte(0x42); - const NUM_BLOCKS: u64 = 3; - // Snapshot validates state_root at `target_block` itself (unlike backfill, - // which validates at block_number - 1), so we corrupt the target. - const CORRUPTED_BLOCK: u64 = NUM_BLOCKS; - const BOGUS_ROOT: B256 = B256::repeat_byte(0xAB); - - let mut last_hash = chain_spec.genesis_hash(); - for n in 1..=NUM_BLOCKS { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - if n == CORRUPTED_BLOCK { - block.set_state_root(BOGUS_ROOT); - } - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - } - - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(NUM_BLOCKS, last_hash) - .unwrap(); - } - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let err = SnapshotInitJob::new(reth_provider, storage.clone()).run(NUM_BLOCKS).unwrap_err(); - match err { - SnapshotError::StateRootMismatch { block_number, expected, .. } => { - assert_eq!( - block_number, NUM_BLOCKS, - "validation fires at target_block (not target_block - 1)" - ); - assert_eq!(expected, BOGUS_ROOT, "expected root must come from the tampered header"); - } - other => panic!("expected StateRootMismatch, got {other:?}"), - } - - let init_anchor = storage - .snapshot_initialization_provider() - .expect("init") - .snapshot_init_anchor() - .expect("anchor"); - assert_eq!( - init_anchor.status, - SnapshotInitStatus::InProgress, - "meta must stay Building on validation failure", - ); -} - -/// Resume happy path for [`SnapshotInitJob::start_or_resume`] — the -/// `InProgress` arm at the *matching* anchor -#[test] -fn snapshot_init_resumes_from_partial_building_at_matching_anchor() { - let (provider_factory, storage, latest_num, latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - let target = BlockNumHash::new(latest_num, latest_hash); - - let source_count = count_source_account_trie(&storage, latest_num); - assert!( - source_count >= 2, - "resume test needs ≥2 source rows; got {source_count}. \ - Enrich the chain helper (more genesis allocs, multi-slot storage) \ - to restore meaningful resume coverage.", - ); - - // Read the first source row — this is what we'll seed as a "previously - // committed" chunk. Must match a real source row so the snapshot table - // stays consistent with the source for state-root validation. - let first_row = { - let ro = storage.provider_ro().expect("ro"); - let mut cursor = ro.account_trie_cursor(latest_num).expect("cursor"); - let (path, node) = cursor.seek(Nibbles::default()).expect("seek").expect("first row"); - (StoredNibbles(path), node) - }; - - // Plant a partial Building snapshot at `target`: meta + one row. - { - let sp = storage.snapshot_initialization_provider().expect("init"); - sp.set_snapshot_init_anchor(target).expect("plant"); - sp.store_account_trie_snapshot_branches(vec![first_row.clone()]).expect("seed"); - OpProofsSnapshotInitProvider::commit(sp).expect("commit"); - } - - // Sanity: the planted state is what `start_or_resume` will read. - let pre = storage - .snapshot_initialization_provider() - .expect("init") - .snapshot_init_anchor() - .expect("anchor"); - assert_eq!(pre.status, SnapshotInitStatus::InProgress); - assert_eq!(pre.block, Some(target)); - assert_eq!(pre.last_account_trie_key, Some(first_row.0)); - - // Resume. `chunk_size = 1` forces every remaining row through the - // `Some(resume_after)` branch, not just the first iteration. - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = SnapshotInitJob::new(reth_provider, storage.clone()) - .with_chunk_size(1) - .run(latest_num) - .expect("resume"); - - assert_eq!(outcome.block, target); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); - assert_eq!( - outcome.account_nodes_copied as usize, - source_count - 1, - "resumed run must not re-count the seeded row", - ); - - // Destination mirrors source — drain skipped the seeded row and - // appended the rest with no duplicate-key error, no missing rows. - assert_eq!(count_snapshot_account_trie(&storage), source_count); - - // `finalize_ready` ran — snapshot_anchor() returns `target` only when - // meta is Ready. - let post = storage.snapshot_provider_ro().expect("ro").snapshot_anchor().expect("ready"); - assert_eq!(post, target); -} - -fn widen_window(provider_factory: &F, storage: Arc, target_earliest: u64) -where - F: DatabaseProviderFactory< - Provider: reth_provider::DBProvider - + reth_provider::StageCheckpointReader - + reth_provider::ChangeSetReader - + reth_provider::StorageChangeSetReader - + reth_provider::BlockNumReader - + reth_provider::BlockHashReader - + reth_provider::HeaderProvider - + reth_provider::StorageSettingsCache - + Send, - >, -{ - let provider = provider_factory.database_provider_ro().unwrap(); - BackfillJob::new(provider, storage).run(target_earliest).expect("backfill widens window"); -} - -/// Interior-target snapshot — the history-aware cursors at `target` actually -/// have to reconstruct trie state at a block below the tip. -#[test] -fn snapshot_init_at_interior_target() { - // Build chain [1..=5] then backfill earliest to 2 → window [2, 5]. - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - assert_eq!(latest_num, 5); - widen_window(&provider_factory, storage.clone(), 2); - - // Snapshot at interior block 3. - const INTERIOR: u64 = 3; - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = SnapshotInitJob::new(reth_provider, storage.clone()) - .run(INTERIOR) - .expect("interior snapshot"); - - // `outcome.block.hash` resolves to reth's `block_hash(INTERIOR)`; the job - // already validated the snapshot against `header[INTERIOR].state_root` - // (not header[latest]), so reaching `Completed` means the historical - // reconstruction agrees with reth's stored interior root. - assert_eq!(outcome.block.number, INTERIOR); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); - - let sp = storage.snapshot_provider_ro().unwrap(); - let anchor = sp.snapshot_anchor().expect("ready"); - assert_eq!(anchor.number, INTERIOR); -} - -/// Lower-bound rejection: `target_block < window.earliest` must fire -/// `SnapshotInitTargetOutsideWindow`. Existing -/// `snapshot_init_target_outside_window_errors` only hits the `> latest` -/// half; this test pins the other half of the same `if`. -#[test] -fn snapshot_init_target_below_earliest_errors() { - // Window [2, 5] — running with target=1 falls below earliest. - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - assert_eq!(latest_num, 5); - widen_window(&provider_factory, storage.clone(), 2); - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let err = SnapshotInitJob::new(reth_provider, storage).run(1).unwrap_err(); - assert!( - matches!( - err, - SnapshotError::SnapshotInitTargetOutsideWindow { - target_block: 1, - earliest: 2, - latest: 5, - } - ), - "got {err:?}", - ); -} - -/// Inclusive window boundaries: both `target == earliest` and -/// `target == latest` must accept. -#[test] -fn snapshot_init_at_earliest_and_latest_boundaries_succeed() { - // Earliest boundary (target == earliest = 2 in a [2, 5] window). - { - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - assert_eq!(latest_num, 5); - widen_window(&provider_factory, storage.clone(), 2); - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = - SnapshotInitJob::new(reth_provider, storage).run(2).expect("earliest boundary"); - assert_eq!(outcome.block.number, 2); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); - } - - // Latest boundary (target == latest = 5 in a [2, 5] window). - { - let (provider_factory, storage, latest_num, _latest_hash) = - build_chain_with_storage_writes_and_initialize_storage(5); - assert_eq!(latest_num, 5); - widen_window(&provider_factory, storage.clone(), 2); - - let reth_provider = provider_factory.database_provider_ro().unwrap(); - let outcome = SnapshotInitJob::new(reth_provider, storage).run(5).expect("latest boundary"); - assert_eq!(outcome.block.number, 5); - assert_eq!(outcome.status, SnapshotInitStatus::Completed); - } -} diff --git a/vendor/reth-optimism-trie/src/test_utils.rs b/vendor/reth-optimism-trie/src/test_utils.rs deleted file mode 100644 index a81fa3e..0000000 --- a/vendor/reth-optimism-trie/src/test_utils.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! Shared test fixtures for OP-reth proofs storage tests. -//! -//! Builds real reth-side chains (genesis + EVM-executed blocks) and -//! initializes a V2 MDBX proofs storage on top, so downstream tests -//! (backfill, snapshot, …) can exercise end-to-end pipelines without -//! duplicating the chain-construction boilerplate. -//! -//! Gated on `#[cfg(test)]` and re-exported via `pub(crate)`; not part of the -//! crate's public surface. - -use crate::{MdbxProofsStorageV2, RethTrieStorageLayout, initialize::InitializationJob}; -use alloy_consensus::{Header, TxEip2930, constants::ETH_TO_WEI}; -use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256}; -use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, MAINNET, MIN_TRANSACTION_GAS}; -use reth_db::Database; -use reth_db_common::init::init_genesis; -use reth_ethereum_primitives::{Block, BlockBody, Receipt, Transaction, TransactionSigned}; -use reth_evm::{ConfigureEvm, execute::Executor}; -use reth_evm_ethereum::EthEvmConfig; -use reth_node_api::{NodePrimitives, NodeTypesWithDB}; -use reth_primitives_traits::{AlloyBlockHeader, Block as _, RecoveredBlock}; -use reth_provider::{ - BlockWriter as _, ExecutionOutcome, HashedPostStateProvider, LatestStateProviderRef, - ProviderFactory, StateRootProvider, StorageSettingsCache, providers::ProviderNodeTypes, - test_utils::create_test_provider_factory_with_chain_spec, -}; -use reth_revm::database::StateProviderDatabase; -use secp256k1::{Keypair, Secp256k1, SecretKey}; -use std::sync::Arc; -use tempfile::TempDir; - -/// Create a fresh V2 MDBX proofs storage backed by a temporary directory. -pub(crate) fn create_storage() -> Arc { - let path = TempDir::new().unwrap(); - Arc::new(MdbxProofsStorageV2::new(path.path()).unwrap()) -} - -pub(crate) fn public_key_to_address(pubkey: secp256k1::PublicKey) -> Address { - let hash = keccak256(&pubkey.serialize_uncompressed()[1..]); - Address::from_slice(&hash[12..]) -} - -/// Deterministic test keypair. Using a fixed secret key (rather than the -/// thread-local OS RNG) keeps the sender address and thus the entire chain's -/// state roots reproducible across runs. -pub(crate) fn deterministic_keypair() -> Keypair { - let secp = Secp256k1::new(); - let secret = SecretKey::from_byte_array([0x42u8; 32]).expect("valid secret"); - Keypair::from_secret_key(&secp, &secret) -} - -fn sign_tx_with_key_pair(key_pair: Keypair, tx: Transaction) -> TransactionSigned { - use alloy_consensus::SignableTransaction; - use reth_primitives_traits::crypto::secp256k1::sign_message; - let secret = B256::from_slice(&key_pair.secret_bytes()); - let sig = sign_message(secret, tx.signature_hash()).unwrap(); - tx.into_signed(sig).into() -} - -/// Pre-allocated contract address for storage-write tests. -const STORAGE_CONTRACT: Address = Address::repeat_byte(0xAB); - -/// Minimal contract that writes `BLOCKNUMBER` (i.e. current block.number) to -/// storage slot 0: -/// -/// ```text -/// 0x43 BLOCKNUMBER push block.number -/// 0x60 0x00 PUSH1 0x00 push slot 0 -/// 0x55 SSTORE store -/// 0x00 STOP -/// ``` -const STORAGE_BYTECODE: [u8; 5] = [0x43, 0x60, 0x00, 0x55, 0x00]; - -pub(crate) fn chain_spec_with_address(address: Address) -> Arc { - let alloc = std::iter::once(( - address, - GenesisAccount { balance: U256::from(10 * ETH_TO_WEI), ..Default::default() }, - )) - .chain(std::iter::once(( - STORAGE_CONTRACT, - GenesisAccount { code: Some(Bytes::from_static(&STORAGE_BYTECODE)), ..Default::default() }, - ))) - .chain((0x10u8..0x30).map(|i| { - ( - Address::repeat_byte(i), - GenesisAccount { balance: U256::from(1u64), ..Default::default() }, - ) - })) - .collect(); - - Arc::new( - ChainSpecBuilder::default() - .chain(MAINNET.chain) - .genesis(Genesis { alloc, ..MAINNET.genesis.clone() }) - .paris_activated() - .build(), - ) -} - -/// Construct an unsealed block with a single simple transfer. -pub(crate) fn build_transfer_block( - block_number: u64, - parent_hash: B256, - chain_spec: &Arc, - key_pair: Keypair, - nonce: u64, - recipient: Address, -) -> RecoveredBlock { - let tx = sign_tx_with_key_pair( - key_pair, - TxEip2930 { - chain_id: chain_spec.chain.id(), - nonce, - gas_limit: MIN_TRANSACTION_GAS, - gas_price: 1_500_000_000, - to: TxKind::Call(recipient), - value: U256::from(1), - ..Default::default() - } - .into(), - ); - Block { - header: Header { - parent_hash, - receipts_root: alloy_primitives::b256!( - "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" - ), - difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), - number: block_number, - gas_limit: MIN_TRANSACTION_GAS, - gas_used: MIN_TRANSACTION_GAS, - state_root: B256::ZERO, // filled in by execute_block - ..Default::default() - }, - body: BlockBody { transactions: vec![tx], ..Default::default() }, - } - .try_into_recovered() - .unwrap() -} - -pub(crate) fn execute_block( - block: &mut RecoveredBlock, - provider_factory: &ProviderFactory, - chain_spec: &Arc, -) -> reth_evm::execute::BlockExecutionOutput -where - N: ProviderNodeTypes< - Primitives: NodePrimitives, - > + NodeTypesWithDB, -{ - let provider = provider_factory.provider().unwrap(); - let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); - let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); - let block_executor = evm_config.batch_executor(db); - let execution_result = block_executor.execute(block).unwrap(); - - let hashed_state = - LatestStateProviderRef::new(&provider).hashed_post_state(&execution_result.state); - let state_root = LatestStateProviderRef::new(&provider).state_root(hashed_state).unwrap(); - block.set_state_root(state_root); - execution_result -} - -pub(crate) fn commit_block_to_database( - block: &RecoveredBlock, - execution_output: &reth_evm::execute::BlockExecutionOutput, - provider_factory: &ProviderFactory, -) where - N: ProviderNodeTypes< - Primitives: NodePrimitives, - > + NodeTypesWithDB, -{ - let execution_outcome = ExecutionOutcome { - bundle: execution_output.state.clone(), - receipts: vec![execution_output.receipts.clone()], - first_block: block.number(), - requests: vec![execution_output.requests.clone()], - }; - let state_provider = provider_factory.provider().unwrap(); - let hashed_state = HashedPostStateProvider::hashed_post_state( - &LatestStateProviderRef::new(&state_provider), - &execution_output.state, - ); - let provider_rw = provider_factory.provider_rw().unwrap(); - provider_rw - .append_blocks_with_state( - vec![block.clone()], - &execution_outcome, - hashed_state.into_sorted(), - ) - .unwrap(); - provider_rw.commit().unwrap(); -} - -/// Construct an unsealed block whose sole tx calls [`STORAGE_CONTRACT`], -/// triggering an SSTORE of `block.number` into slot 0 of the contract's storage. -/// -/// Gas accounting: the executor recomputes `gas_used` against the actual EVM -/// trace, so we deliberately set `gas_limit == gas_used` to a value large -/// enough to cover both the 21 000-gas tx base cost and the worst-case cold -/// SSTORE (~22 100 gas). -fn build_storage_call_block( - block_number: u64, - parent_hash: B256, - chain_spec: &Arc, - key_pair: Keypair, - nonce: u64, -) -> RecoveredBlock { - const CALL_GAS_LIMIT: u64 = 100_000; - let tx = sign_tx_with_key_pair( - key_pair, - TxEip2930 { - chain_id: chain_spec.chain.id(), - nonce, - gas_limit: CALL_GAS_LIMIT, - gas_price: 1_500_000_000, - to: TxKind::Call(STORAGE_CONTRACT), - value: U256::ZERO, - ..Default::default() - } - .into(), - ); - Block { - header: Header { - parent_hash, - receipts_root: alloy_primitives::b256!( - "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" - ), - difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), - number: block_number, - gas_limit: CALL_GAS_LIMIT, - gas_used: CALL_GAS_LIMIT, - state_root: B256::ZERO, - ..Default::default() - }, - body: BlockBody { transactions: vec![tx], ..Default::default() }, - } - .try_into_recovered() - .unwrap() -} - -/// Build a chain of `num_blocks` simple transfer blocks on top of a freshly -/// initialized genesis, then initialize the v2 proofs storage at the latest -/// block. Returns the provider factory, the storage, and the latest -/// (number, hash) pair. -pub(crate) fn build_chain_and_initialize_storage( - num_blocks: u64, -) -> ( - ProviderFactory, - Arc, - u64, - B256, -) { - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient = Address::repeat_byte(0x42); - let mut last_hash = chain_spec.genesis_hash(); - let mut last_number = 0u64; - for n in 1..=num_blocks { - let mut block = build_transfer_block(n, last_hash, &chain_spec, key_pair, n - 1, recipient); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - last_number = n; - } - - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(last_number, last_hash) - .unwrap(); - } - - (provider_factory, storage, last_number, last_hash) -} - -/// Like [`build_chain_and_initialize_storage`] but every block calls -/// [`STORAGE_CONTRACT`], so each block produces hashed-storage changesets in -/// addition to the account-level ones. -pub(crate) fn build_chain_with_storage_writes_and_initialize_storage( - num_blocks: u64, -) -> ( - ProviderFactory, - Arc, - u64, - B256, -) { - let key_pair = deterministic_keypair(); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let mut last_hash = chain_spec.genesis_hash(); - let mut last_number = 0u64; - for n in 1..=num_blocks { - let mut block = build_storage_call_block(n, last_hash, &chain_spec, key_pair, n - 1); - let exec = execute_block(&mut block, &provider_factory, &chain_spec); - commit_block_to_database(&block, &exec, &provider_factory); - last_hash = block.hash(); - last_number = n; - } - - let storage = create_storage(); - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let tx = provider_factory.db_ref().tx().unwrap(); - InitializationJob::new(storage.clone(), tx, trie_layout) - .run(last_number, last_hash) - .unwrap(); - } - - (provider_factory, storage, last_number, last_hash) -} diff --git a/vendor/reth-optimism-trie/tests/lib.rs b/vendor/reth-optimism-trie/tests/lib.rs deleted file mode 100644 index 7573ee3..0000000 --- a/vendor/reth-optimism-trie/tests/lib.rs +++ /dev/null @@ -1,2163 +0,0 @@ -//! Common test suite for [`OpProofsStore`] implementations. - -use alloy_eips::{BlockNumHash, NumHash, eip1898::BlockWithParent}; -use alloy_primitives::{B256, U256}; -use reth_optimism_trie::{ - BlockStateDiff, InMemoryProofsStorage, OpProofsInitProvider, OpProofsProviderRO, - OpProofsProviderRw, OpProofsStorageError, OpProofsStore, - db::{MdbxProofsStorage, MdbxProofsStorageV2}, -}; -use reth_primitives_traits::Account; -use reth_trie::{ - BranchNodeCompact, HashedPostState, HashedPostStateSorted, HashedStorage, Nibbles, TrieMask, - hashed_cursor::HashedCursor, - trie_cursor::TrieCursor, - updates::{TrieUpdates, TrieUpdatesSorted}, -}; -use serial_test::serial; -use std::sync::Arc; -use tempfile::TempDir; -use test_case::test_case; - -/// Helper to create a simple test branch node -fn create_test_branch() -> BranchNodeCompact { - let mut state_mask = TrieMask::default(); - state_mask.set_bit(0); - state_mask.set_bit(1); - - BranchNodeCompact { - state_mask, - tree_mask: TrieMask::default(), - hash_mask: TrieMask::default(), - hashes: Arc::new(vec![]), - root_hash: None, - } -} - -/// Helper to create a variant test branch node for comparison tests -fn create_test_branch_variant() -> BranchNodeCompact { - let mut state_mask = TrieMask::default(); - state_mask.set_bit(5); - state_mask.set_bit(6); - - BranchNodeCompact { - state_mask, - tree_mask: TrieMask::default(), - hash_mask: TrieMask::default(), - hashes: Arc::new(vec![]), - root_hash: None, - } -} - -/// Helper to create nibbles from a vector of u8 values -fn nibbles_from(vec: Vec) -> Nibbles { - Nibbles::from_nibbles_unchecked(vec) -} - -/// Helper to create a test account -fn create_test_account() -> Account { - Account { - nonce: 42, - balance: U256::from(1000000), - bytecode_hash: Some(B256::repeat_byte(0xBB)), - } -} - -/// Helper to create a test account with custom values -fn create_test_account_with_values(nonce: u64, balance: u64, code_hash_byte: u8) -> Account { - Account { - nonce, - balance: U256::from(balance), - bytecode_hash: Some(B256::repeat_byte(code_hash_byte)), - } -} - -/// Bootstrap a fresh store via the init flow. Equivalent to running the initial-state -/// preparation step in production. -fn bootstrap_anchor(storage: &S, anchor: BlockNumHash) { - let init = storage.initialization_provider().expect("init provider"); - init.set_initial_state_anchor(anchor).expect("set anchor"); - init.commit_initial_state().expect("commit initial state"); - OpProofsInitProvider::commit(init).expect("commit tx"); -} - -fn create_mdbx_proofs_storage() -> MdbxProofsStorage { - let path = TempDir::new().unwrap(); - let storage = MdbxProofsStorage::new(path.path()).unwrap(); - bootstrap_anchor(&storage, BlockNumHash { number: 0, hash: B256::ZERO }); - storage -} - -fn create_mdbx_proofs_storage_v2() -> MdbxProofsStorageV2 { - let path = TempDir::new().unwrap(); - let storage = MdbxProofsStorageV2::new(path.path()).unwrap(); - bootstrap_anchor(&storage, BlockNumHash { number: 0, hash: B256::ZERO }); - storage -} - -/// Test bootstrap via `commit_initial_state` populates both earliest and latest. -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(MdbxProofsStorage::new(TempDir::new().unwrap().path()).unwrap(); "Mdbx")] -#[test_case(MdbxProofsStorageV2::new(TempDir::new().unwrap().path()).unwrap(); "MdbxV2")] -#[serial] -fn test_bootstrap_initial_state(storage: S) -> Result<(), OpProofsStorageError> { - // Initially both endpoints surface NoBlocksFound. - let provider = storage.provider_ro().expect("provider ro"); - assert!(matches!(provider.get_earliest_block(), Err(OpProofsStorageError::NoBlocksFound))); - assert!(matches!(provider.get_latest_block(), Err(OpProofsStorageError::NoBlocksFound))); - - // Run the init flow. - let block_hash = B256::repeat_byte(0x42); - bootstrap_anchor(&storage, BlockNumHash { number: 100, hash: block_hash }); - - // Both endpoints point at the anchor. - let provider = storage.provider_ro().expect("provider ro"); - assert_eq!(provider.get_earliest_block()?, NumHash::new(100, block_hash)); - assert_eq!(provider.get_latest_block()?, NumHash::new(100, block_hash)); - - Ok(()) -} - -/// Test storing and retrieving trie updates -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_trie_updates_operations(storage: S) -> Result<(), OpProofsStorageError> { - let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); - let sorted_trie_updates = TrieUpdatesSorted::default(); - let sorted_post_state = HashedPostStateSorted::default(); - let block_state_diff = BlockStateDiff { - sorted_trie_updates: sorted_trie_updates.clone(), - sorted_post_state: sorted_post_state.clone(), - }; - - // Store trie updates - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref, block_state_diff)?; - provider_rw.commit()?; - - // Retrieve and verify - let provider_rw = storage.provider_rw().expect("provider ro"); - let retrieved_diff = provider_rw.fetch_trie_updates(block_ref.block.number)?; - provider_rw.commit()?; - assert_eq!(retrieved_diff.sorted_trie_updates, sorted_trie_updates); - assert_eq!(retrieved_diff.sorted_post_state, sorted_post_state); - - Ok(()) -} - -// ============================================================================= -// 1. Basic Cursor Operations -// ============================================================================= - -/// Test cursor operations on empty trie -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_cursor_empty_trie(storage: S) -> Result<(), OpProofsStorageError> { - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // All operations should return None on empty trie - assert!(cursor.seek_exact(Nibbles::default())?.is_none()); - assert!(cursor.seek(Nibbles::default())?.is_none()); - assert!(cursor.next()?.is_none()); - assert!(cursor.current()?.is_none()); - - Ok(()) -} - -/// Test cursor operations with single entry -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_cursor_single_entry(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2, 3]); - let branch = create_test_branch(); - - // Store single entry - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // Test seek_exact - let result = cursor.seek_exact(path)?.unwrap(); - assert_eq!(result.0, path); - - // Test current position - assert_eq!(cursor.current()?.unwrap(), path); - - // Test next from end should return None - assert!(cursor.next()?.is_none()); - - Ok(()) -} - -/// Test cursor operations with multiple entries -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_cursor_multiple_entries(storage: S) -> Result<(), OpProofsStorageError> { - let paths = vec![ - nibbles_from(vec![1]), - nibbles_from(vec![1, 2]), - nibbles_from(vec![2]), - nibbles_from(vec![2, 3]), - ]; - let branch = create_test_branch(); - - // Store multiple entries - let init_provider = storage.initialization_provider().expect("provider ro"); - for path in &paths { - init_provider.store_account_branches(vec![(*path, Some(branch.clone()))])?; - } - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // Test that we can iterate through all entries - let mut found_paths = Vec::new(); - while let Some((path, _)) = cursor.next()? { - found_paths.push(path); - } - - assert_eq!(found_paths.len(), 4); - // Paths should be in lexicographic order - for i in 0..paths.len() { - assert_eq!(found_paths[i], paths[i]); - } - - Ok(()) -} - -// ============================================================================= -// 2. Seek Operations -// ============================================================================= - -/// Test `seek_exact` with existing path -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_exact_existing_path(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2, 3]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let result = cursor.seek_exact(path)?.unwrap(); - assert_eq!(result.0, path); - - Ok(()) -} - -/// Test `seek_exact` with non-existing path -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_exact_non_existing_path( - storage: S, -) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2, 3]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let non_existing = nibbles_from(vec![4, 5, 6]); - assert!(cursor.seek_exact(non_existing)?.is_none()); - - Ok(()) -} - -/// Test `seek_exact` with empty path -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_exact_empty_path(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let result = cursor.seek_exact(Nibbles::default())?.unwrap(); - assert_eq!(result.0, Nibbles::default()); - - Ok(()) -} - -/// Test seek to existing path -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_to_existing_path(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2, 3]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let result = cursor.seek(path)?.unwrap(); - assert_eq!(result.0, path); - - Ok(()) -} - -/// Test seek between existing nodes -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_between_existing_nodes( - storage: S, -) -> Result<(), OpProofsStorageError> { - let path1 = nibbles_from(vec![1]); - let path2 = nibbles_from(vec![3]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path1, Some(branch.clone()))])?; - init_provider.store_account_branches(vec![(path2, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - // Seek to path between 1 and 3, should return path 3 - let seek_path = nibbles_from(vec![2]); - let result = cursor.seek(seek_path)?.unwrap(); - assert_eq!(result.0, path2); - - Ok(()) -} - -/// Test seek after all nodes -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_after_all_nodes(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - // Seek to path after all nodes - let seek_path = nibbles_from(vec![9]); - assert!(cursor.seek(seek_path)?.is_none()); - - Ok(()) -} - -/// Test seek before all nodes -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_seek_before_all_nodes(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![5]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - // Seek to path before all nodes, should return first node - let seek_path = nibbles_from(vec![1]); - let result = cursor.seek(seek_path)?.unwrap(); - assert_eq!(result.0, path); - - Ok(()) -} - -// ============================================================================= -// 3. Navigation Tests -// ============================================================================= - -/// Test next without prior seek -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_next_without_prior_seek(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - // next() without prior seek should start from beginning - let result = cursor.next()?.unwrap(); - assert_eq!(result.0, path); - - Ok(()) -} - -/// Test next after seek -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_next_after_seek(storage: S) -> Result<(), OpProofsStorageError> { - let path1 = nibbles_from(vec![1]); - let path2 = nibbles_from(vec![2]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path1, Some(branch.clone()))])?; - init_provider.store_account_branches(vec![(path2, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - cursor.seek(path1)?; - - // next() should return second node - let result = cursor.next()?.unwrap(); - assert_eq!(result.0, path2); - - Ok(()) -} - -/// Test next at end of trie -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_next_at_end_of_trie(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - cursor.seek(path)?; - - // next() at end should return None - assert!(cursor.next()?.is_none()); - - Ok(()) -} - -/// Test multiple consecutive next calls -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_multiple_consecutive_next( - storage: S, -) -> Result<(), OpProofsStorageError> { - let paths = vec![nibbles_from(vec![1]), nibbles_from(vec![2]), nibbles_from(vec![3])]; - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - for path in &paths { - init_provider.store_account_branches(vec![(*path, Some(branch.clone()))])?; - } - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // Iterate through all with consecutive next() calls - for expected_path in &paths { - let result = cursor.next()?.unwrap(); - assert_eq!(result.0, *expected_path); - } - - // Final next() should return None - assert!(cursor.next()?.is_none()); - - Ok(()) -} - -/// Test current after operations -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_current_after_operations(storage: S) -> Result<(), OpProofsStorageError> { - let path1 = nibbles_from(vec![1]); - let path2 = nibbles_from(vec![2]); - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path1, Some(branch.clone()))])?; - init_provider.store_account_branches(vec![(path2, Some(branch))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // Current should be None initially - assert!(cursor.current()?.is_none()); - - // After seek, current should track position - cursor.seek(path1)?; - assert_eq!(cursor.current()?.unwrap(), path1); - - // After next, current should update - cursor.next()?; - assert_eq!(cursor.current()?.unwrap(), path2); - - Ok(()) -} - -/// Test current with no prior operations -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_current_no_prior_operations( - storage: S, -) -> Result<(), OpProofsStorageError> { - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // Current should be None when no operations performed - assert!(cursor.current()?.is_none()); - - Ok(()) -} - -// ============================================================================= -// 4. Block Number Filtering -// ============================================================================= - -/// Test same path with different blocks -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_same_path_different_blocks( - storage: S, -) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2]); - let branch1 = create_test_branch(); - let branch2 = create_test_branch_variant(); - - // Store same path at different blocks - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch1))])?; - init_provider.store_account_branches(vec![(path, Some(branch2))])?; - init_provider.commit()?; - - // Cursor with max_block_number=75 should see only block 50 data - let mut cursor75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - let result75 = cursor75.seek_exact(path)?.unwrap(); - assert_eq!(result75.0, path); - - // Cursor with max_block_number=150 should see block 100 data (latest) - let mut cursor150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - let result150 = cursor150.seek_exact(path)?.unwrap(); - assert_eq!(result150.0, path); - - Ok(()) -} - -/// Test deleted branch nodes -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_deleted_branch_nodes(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2]); - let branch = create_test_branch(); - let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); - - // Store branch node, then delete it (store None) - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - // Cursor before deletion should see the node - let mut cursor75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - assert!(cursor75.seek_exact(path)?.is_some()); - - let mut block_state_diff_trie_updates = TrieUpdates::default(); - block_state_diff_trie_updates.removed_nodes.insert(path); - let block_state_diff = BlockStateDiff { - sorted_trie_updates: block_state_diff_trie_updates.into_sorted(), - sorted_post_state: HashedPostStateSorted::default(), - }; - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref, block_state_diff)?; - provider_rw.commit()?; - - // Cursor after deletion should not see the node - let mut cursor150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - assert!(cursor150.seek_exact(path)?.is_none()); - - Ok(()) -} - -// ============================================================================= -// 5. Hashed Address Filtering -// ============================================================================= - -/// Test account-specific cursor -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_account_specific_cursor(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2]); - let addr1 = B256::repeat_byte(0x01); - let addr2 = B256::repeat_byte(0x02); - let branch = create_test_branch(); - - // Store same path for different accounts (using storage branches) - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_storage_branches(addr1, vec![(path, Some(branch.clone()))])?; - init_provider.store_storage_branches(addr2, vec![(path, Some(branch))])?; - init_provider.commit()?; - - // Cursor for addr1 should only see addr1 data - let mut cursor1 = - storage.provider_ro().expect("provider ro").storage_trie_cursor(addr1, 100)?; - let result1 = cursor1.seek_exact(path)?.unwrap(); - assert_eq!(result1.0, path); - - // Cursor for addr2 should only see addr2 data - let mut cursor2 = - storage.provider_ro().expect("provider ro").storage_trie_cursor(addr2, 100)?; - let result2 = cursor2.seek_exact(path)?.unwrap(); - assert_eq!(result2.0, path); - - // Cursor for addr1 should not see addr2 data when iterating - let mut cursor1_iter = - storage.provider_ro().expect("provider ro").storage_trie_cursor(addr1, 100)?; - let mut found_count = 0; - while cursor1_iter.next()?.is_some() { - found_count += 1; - } - assert_eq!(found_count, 1); // Should only see one entry (for addr1) - - Ok(()) -} - -/// Test state trie cursor -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_state_trie_cursor(storage: S) -> Result<(), OpProofsStorageError> { - let path = nibbles_from(vec![1, 2]); - let addr = B256::repeat_byte(0x01); - let branch = create_test_branch(); - - // Store data for account trie and state trie - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_storage_branches(addr, vec![(path, Some(branch.clone()))])?; - init_provider.store_account_branches(vec![(path, Some(branch))])?; - init_provider.commit()?; - - // State trie cursor (None address) should only see state trie data - let mut state_cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let result = state_cursor.seek_exact(path)?.unwrap(); - assert_eq!(result.0, path); - - // Verify state cursor doesn't see account data when iterating - let mut state_cursor_iter = - storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let mut found_count = 0; - while state_cursor_iter.next()?.is_some() { - found_count += 1; - } - - assert_eq!(found_count, 1); // Should only see state trie entry - - Ok(()) -} - -/// Test mixed account and state data -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_mixed_account_state_data(storage: S) -> Result<(), OpProofsStorageError> { - let path1 = nibbles_from(vec![1]); - let path2 = nibbles_from(vec![2]); - let addr = B256::repeat_byte(0x01); - let branch = create_test_branch(); - - // Store mixed account and state trie data - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_storage_branches(addr, vec![(path1, Some(branch.clone()))])?; - init_provider.store_account_branches(vec![(path2, Some(branch))])?; - init_provider.commit()?; - - // Account cursor should only see account data - let mut account_cursor = - storage.provider_ro().expect("provider ro").storage_trie_cursor(addr, 100)?; - let mut account_paths = Vec::new(); - while let Some((path, _)) = account_cursor.next()? { - account_paths.push(path); - } - assert_eq!(account_paths.len(), 1); - assert_eq!(account_paths[0], path1); - - // State cursor should only see state data - let mut state_cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let mut state_paths = Vec::new(); - while let Some((path, _)) = state_cursor.next()? { - state_paths.push(path); - } - assert_eq!(state_paths.len(), 1); - assert_eq!(state_paths[0], path2); - - Ok(()) -} - -// ============================================================================= -// 6. Path Ordering Tests -// ============================================================================= - -/// Test lexicographic ordering -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_lexicographic_ordering(storage: S) -> Result<(), OpProofsStorageError> { - let paths = vec![ - nibbles_from(vec![3, 1]), - nibbles_from(vec![1, 2]), - nibbles_from(vec![2]), - nibbles_from(vec![1]), - ]; - let branch = create_test_branch(); - - // Store paths in sorted order (init provider requires sorted input, like a real trie walk) - let mut sorted_paths = paths; - sorted_paths.sort(); - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches( - sorted_paths.into_iter().map(|p| (p, Some(branch.clone()))).collect(), - )?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let mut found_paths = Vec::new(); - while let Some((path, _)) = cursor.next()? { - found_paths.push(path); - } - - // Should be returned in lexicographic order: [1], [1,2], [2], [3,1] - let expected_order = vec![ - nibbles_from(vec![1]), - nibbles_from(vec![1, 2]), - nibbles_from(vec![2]), - nibbles_from(vec![3, 1]), - ]; - - assert_eq!(found_paths, expected_order); - - Ok(()) -} - -/// Test path prefix scenarios -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_path_prefix_scenarios(storage: S) -> Result<(), OpProofsStorageError> { - let paths = vec![ - nibbles_from(vec![1]), // Prefix of next - nibbles_from(vec![1, 2]), // Extends first - nibbles_from(vec![1, 2, 3]), // Extends second - ]; - let branch = create_test_branch(); - - let init_provider = storage.initialization_provider().expect("provider ro"); - for path in &paths { - init_provider.store_account_branches(vec![(*path, Some(branch.clone()))])?; - } - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - - // Seek to prefix should find exact match - let result = cursor.seek_exact(paths[0])?.unwrap(); - assert_eq!(result.0, paths[0]); - - // Next should go to next path, not skip prefixed paths - let result = cursor.next()?.unwrap(); - assert_eq!(result.0, paths[1]); - - let result = cursor.next()?.unwrap(); - assert_eq!(result.0, paths[2]); - - Ok(()) -} - -/// Test complex nibble combinations -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_complex_nibble_combinations( - storage: S, -) -> Result<(), OpProofsStorageError> { - // Test various nibble patterns including edge values - let paths = vec![ - nibbles_from(vec![0]), - nibbles_from(vec![0, 15]), - nibbles_from(vec![15]), - nibbles_from(vec![15, 0]), - nibbles_from(vec![7, 8, 9]), - ]; - let branch = create_test_branch(); - - // Store paths in sorted order (init provider requires sorted input, like a real trie walk) - let mut sorted_paths = paths; - sorted_paths.sort(); - let init_provider = storage.initialization_provider().expect("provider ro"); - init_provider.store_account_branches( - sorted_paths.into_iter().map(|p| (p, Some(branch.clone()))).collect(), - )?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - let mut found_paths = Vec::new(); - while let Some((path, _)) = cursor.next()? { - found_paths.push(path); - } - - // All paths should be found and in correct order - assert_eq!(found_paths.len(), 5); - - // Verify specific ordering for edge cases - assert_eq!(found_paths[0], nibbles_from(vec![0])); - assert_eq!(found_paths[1], nibbles_from(vec![0, 15])); - assert_eq!(found_paths[4], nibbles_from(vec![15, 0])); - - Ok(()) -} - -// ============================================================================= -// 7. Leaf Node Tests (Hashed Accounts and Storage) -// ============================================================================= - -/// Test store and retrieve single account -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_store_and_retrieve_single_account( - storage: S, -) -> Result<(), OpProofsStorageError> { - let account_key = B256::repeat_byte(0x01); - let account = create_test_account(); - - // Store account - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_accounts(vec![(account_key, Some(account))])?; - init_provider.commit()?; - - // Retrieve via cursor - let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; - let result = cursor.seek(account_key)?.unwrap(); - - assert_eq!(result.0, account_key); - assert_eq!(result.1.nonce, account.nonce); - assert_eq!(result.1.balance, account.balance); - assert_eq!(result.1.bytecode_hash, account.bytecode_hash); - - Ok(()) -} - -/// Test account cursor navigation -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_account_cursor_navigation( - storage: S, -) -> Result<(), OpProofsStorageError> { - let accounts = [ - (B256::repeat_byte(0x01), create_test_account()), - (B256::repeat_byte(0x03), create_test_account()), - (B256::repeat_byte(0x05), create_test_account()), - ]; - - // Store accounts - let accounts_to_store: Vec<_> = accounts.iter().map(|(k, v)| (*k, Some(*v))).collect(); - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_accounts(accounts_to_store)?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; - - // Test seeking to exact key - let result = cursor.seek(accounts[1].0)?.unwrap(); - assert_eq!(result.0, accounts[1].0); - - // Test seeking to key that doesn't exist (should return next greater) - let seek_key = B256::repeat_byte(0x02); - let result = cursor.seek(seek_key)?.unwrap(); - assert_eq!(result.0, accounts[1].0); // Should find 0x03 - - // Test next() navigation - let result = cursor.next()?.unwrap(); - assert_eq!(result.0, accounts[2].0); // Should find 0x05 - - // Test next() at end - assert!(cursor.next()?.is_none()); - - Ok(()) -} - -/// Test account block versioning -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_account_block_versioning(storage: S) -> Result<(), OpProofsStorageError> { - let account_key = B256::repeat_byte(0x01); - let account_v1 = create_test_account_with_values(1, 100, 0xBB); - let account_v2 = create_test_account_with_values(2, 200, 0xDD); - - // Store account at different blocks - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_accounts(vec![(account_key, Some(account_v1))])?; - init_provider.commit()?; - - // Cursor with max_block_number=75 should see v1 - let mut cursor75 = storage.provider_ro().expect("provider ro").account_hashed_cursor(75)?; - let result75 = cursor75.seek(account_key)?.unwrap(); - assert_eq!(result75.1.nonce, account_v1.nonce); - assert_eq!(result75.1.balance, account_v1.balance); - - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_accounts(vec![(account_key, Some(account_v2))])?; - init_provider.commit()?; - - // After update, Cursor with max_block_number=150 should see v2 - let mut cursor150 = storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; - let result150 = cursor150.seek(account_key)?.unwrap(); - assert_eq!(result150.1.nonce, account_v2.nonce); - assert_eq!(result150.1.balance, account_v2.balance); - - Ok(()) -} - -/// Test store and retrieve storage -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] - -fn test_store_and_retrieve_storage( - storage: S, -) -> Result<(), OpProofsStorageError> { - let hashed_address = B256::repeat_byte(0x01); - let storage_slots = vec![ - (B256::repeat_byte(0x10), U256::from(100)), - (B256::repeat_byte(0x20), U256::from(200)), - (B256::repeat_byte(0x30), U256::from(300)), - ]; - - // Store storage slots - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(hashed_address, storage_slots.clone())?; - init_provider.commit()?; - - // Retrieve via cursor - let mut cursor = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; - - // Test seeking to each slot - for (key, expected_value) in &storage_slots { - let result = cursor.seek(*key)?.unwrap(); - assert_eq!(result.0, *key); - assert_eq!(result.1, *expected_value); - } - - Ok(()) -} - -/// Test storage cursor navigation -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_storage_cursor_navigation( - storage: S, -) -> Result<(), OpProofsStorageError> { - let hashed_address = B256::repeat_byte(0x01); - let storage_slots = vec![ - (B256::repeat_byte(0x10), U256::from(100)), - (B256::repeat_byte(0x30), U256::from(300)), - (B256::repeat_byte(0x50), U256::from(500)), - ]; - - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(hashed_address, storage_slots.clone())?; - init_provider.commit()?; - - let mut cursor = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; - - // Start from beginning with next() - let mut found_slots = Vec::new(); - while let Some((key, value)) = cursor.next()? { - found_slots.push((key, value)); - } - - assert_eq!(found_slots.len(), 3); - assert_eq!(found_slots[0], storage_slots[0]); - assert_eq!(found_slots[1], storage_slots[1]); - assert_eq!(found_slots[2], storage_slots[2]); - - Ok(()) -} - -/// Test storage account isolation -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_storage_account_isolation( - storage: S, -) -> Result<(), OpProofsStorageError> { - let address1 = B256::repeat_byte(0x01); - let address2 = B256::repeat_byte(0x02); - let storage_key = B256::repeat_byte(0x10); - - // Store same storage key for different accounts - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(address1, vec![(storage_key, U256::from(100))])?; - init_provider.store_hashed_storages(address2, vec![(storage_key, U256::from(200))])?; - init_provider.commit()?; - - // Verify each account sees only its own storage - let mut cursor1 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(address1, 100)?; - let result1 = cursor1.seek(storage_key)?.unwrap(); - assert_eq!(result1.1, U256::from(100)); - - let mut cursor2 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(address2, 100)?; - let result2 = cursor2.seek(storage_key)?.unwrap(); - assert_eq!(result2.1, U256::from(200)); - - // Verify cursor1 doesn't see address2's storage - let mut cursor1_iter = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(address1, 100)?; - let mut count = 0; - while cursor1_iter.next()?.is_some() { - count += 1; - } - assert_eq!(count, 1); // Should only see one entry - - Ok(()) -} - -/// Test storage block versioning -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_storage_block_versioning(storage: S) -> Result<(), OpProofsStorageError> { - let hashed_address = B256::repeat_byte(0x01); - let storage_key = B256::repeat_byte(0x10); - - // Set up initial state with value 100 (init_provider writes current-state only) - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(hashed_address, vec![(storage_key, U256::from(100))])?; - init_provider.commit()?; - - // Apply a block-level update at block 100 that changes the value to 200. - // This records changeset (old=100) + history bitmap + updates current state. - let mut block_state_diff_post_state = HashedPostState::default(); - let mut hashed_storage = HashedStorage::default(); - hashed_storage.storage.insert(storage_key, U256::from(200)); - block_state_diff_post_state.storages.insert(hashed_address, hashed_storage); - - let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); - let block_state_diff = BlockStateDiff { - sorted_trie_updates: TrieUpdatesSorted::default(), - sorted_post_state: block_state_diff_post_state.into_sorted(), - }; - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref, block_state_diff)?; - provider_rw.commit()?; - - // Cursor with max_block_number=75 should see old value (before block 100 update) - let mut cursor75 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; - let result75 = cursor75.seek(storage_key)?.unwrap(); - assert_eq!(result75.1, U256::from(100)); - - // Cursor with max_block_number=150 should see new value (after block 100 update) - let mut cursor150 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 150)?; - let result150 = cursor150.seek(storage_key)?.unwrap(); - assert_eq!(result150.1, U256::from(200)); - - Ok(()) -} - -/// Test storage zero value deletion -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_storage_zero_value_deletion( - storage: S, -) -> Result<(), OpProofsStorageError> { - let hashed_address = B256::repeat_byte(0x01); - let storage_key = B256::repeat_byte(0x10); - - // Store non-zero value - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(hashed_address, vec![(storage_key, U256::from(100))])?; - init_provider.commit()?; - - // Cursor before deletion should see the value - let mut cursor75 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; - let result75 = cursor75.seek(storage_key)?.unwrap(); - assert_eq!(result75.1, U256::from(100)); - - // "Delete" by storing zero value at block 100 - let mut block_state_diff_post_state = HashedPostState::default(); - let mut hashed_storage = HashedStorage::default(); - hashed_storage.storage.insert(storage_key, U256::ZERO); - block_state_diff_post_state.storages.insert(hashed_address, hashed_storage); - - let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); - let block_state_diff = BlockStateDiff { - sorted_trie_updates: TrieUpdatesSorted::default(), - sorted_post_state: block_state_diff_post_state.into_sorted(), - }; - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref, block_state_diff)?; - provider_rw.commit()?; - - // Cursor after deletion should NOT see the entry (zero values are skipped) - let mut cursor150 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 150)?; - let result150 = cursor150.seek(storage_key)?; - assert!(result150.is_none(), "Zero values should be skipped/deleted"); - - Ok(()) -} - -/// Test that zero values are skipped during iteration -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_storage_cursor_skips_zero_values( - storage: S, -) -> Result<(), OpProofsStorageError> { - let hashed_address = B256::repeat_byte(0x01); - - // Create a mix of non-zero and zero value storage slots - let storage_slots = vec![ - (B256::repeat_byte(0x10), U256::from(100)), // Non-zero - (B256::repeat_byte(0x20), U256::ZERO), // Zero value - should be skipped - (B256::repeat_byte(0x30), U256::from(300)), // Non-zero - (B256::repeat_byte(0x40), U256::ZERO), // Zero value - should be skipped - (B256::repeat_byte(0x50), U256::from(500)), // Non-zero - ]; - - // Store all slots - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(hashed_address, storage_slots)?; - init_provider.commit()?; - - // Create cursor and iterate through all entries - let mut cursor = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; - let mut found_slots = Vec::new(); - while let Some((key, value)) = cursor.next()? { - found_slots.push((key, value)); - } - - // Should only find 3 non-zero values - assert_eq!(found_slots.len(), 3, "Zero values should be skipped during iteration"); - - // Verify the non-zero values are the ones we stored - assert_eq!(found_slots[0], (B256::repeat_byte(0x10), U256::from(100))); - assert_eq!(found_slots[1], (B256::repeat_byte(0x30), U256::from(300))); - assert_eq!(found_slots[2], (B256::repeat_byte(0x50), U256::from(500))); - - // Verify seeking to a zero-value slot returns None or skips to next non-zero - let mut seek_cursor = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 100)?; - let seek_result = seek_cursor.seek(B256::repeat_byte(0x20))?; - - // Should either return None or skip to the next non-zero value (0x30) - if let Some((key, value)) = seek_result { - assert_eq!(key, B256::repeat_byte(0x30), "Should skip zero value and find next non-zero"); - assert_eq!(value, U256::from(300)); - } - - Ok(()) -} - -/// Test empty cursors -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_empty_cursors(storage: S) -> Result<(), OpProofsStorageError> { - // Test empty account cursor - let mut account_cursor = - storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; - assert!(account_cursor.seek(B256::repeat_byte(0x01))?.is_none()); - assert!(account_cursor.next()?.is_none()); - - // Test empty storage cursor - let mut storage_cursor = storage - .provider_ro() - .expect("provider ro") - .storage_hashed_cursor(B256::repeat_byte(0x01), 100)?; - assert!(storage_cursor.seek(B256::repeat_byte(0x10))?.is_none()); - assert!(storage_cursor.next()?.is_none()); - - Ok(()) -} - -/// Test cursor boundary conditions -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_cursor_boundary_conditions( - storage: S, -) -> Result<(), OpProofsStorageError> { - let account_key = B256::repeat_byte(0x80); // Middle value - let account = create_test_account(); - - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_accounts(vec![(account_key, Some(account))])?; - init_provider.commit()?; - - let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; - - // Seek to minimum key should find our account - let result = cursor.seek(B256::ZERO)?.unwrap(); - assert_eq!(result.0, account_key); - - // Seek to maximum key should find nothing - assert!(cursor.seek(B256::repeat_byte(0xFF))?.is_none()); - - // Seek to key just before our account should find our account - let just_before = B256::repeat_byte(0x7F); - let result = cursor.seek(just_before)?.unwrap(); - assert_eq!(result.0, account_key); - - Ok(()) -} - -/// Test large batch operations -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_large_batch_operations(storage: S) -> Result<(), OpProofsStorageError> { - // Create large batch of accounts - let mut accounts = Vec::new(); - for i in 0..100 { - let key = B256::from([i as u8; 32]); - let account = create_test_account_with_values(i, i * 1000, (i + 1) as u8); - accounts.push((key, Some(account))); - } - - // Store in batch - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_accounts(accounts.clone())?; - init_provider.commit()?; - - // Verify all accounts can be retrieved - let mut cursor = storage.provider_ro().expect("provider ro").account_hashed_cursor(100)?; - let mut found_count = 0; - while cursor.next()?.is_some() { - found_count += 1; - } - assert_eq!(found_count, 100); - - // Test specific account retrieval - let test_key = B256::from([42u8; 32]); - let result = cursor.seek(test_key)?.unwrap(); - assert_eq!(result.0, test_key); - assert_eq!(result.1.nonce, 42); - - Ok(()) -} - -/// Test wiped storage in [`HashedPostState`] -/// -/// When `store_trie_updates` receives a [`HashedPostState`] with wiped=true for a storage entry, -/// it should iterate all existing values for that address and create deletion entries for them. -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_store_trie_updates_with_wiped_storage( - storage: S, -) -> Result<(), OpProofsStorageError> { - use reth_trie::HashedStorage; - - let hashed_address = B256::repeat_byte(0x01); - let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); - - // First, store some storage values at block 50 - let storage_slots = vec![ - (B256::repeat_byte(0x10), U256::from(100)), - (B256::repeat_byte(0x20), U256::from(200)), - (B256::repeat_byte(0x30), U256::from(300)), - (B256::repeat_byte(0x40), U256::from(400)), - ]; - - let init_provider = storage.initialization_provider().expect("provider rw"); - init_provider.store_hashed_storages(hashed_address, storage_slots.clone())?; - init_provider.commit()?; - - // Verify all values are present at block 75 - let mut cursor75 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; - let mut found_slots = Vec::new(); - while let Some((key, value)) = cursor75.next()? { - found_slots.push((key, value)); - } - assert_eq!(found_slots.len(), 4, "All storage slots should be present before wipe"); - assert_eq!(found_slots[0], (B256::repeat_byte(0x10), U256::from(100))); - assert_eq!(found_slots[1], (B256::repeat_byte(0x20), U256::from(200))); - assert_eq!(found_slots[2], (B256::repeat_byte(0x30), U256::from(300))); - assert_eq!(found_slots[3], (B256::repeat_byte(0x40), U256::from(400))); - - // Now create a HashedPostState with wiped=true for this address at block 100 - let mut post_state = HashedPostState::default(); - let wiped_storage = HashedStorage::new(true); // wiped=true, empty storage map - post_state.storages.insert(hashed_address, wiped_storage); - - let block_state_diff = BlockStateDiff { - sorted_trie_updates: TrieUpdatesSorted::default(), - sorted_post_state: post_state.into_sorted(), - }; - - // Store the wiped state - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref, block_state_diff)?; - provider_rw.commit()?; - - // After wiping, cursor at block 150 should see NO storage values - let mut cursor150 = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 150)?; - let mut found_slots_after_wipe = Vec::new(); - while let Some((key, value)) = cursor150.next()? { - found_slots_after_wipe.push((key, value)); - } - - assert_eq!( - found_slots_after_wipe.len(), - 0, - "All storage slots should be deleted after wipe. Found: {:?}", - found_slots_after_wipe - ); - - // Verify individual seeks also return None - for (slot, _) in &storage_slots { - let mut seek_cursor = storage - .provider_ro() - .expect("provider ro") - .storage_hashed_cursor(hashed_address, 150)?; - let result = seek_cursor.seek(*slot)?; - assert!( - result.is_none() || result.unwrap().0 != *slot, - "Storage slot {:?} should be deleted after wipe", - slot - ); - } - - // Verify cursor at block 75 (before wipe) still sees all values - let mut cursor75_after = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(hashed_address, 75)?; - let mut found_slots_before_wipe = Vec::new(); - while let Some((key, value)) = cursor75_after.next()? { - found_slots_before_wipe.push((key, value)); - } - assert_eq!( - found_slots_before_wipe.len(), - 4, - "All storage slots should still be present when querying before wipe block" - ); - - Ok(()) -} - -/// Test that `store_trie_updates` properly stores branch nodes, leaf nodes, and removals -/// -/// This test verifies that all data stored via `store_trie_updates` can be read back -/// through the cursor APIs. -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_store_trie_updates_comprehensive( - storage: S, -) -> Result<(), OpProofsStorageError> { - use reth_trie::{HashedStorage, updates::StorageTrieUpdates}; - - let block_ref = BlockWithParent::new(B256::ZERO, NumHash::new(100, B256::repeat_byte(0x96))); - - // Create comprehensive trie updates with branches, leaves, and removals - let mut trie_updates = TrieUpdates::default(); - - // Add account branch nodes - let account_path1 = nibbles_from(vec![1, 2, 3]); - let account_path2 = nibbles_from(vec![4, 5, 6]); - let account_branch1 = create_test_branch(); - let account_branch2 = create_test_branch_variant(); - - trie_updates.account_nodes.insert(account_path1, account_branch1); - trie_updates.account_nodes.insert(account_path2, account_branch2); - - // Add removed account nodes - let removed_account_path = nibbles_from(vec![7, 8, 9]); - trie_updates.removed_nodes.insert(removed_account_path); - - // Add storage branch nodes for an address - let hashed_address = B256::repeat_byte(0x42); - let storage_path1 = nibbles_from(vec![1, 1]); - let storage_path2 = nibbles_from(vec![2, 2]); - let storage_branch = create_test_branch(); - - let mut storage_trie = StorageTrieUpdates::default(); - storage_trie.storage_nodes.insert(storage_path1, storage_branch.clone()); - storage_trie.storage_nodes.insert(storage_path2, storage_branch); - - // Add removed storage node - let removed_storage_path = nibbles_from(vec![3, 3]); - storage_trie.removed_nodes.insert(removed_storage_path); - - trie_updates.insert_storage_updates(hashed_address, storage_trie); - - // Create post state with accounts and storage - let mut post_state = HashedPostState::default(); - - // Add accounts - let account1_addr = B256::repeat_byte(0x10); - let account2_addr = B256::repeat_byte(0x20); - let account1 = create_test_account_with_values(1, 1000, 0xAA); - let account2 = create_test_account_with_values(2, 2000, 0xBB); - - post_state.accounts.insert(account1_addr, Some(account1)); - post_state.accounts.insert(account2_addr, Some(account2)); - - // Add deleted account - let deleted_account_addr = B256::repeat_byte(0x30); - post_state.accounts.insert(deleted_account_addr, None); - - // Add storage for an address - let storage_addr = B256::repeat_byte(0x50); - let mut hashed_storage = HashedStorage::new(false); - hashed_storage.storage.insert(B256::repeat_byte(0x01), U256::from(111)); - hashed_storage.storage.insert(B256::repeat_byte(0x02), U256::from(222)); - hashed_storage.storage.insert(B256::repeat_byte(0x03), U256::ZERO); // Deleted storage - post_state.storages.insert(storage_addr, hashed_storage); - - let block_state_diff = BlockStateDiff { - sorted_trie_updates: trie_updates.into_sorted(), - sorted_post_state: post_state.into_sorted(), - }; - - // Store the updates - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref, block_state_diff)?; - provider_rw.commit()?; - - // ========== Verify Account Branch Nodes ========== - let mut account_trie_cursor = storage - .provider_ro() - .expect("provider ro") - .account_trie_cursor(block_ref.block.number + 10)?; - - // Should find the added branches - let result1 = account_trie_cursor.seek_exact(account_path1)?; - assert!(result1.is_some(), "Account branch node 1 should be found"); - assert_eq!(result1.unwrap().0, account_path1); - - let result2 = account_trie_cursor.seek_exact(account_path2)?; - assert!(result2.is_some(), "Account branch node 2 should be found"); - assert_eq!(result2.unwrap().0, account_path2); - - // Removed node should not be found - let removed_result = account_trie_cursor.seek_exact(removed_account_path)?; - assert!(removed_result.is_none(), "Removed account node should not be found"); - - // ========== Verify Storage Branch Nodes ========== - let mut storage_trie_cursor = storage - .provider_ro() - .expect("provider ro") - .storage_trie_cursor(hashed_address, block_ref.block.number + 10)?; - - let storage_result1 = storage_trie_cursor.seek_exact(storage_path1)?; - assert!(storage_result1.is_some(), "Storage branch node 1 should be found"); - - let storage_result2 = storage_trie_cursor.seek_exact(storage_path2)?; - assert!(storage_result2.is_some(), "Storage branch node 2 should be found"); - - // Removed storage node should not be found - let removed_storage_result = storage_trie_cursor.seek_exact(removed_storage_path)?; - assert!(removed_storage_result.is_none(), "Removed storage node should not be found"); - - // ========== Verify Account Leaves ========== - let mut account_cursor = storage - .provider_ro() - .expect("provider ro") - .account_hashed_cursor(block_ref.block.number + 10)?; - - let acc1_result = account_cursor.seek(account1_addr)?; - assert!(acc1_result.is_some(), "Account 1 should be found"); - assert_eq!(acc1_result.unwrap().0, account1_addr); - assert_eq!(acc1_result.unwrap().1.nonce, 1); - assert_eq!(acc1_result.unwrap().1.balance, U256::from(1000)); - - let acc2_result = account_cursor.seek(account2_addr)?; - assert!(acc2_result.is_some(), "Account 2 should be found"); - assert_eq!(acc2_result.unwrap().1.nonce, 2); - - // Deleted account should not be found - let deleted_acc_result = account_cursor.seek(deleted_account_addr)?; - assert!( - deleted_acc_result.is_none() || deleted_acc_result.unwrap().0 != deleted_account_addr, - "Deleted account should not be found" - ); - - // ========== Verify Storage Leaves ========== - let mut storage_cursor = storage - .provider_ro() - .expect("provider ro") - .storage_hashed_cursor(storage_addr, block_ref.block.number + 10)?; - - let slot1_result = storage_cursor.seek(B256::repeat_byte(0x01))?; - assert!(slot1_result.is_some(), "Storage slot 1 should be found"); - assert_eq!(slot1_result.unwrap().1, U256::from(111)); - - let slot2_result = storage_cursor.seek(B256::repeat_byte(0x02))?; - assert!(slot2_result.is_some(), "Storage slot 2 should be found"); - assert_eq!(slot2_result.unwrap().1, U256::from(222)); - - // Zero-valued storage should not be found (deleted) - let slot3_result = storage_cursor.seek(B256::repeat_byte(0x03))?; - assert!( - slot3_result.is_none() || slot3_result.unwrap().0 != B256::repeat_byte(0x03), - "Zero-valued storage slot should not be found" - ); - - // ========== Verify fetch_trie_updates can retrieve the data ========== - let provider_rw = storage.provider_rw().expect("provider ro"); - let fetched_diff = provider_rw.fetch_trie_updates(block_ref.block.number)?; - provider_rw.commit()?; - - // Check that trie updates are stored - assert_eq!( - fetched_diff.sorted_trie_updates.account_nodes_ref().len(), - 3, - "Should have 3 account nodes, including removed" - ); - assert_eq!( - fetched_diff.sorted_trie_updates.storage_tries_ref().len(), - 1, - "Should have 1 storage trie" - ); - - // Check that post state is stored - assert_eq!( - fetched_diff.sorted_post_state.accounts.len(), - 3, - "Should have 3 accounts (including deleted)" - ); - assert_eq!(fetched_diff.sorted_post_state.storages.len(), 1, "Should have 1 storage entry"); - - Ok(()) -} - -/// Test that `replace_updates` properly applies hashed/trie storage updates to the DB -/// -/// This test verifies the bug fix where `replace_updates` was only storing `trie_updates` -/// and `post_states` directly without populating the internal data structures -/// (`hashed_accounts`, `hashed_storages`, `account_branches`, `storage_branches`). -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_replace_updates_applies_all_updates( - storage: S, -) -> Result<(), OpProofsStorageError> { - use reth_trie::{HashedStorage, updates::StorageTrieUpdates}; - - let block_ref_50 = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); - - // ========== Setup: Store initial state at blocks 50, 100, 101 ========== - let initial_account_addr = B256::repeat_byte(0x10); - let initial_account = create_test_account_with_values(1, 1000, 0xAA); - - let initial_storage_addr = B256::repeat_byte(0x20); - let initial_storage_slot = B256::repeat_byte(0x01); - let initial_storage_value = U256::from(100); - - let initial_branch_path = nibbles_from(vec![1, 2, 3]); - let initial_branch = create_test_branch(); - - // Store initial data at block 50 - let mut initial_trie_updates_50 = TrieUpdates::default(); - initial_trie_updates_50.account_nodes.insert(initial_branch_path, initial_branch.clone()); - - let mut initial_post_state_50 = HashedPostState::default(); - initial_post_state_50.accounts.insert(initial_account_addr, Some(initial_account)); - - let initial_diff_50 = BlockStateDiff { - sorted_trie_updates: initial_trie_updates_50.into_sorted(), - sorted_post_state: initial_post_state_50.into_sorted(), - }; - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_50, initial_diff_50)?; - provider_rw.commit()?; - - // Store data at block 100 (common block) - let mut initial_trie_updates_100 = TrieUpdates::default(); - let common_branch_path = nibbles_from(vec![4, 5, 6]); - initial_trie_updates_100.account_nodes.insert(common_branch_path, initial_branch.clone()); - - let mut initial_post_state_100 = HashedPostState::default(); - let mut initial_storage_100 = HashedStorage::new(false); - initial_storage_100.storage.insert(initial_storage_slot, initial_storage_value); - initial_post_state_100.storages.insert(initial_storage_addr, initial_storage_100); - - let initial_diff_100 = BlockStateDiff { - sorted_trie_updates: initial_trie_updates_100.into_sorted(), - sorted_post_state: initial_post_state_100.into_sorted(), - }; - - let block_ref_100 = - BlockWithParent::new(block_ref_50.block.hash, NumHash::new(100, B256::repeat_byte(0x97))); - - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_100, initial_diff_100)?; - provider_rw.commit()?; - - // Store data at block 101 (will be replaced) - let mut initial_trie_updates_101 = TrieUpdates::default(); - let old_branch_path = nibbles_from(vec![7, 8, 9]); - initial_trie_updates_101.account_nodes.insert(old_branch_path, initial_branch); - - let mut initial_post_state_101 = HashedPostState::default(); - let old_account_addr = B256::repeat_byte(0x30); - let old_account = create_test_account_with_values(99, 9999, 0xFF); - initial_post_state_101.accounts.insert(old_account_addr, Some(old_account)); - - let initial_diff_101 = BlockStateDiff { - sorted_trie_updates: initial_trie_updates_101.into_sorted(), - sorted_post_state: initial_post_state_101.into_sorted(), - }; - let block_ref_101 = - BlockWithParent::new(block_ref_100.block.hash, NumHash::new(101, B256::repeat_byte(0x98))); - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_101, initial_diff_101)?; - provider_rw.commit()?; - - let block_ref_102 = - BlockWithParent::new(block_ref_101.block.hash, NumHash::new(102, B256::repeat_byte(0x99))); - - // ========== Verify initial state exists ========== - // Verify block 50 data exists - let mut cursor_initial = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - assert!( - cursor_initial.seek_exact(initial_branch_path)?.is_some(), - "Initial branch should exist before replace" - ); - - // Verify block 101 old data exists - let mut cursor_old = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - assert!( - cursor_old.seek_exact(old_branch_path)?.is_some(), - "Old branch at block 101 should exist before replace" - ); - - let mut account_cursor_old = - storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; - assert!( - account_cursor_old.seek(old_account_addr)?.is_some(), - "Old account at block 101 should exist before replace" - ); - - // ========== Call replace_updates to replace blocks after 100 ========== - let mut blocks_to_add: Vec<(BlockWithParent, BlockStateDiff)> = Vec::default(); - - // New data for block 101 - let new_account_addr = B256::repeat_byte(0x40); - let new_account = create_test_account_with_values(5, 5000, 0xCC); - - let new_storage_addr = B256::repeat_byte(0x50); - let new_storage_slot = B256::repeat_byte(0x02); - let new_storage_value = U256::from(999); - - let new_branch_path = nibbles_from(vec![10, 11, 12]); - let new_branch = create_test_branch_variant(); - - let storage_branch_path = nibbles_from(vec![5, 5]); - let storage_hashed_addr = B256::repeat_byte(0x60); - - let mut new_trie_updates = TrieUpdates::default(); - new_trie_updates.account_nodes.insert(new_branch_path, new_branch.clone()); - - // Add storage trie updates - let mut storage_trie = StorageTrieUpdates::default(); - storage_trie.storage_nodes.insert(storage_branch_path, new_branch.clone()); - new_trie_updates.insert_storage_updates(storage_hashed_addr, storage_trie); - - let mut new_post_state = HashedPostState::default(); - new_post_state.accounts.insert(new_account_addr, Some(new_account)); - - let mut new_storage = HashedStorage::new(false); - new_storage.storage.insert(new_storage_slot, new_storage_value); - new_post_state.storages.insert(new_storage_addr, new_storage); - - blocks_to_add.push(( - block_ref_101, - BlockStateDiff { - sorted_trie_updates: new_trie_updates.into_sorted(), - sorted_post_state: new_post_state.into_sorted(), - }, - )); - - // New data for block 102 - let block_102_account_addr = B256::repeat_byte(0x70); - let block_102_account = create_test_account_with_values(10, 10000, 0xDD); - - let mut trie_updates_102 = TrieUpdates::default(); - let block_102_branch_path = nibbles_from(vec![15, 14, 13]); - trie_updates_102.account_nodes.insert(block_102_branch_path, new_branch); - - let mut post_state_102 = HashedPostState::default(); - post_state_102.accounts.insert(block_102_account_addr, Some(block_102_account)); - - blocks_to_add.push(( - block_ref_102, - BlockStateDiff { - sorted_trie_updates: trie_updates_102.into_sorted(), - sorted_post_state: post_state_102.into_sorted(), - }, - )); - - // Execute replace_updates - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.replace_updates(BlockNumHash::new(100, block_ref_100.block.hash), blocks_to_add)?; - provider_rw.commit()?; - // ========== Verify that data up to block 100 still exists ========== - let mut cursor_50 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - assert!( - cursor_50.seek_exact(initial_branch_path)?.is_some(), - "Block 50 branch should still exist after replace" - ); - - let mut cursor_100 = storage.provider_ro().expect("provider ro").account_trie_cursor(100)?; - assert!( - cursor_100.seek_exact(common_branch_path)?.is_some(), - "Block 100 branch should still exist after replace" - ); - - let mut storage_cursor_100 = storage - .provider_ro() - .expect("provider ro") - .storage_hashed_cursor(initial_storage_addr, 100)?; - let result_100 = storage_cursor_100.seek(initial_storage_slot)?; - assert!(result_100.is_some(), "Block 100 storage should still exist after replace"); - assert_eq!( - result_100.unwrap().1, - initial_storage_value, - "Block 100 storage value should be unchanged" - ); - - // ========== Verify that old data after block 100 is gone ========== - let mut cursor_old_gone = - storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - assert!( - cursor_old_gone.seek_exact(old_branch_path)?.is_none(), - "Old branch at block 101 should be removed after replace" - ); - - let mut account_cursor_old_gone = - storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; - let old_acc_result = account_cursor_old_gone.seek(old_account_addr)?; - assert!( - old_acc_result.is_none() || old_acc_result.unwrap().0 != old_account_addr, - "Old account at block 101 should be removed after replace" - ); - - // ========== Verify new data is properly accessible via cursors ========== - - // Verify new account branch nodes - let mut trie_cursor = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - let branch_result = trie_cursor.seek_exact(new_branch_path)?; - assert!(branch_result.is_some(), "New account branch should be accessible via cursor"); - assert_eq!(branch_result.unwrap().0, new_branch_path); - - // Verify new storage branch nodes - let mut storage_trie_cursor = storage - .provider_ro() - .expect("provider ro") - .storage_trie_cursor(storage_hashed_addr, 150)?; - let storage_branch_result = storage_trie_cursor.seek_exact(storage_branch_path)?; - assert!(storage_branch_result.is_some(), "New storage branch should be accessible via cursor"); - assert_eq!(storage_branch_result.unwrap().0, storage_branch_path); - - // Verify new hashed accounts - let mut account_cursor = - storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; - let account_result = account_cursor.seek(new_account_addr)?; - assert!(account_result.is_some(), "New account should be accessible via cursor"); - assert_eq!(account_result.as_ref().unwrap().0, new_account_addr); - assert_eq!(account_result.as_ref().unwrap().1.nonce, new_account.nonce); - assert_eq!(account_result.as_ref().unwrap().1.balance, new_account.balance); - assert_eq!(account_result.as_ref().unwrap().1.bytecode_hash, new_account.bytecode_hash); - - // Verify new hashed storages - let mut storage_cursor = - storage.provider_ro().expect("provider ro").storage_hashed_cursor(new_storage_addr, 150)?; - let storage_result = storage_cursor.seek(new_storage_slot)?; - assert!(storage_result.is_some(), "New storage should be accessible via cursor"); - assert_eq!(storage_result.as_ref().unwrap().0, new_storage_slot); - assert_eq!(storage_result.as_ref().unwrap().1, new_storage_value); - - // Verify block 102 data - let mut trie_cursor_102 = - storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - let branch_result_102 = trie_cursor_102.seek_exact(block_102_branch_path)?; - assert!(branch_result_102.is_some(), "Block 102 branch should be accessible"); - assert_eq!(branch_result_102.unwrap().0, block_102_branch_path); - - let mut account_cursor_102 = - storage.provider_ro().expect("provider ro").account_hashed_cursor(150)?; - let account_result_102 = account_cursor_102.seek(block_102_account_addr)?; - assert!(account_result_102.is_some(), "Block 102 account should be accessible"); - assert_eq!(account_result_102.as_ref().unwrap().0, block_102_account_addr); - assert_eq!(account_result_102.as_ref().unwrap().1.nonce, block_102_account.nonce); - - // Verify fetch_trie_updates returns the new data - let provider_rw = storage.provider_rw().expect("provider ro"); - let fetched_101 = provider_rw.fetch_trie_updates(101)?; - provider_rw.commit()?; - assert_eq!( - fetched_101.sorted_trie_updates.account_nodes_ref().len(), - 1, - "Should have 1 account branch node at block 101" - ); - assert!( - fetched_101 - .sorted_trie_updates - .account_nodes_ref() - .iter() - .any(|(addr, _)| *addr == new_branch_path), - "New branch path should be in trie_updates" - ); - assert_eq!( - fetched_101.sorted_post_state.accounts.len(), - 1, - "Should have 1 account at block 101" - ); - assert!( - fetched_101.sorted_post_state.accounts.iter().any(|(addr, _)| *addr == new_account_addr), - "New account should be in post_state" - ); - - Ok(()) -} - -/// Test that pure deletions (nodes only in `removed_nodes`) are properly stored -/// -/// This test verifies that when a node appears only in `removed_nodes` (not in updates), -/// it is properly stored as a deletion and subsequent queries return None for that path. -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_pure_deletions_stored_correctly( - storage: S, -) -> Result<(), OpProofsStorageError> { - use reth_trie::updates::StorageTrieUpdates; - - // ========== Setup: Store initial branch nodes at block 50 ========== - let account_path1 = nibbles_from(vec![1, 2, 3]); - let account_path2 = nibbles_from(vec![4, 5, 6]); - let storage_path1 = nibbles_from(vec![7, 8, 9]); - let storage_path2 = nibbles_from(vec![10, 11, 12]); - let storage_address = B256::repeat_byte(0x42); - - let initial_branch = create_test_branch(); - - let mut initial_trie_updates = TrieUpdates::default(); - initial_trie_updates.account_nodes.insert(account_path1, initial_branch.clone()); - initial_trie_updates.account_nodes.insert(account_path2, initial_branch.clone()); - - let mut storage_trie = StorageTrieUpdates::default(); - storage_trie.storage_nodes.insert(storage_path1, initial_branch.clone()); - storage_trie.storage_nodes.insert(storage_path2, initial_branch); - initial_trie_updates.insert_storage_updates(storage_address, storage_trie); - - let initial_diff = BlockStateDiff { - sorted_trie_updates: initial_trie_updates.into_sorted(), - sorted_post_state: HashedPostStateSorted::default(), - }; - - let block_ref_50 = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); - - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_50, initial_diff)?; - provider_rw.commit()?; - - // Verify initial state exists at block 75 - let mut cursor_75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - assert!( - cursor_75.seek_exact(account_path1)?.is_some(), - "Initial account branch 1 should exist at block 75" - ); - assert!( - cursor_75.seek_exact(account_path2)?.is_some(), - "Initial account branch 2 should exist at block 75" - ); - - let mut storage_cursor_75 = - storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 75)?; - assert!( - storage_cursor_75.seek_exact(storage_path1)?.is_some(), - "Initial storage branch 1 should exist at block 75" - ); - assert!( - storage_cursor_75.seek_exact(storage_path2)?.is_some(), - "Initial storage branch 2 should exist at block 75" - ); - - // ========== At block 100: Mark paths as deleted (ONLY in removed_nodes) ========== - let mut deletion_trie_updates = TrieUpdates::default(); - - // Add to removed_nodes ONLY (no updates) - deletion_trie_updates.removed_nodes.insert(account_path1); - - // Do the same for storage branch - let mut deletion_storage_trie = StorageTrieUpdates::default(); - deletion_storage_trie.removed_nodes.insert(storage_path1); - deletion_trie_updates.insert_storage_updates(storage_address, deletion_storage_trie); - - let deletion_diff = BlockStateDiff { - sorted_trie_updates: deletion_trie_updates.into_sorted(), - sorted_post_state: HashedPostStateSorted::default(), - }; - - let block_ref_100 = - BlockWithParent::new(B256::repeat_byte(0x96), NumHash::new(100, B256::repeat_byte(0x97))); - - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_100, deletion_diff)?; - provider_rw.commit()?; - - // ========== Verify that deleted nodes return None at block 150 ========== - - // Deleted account branch should not be found - let mut cursor_150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - let account_result = cursor_150.seek_exact(account_path1)?; - assert!(account_result.is_none(), "Deleted account branch should return None at block 150"); - - // Non-deleted account branch should still exist - let account_result2 = cursor_150.seek_exact(account_path2)?; - assert!( - account_result2.is_some(), - "Non-deleted account branch should still exist at block 150" - ); - - // Deleted storage branch should not be found - let mut storage_cursor_150 = - storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 150)?; - let storage_result = storage_cursor_150.seek_exact(storage_path1)?; - assert!(storage_result.is_none(), "Deleted storage branch should return None at block 150"); - - // Non-deleted storage branch should still exist - let storage_result2 = storage_cursor_150.seek_exact(storage_path2)?; - assert!( - storage_result2.is_some(), - "Non-deleted storage branch should still exist at block 150" - ); - - // ========== Verify that the nodes still exist at block 75 (before deletion) ========== - let mut cursor_75_after = - storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - assert!( - cursor_75_after.seek_exact(account_path1)?.is_some(), - "Deleted node should still exist at block 75 (before deletion)" - ); - - let mut storage_cursor_75_after = - storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 75)?; - assert!( - storage_cursor_75_after.seek_exact(storage_path1)?.is_some(), - "Deleted storage node should still exist at block 75 (before deletion)" - ); - - // ========== Verify iteration skips deleted nodes ========== - let mut cursor_iter = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - let mut found_paths = Vec::new(); - while let Some((path, _)) = cursor_iter.next()? { - found_paths.push(path); - } - - assert!(!found_paths.contains(&account_path1), "Iteration should skip deleted node"); - assert!(found_paths.contains(&account_path2), "Iteration should include non-deleted node"); - - Ok(()) -} - -/// Test that updates take precedence over removals when both are present -/// -/// This test verifies that when a path appears in both `removed_nodes` and `account_nodes`, -/// the update from `account_nodes` takes precedence. This is critical for correctness -/// when processing trie updates that both remove and update the same node. -#[test_case(InMemoryProofsStorage::new(); "InMemory")] -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_updates_take_precedence_over_removals( - storage: S, -) -> Result<(), OpProofsStorageError> { - use reth_trie::updates::StorageTrieUpdates; - - // ========== Setup: Store initial branch nodes at block 50 ========== - let account_path = nibbles_from(vec![1, 2, 3]); - let storage_path = nibbles_from(vec![4, 5, 6]); - let storage_address = B256::repeat_byte(0x42); - - let initial_branch = create_test_branch(); - - let mut initial_trie_updates = TrieUpdates::default(); - initial_trie_updates.account_nodes.insert(account_path, initial_branch.clone()); - - let mut storage_trie = StorageTrieUpdates::default(); - storage_trie.storage_nodes.insert(storage_path, initial_branch.clone()); - initial_trie_updates.insert_storage_updates(storage_address, storage_trie); - - let initial_diff = BlockStateDiff { - sorted_trie_updates: initial_trie_updates.into_sorted(), - sorted_post_state: HashedPostStateSorted::default(), - }; - - let block_ref_50 = BlockWithParent::new(B256::ZERO, NumHash::new(50, B256::repeat_byte(0x96))); - - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_50, initial_diff)?; - provider_rw.commit()?; - - // Verify initial state exists at block 75 - let mut cursor_75 = storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - assert!( - cursor_75.seek_exact(account_path)?.is_some(), - "Initial account branch should exist at block 75" - ); - - let mut storage_cursor_75 = - storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 75)?; - assert!( - storage_cursor_75.seek_exact(storage_path)?.is_some(), - "Initial storage branch should exist at block 75" - ); - - // ========== At block 100: Add paths to BOTH removed_nodes AND account_nodes ========== - // This simulates a scenario where a node is both removed and updated - // The update should take precedence - let updated_branch = create_test_branch_variant(); - - let mut conflicting_trie_updates = TrieUpdates::default(); - - // Add to removed_nodes - conflicting_trie_updates.removed_nodes.insert(account_path); - - // Also add to account_nodes (this should take precedence) - conflicting_trie_updates.account_nodes.insert(account_path, updated_branch.clone()); - - // Do the same for storage branch - let mut conflicting_storage_trie = StorageTrieUpdates::default(); - conflicting_storage_trie.removed_nodes.insert(storage_path); - conflicting_storage_trie.storage_nodes.insert(storage_path, updated_branch.clone()); - conflicting_trie_updates.insert_storage_updates(storage_address, conflicting_storage_trie); - - let conflicting_diff = BlockStateDiff { - sorted_trie_updates: conflicting_trie_updates.into_sorted(), - sorted_post_state: HashedPostStateSorted::default(), - }; - - let block_ref_100 = - BlockWithParent::new(B256::repeat_byte(0x96), NumHash::new(100, B256::repeat_byte(0x97))); - - let provider_rw = storage.provider_rw().expect("provider rw"); - provider_rw.store_trie_updates(block_ref_100, conflicting_diff)?; - provider_rw.commit()?; - - // ========== Verify that updates took precedence at block 150 ========== - - // Account branch should exist (not deleted) with the updated value - let mut cursor_150 = storage.provider_ro().expect("provider ro").account_trie_cursor(150)?; - let account_result = cursor_150.seek_exact(account_path)?; - assert!( - account_result.is_some(), - "Account branch should exist at block 150 (update should take precedence over removal)" - ); - let (found_path, found_branch) = account_result.unwrap(); - assert_eq!(found_path, account_path); - // Verify it's the updated branch, not the initial one - assert_eq!( - found_branch.state_mask, updated_branch.state_mask, - "Account branch should be the updated version, not the initial one" - ); - - // Storage branch should exist (not deleted) with the updated value - let mut storage_cursor_150 = - storage.provider_ro().expect("provider ro").storage_trie_cursor(storage_address, 150)?; - let storage_result = storage_cursor_150.seek_exact(storage_path)?; - assert!( - storage_result.is_some(), - "Storage branch should exist at block 150 (update should take precedence over removal)" - ); - let (found_storage_path, found_storage_branch) = storage_result.unwrap(); - assert_eq!(found_storage_path, storage_path); - // Verify it's the updated branch - assert_eq!( - found_storage_branch.state_mask, updated_branch.state_mask, - "Storage branch should be the updated version, not the initial one" - ); - - // ========== Verify that the old version still exists at block 75 ========== - let mut cursor_75_after = - storage.provider_ro().expect("provider ro").account_trie_cursor(75)?; - let result_75 = cursor_75_after.seek_exact(account_path)?; - assert!(result_75.is_some(), "Initial version should still exist at block 75"); - let (_, branch_75) = result_75.unwrap(); - assert_eq!( - branch_75.state_mask, initial_branch.state_mask, - "Block 75 should see the initial branch, not the updated one" - ); - - Ok(()) -} diff --git a/vendor/reth-optimism-trie/tests/live.rs b/vendor/reth-optimism-trie/tests/live.rs deleted file mode 100644 index 3d3ec99..0000000 --- a/vendor/reth-optimism-trie/tests/live.rs +++ /dev/null @@ -1,633 +0,0 @@ -//! End-to-end test of the live trie collector. - -use alloy_consensus::{BlockHeader, Header, TxEip2930, constants::ETH_TO_WEI}; -use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::{Address, B256, TxKind, U256, keccak256}; -use derive_more::Constructor; -use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, MAINNET, MIN_TRANSACTION_GAS}; -use reth_db::Database; -use reth_db_common::init::init_genesis; -use reth_ethereum_primitives::{Block, BlockBody, Receipt, Transaction, TransactionSigned}; -use reth_evm::{ConfigureEvm, execute::Executor}; -use reth_evm_ethereum::EthEvmConfig; -use reth_node_api::{NodePrimitives, NodeTypesWithDB}; -use reth_optimism_trie::{ - MdbxProofsStorage, MdbxProofsStorageV2, OpProofStoragePruner, OpProofsStorage, OpProofsStore, - RethTrieStorageLayout, - engine::{EngineError, EngineHandle}, - initialize::InitializationJob, -}; -use reth_primitives_traits::{Block as _, RecoveredBlock}; -use reth_provider::{ - BlockWriter as _, ExecutionOutcome, HashedPostStateProvider, LatestStateProviderRef, - ProviderFactory, StateRootProvider, StorageSettingsCache, - providers::{BlockchainProvider, ProviderNodeTypes}, - test_utils::create_test_provider_factory_with_chain_spec, -}; -use reth_revm::database::StateProviderDatabase; -use secp256k1::{Keypair, Secp256k1, rand::rng}; -use serial_test::serial; -use std::sync::Arc; -use tempfile::TempDir; -use test_case::test_case; - -fn create_mdbx_proofs_storage() -> Arc { - let path = TempDir::new().unwrap().keep(); - Arc::new(MdbxProofsStorage::new(&path).unwrap()) -} - -fn create_mdbx_proofs_storage_v2() -> Arc { - let path = TempDir::new().unwrap().keep(); - Arc::new(MdbxProofsStorageV2::new(&path).unwrap()) -} - -/// Converts a secp256k1 public key to an Ethereum address. -fn public_key_to_address(pubkey: secp256k1::PublicKey) -> Address { - let hash = keccak256(&pubkey.serialize_uncompressed()[1..]); - Address::from_slice(&hash[12..]) -} - -/// Signs a transaction with the given keypair. -fn sign_tx_with_key_pair(key_pair: Keypair, tx: Transaction) -> TransactionSigned { - use alloy_consensus::SignableTransaction; - use reth_primitives_traits::crypto::secp256k1::sign_message; - let secret = B256::from_slice(&key_pair.secret_bytes()); - let sig = sign_message(secret, tx.signature_hash()).unwrap(); - tx.into_signed(sig).into() -} - -/// Specification for a transaction within a block -#[derive(Debug, Clone)] -struct TxSpec { - /// Recipient address for the transaction - to: Address, - /// Value to transfer (in wei) - value: U256, - /// Nonce for the transaction (will be automatically assigned if None) - nonce: Option, -} - -impl TxSpec { - /// Create a simple transfer transaction - const fn transfer(to: Address, value: U256) -> Self { - Self { to, value, nonce: None } - } -} - -/// Specification for a block in the test chain -#[derive(Debug, Clone, Constructor)] -struct BlockSpec { - /// Transactions to include in this block - txs: Vec, -} - -/// Configuration for a test scenario -#[derive(Debug, Constructor)] -struct TestScenario { - /// Blocks to execute before running the initialization job - blocks_before_initialization: Vec, - /// Blocks to execute after initialization using the live collector - blocks_after_initialization: Vec, -} - -/// Helper to create a chain spec with a genesis account funded -fn chain_spec_with_address(address: Address) -> Arc { - Arc::new( - ChainSpecBuilder::default() - .chain(MAINNET.chain) - .genesis(Genesis { - alloc: [( - address, - GenesisAccount { balance: U256::from(10 * ETH_TO_WEI), ..Default::default() }, - )] - .into(), - ..MAINNET.genesis.clone() - }) - .paris_activated() - .build(), - ) -} - -/// Creates a block from a spec, executing transactions with the given keypair -fn create_block_from_spec( - spec: &BlockSpec, - block_number: u64, - parent_hash: B256, - chain_spec: &Arc, - key_pair: Keypair, - nonce_counter: &mut u64, -) -> RecoveredBlock { - let transactions: Vec = spec - .txs - .iter() - .map(|tx_spec| { - let nonce = tx_spec.nonce.unwrap_or_else(|| { - let current = *nonce_counter; - *nonce_counter += 1; - current - }); - - sign_tx_with_key_pair( - key_pair, - TxEip2930 { - chain_id: chain_spec.chain.id(), - nonce, - gas_limit: MIN_TRANSACTION_GAS, - gas_price: 1_500_000_000, - to: TxKind::Call(tx_spec.to), - value: tx_spec.value, - ..Default::default() - } - .into(), - ) - }) - .collect(); - - let gas_total = transactions.len() as u64 * MIN_TRANSACTION_GAS; - - Block { - header: Header { - parent_hash, - receipts_root: alloy_primitives::b256!( - "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" - ), - difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), - number: block_number, - gas_limit: gas_total.max(MIN_TRANSACTION_GAS), - gas_used: gas_total, - state_root: B256::ZERO, // Will be calculated by executor - ..Default::default() - }, - body: BlockBody { transactions, ..Default::default() }, - } - .try_into_recovered() - .unwrap() -} - -/// Executes a block and returns the updated block with correct state root -fn execute_block( - block: &mut RecoveredBlock, - provider_factory: &ProviderFactory, - chain_spec: &Arc, -) -> eyre::Result> -where - N: ProviderNodeTypes< - Primitives: NodePrimitives, - > + NodeTypesWithDB, -{ - let provider = provider_factory.provider()?; - let db = StateProviderDatabase::new(LatestStateProviderRef::new(&provider)); - let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); - let block_executor = evm_config.batch_executor(db); - - let execution_result = block_executor.execute(block)?; - - let hashed_state = - LatestStateProviderRef::new(&provider).hashed_post_state(&execution_result.state); - let state_root = LatestStateProviderRef::new(&provider).state_root(hashed_state)?; - - block.set_state_root(state_root); - - Ok(execution_result) -} - -/// Commits a block and its execution output to the database -fn commit_block_to_database( - block: &RecoveredBlock, - execution_output: &reth_evm::execute::BlockExecutionOutput, - provider_factory: &ProviderFactory, -) -> eyre::Result<()> -where - N: ProviderNodeTypes< - Primitives: NodePrimitives, - > + NodeTypesWithDB, -{ - let execution_outcome = ExecutionOutcome { - bundle: execution_output.state.clone(), - receipts: vec![execution_output.receipts.clone()], - first_block: block.number(), - requests: vec![execution_output.requests.clone()], - }; - - // Calculate hashed state from execution result - let state_provider = provider_factory.provider()?; - let hashed_state = HashedPostStateProvider::hashed_post_state( - &LatestStateProviderRef::new(&state_provider), - &execution_output.state, - ); - - let provider_rw = provider_factory.provider_rw()?; - provider_rw.append_blocks_with_state( - vec![block.clone()], - &execution_outcome, - hashed_state.into_sorted(), - )?; - provider_rw.commit()?; - - Ok(()) -} - -/// Runs a test scenario with the given configuration -fn run_test_scenario( - scenario: TestScenario, - provider_factory: ProviderFactory, - chain_spec: Arc, - key_pair: Keypair, - raw_storage: S, -) -> eyre::Result<()> -where - N: ProviderNodeTypes< - Primitives: NodePrimitives, - > + NodeTypesWithDB, - S: OpProofsStore + Send + Sync + Clone + std::fmt::Debug + 'static, -{ - let storage: OpProofsStorage = raw_storage.into(); - let genesis_hash = chain_spec.genesis_hash(); - let mut nonce_counter = 0u64; - let mut last_block_hash = genesis_hash; - let mut last_block_number = 0u64; - - // Execute blocks before initialization - for (idx, block_spec) in scenario.blocks_before_initialization.iter().enumerate() { - let block_number = idx as u64 + 1; - let mut block = create_block_from_spec( - block_spec, - block_number, - last_block_hash, - &chain_spec, - key_pair, - &mut nonce_counter, - ); - - let execution_output = execute_block(&mut block, &provider_factory, &chain_spec)?; - commit_block_to_database(&block, &execution_output, &provider_factory)?; - - last_block_hash = block.hash(); - last_block_number = block_number; - } - - { - let trie_layout = if provider_factory.cached_storage_settings().is_v2() { - RethTrieStorageLayout::Packed - } else { - RethTrieStorageLayout::Legacy - }; - let provider = provider_factory.db_ref(); - let tx = provider.tx()?; - let initialization_job = InitializationJob::new(storage.clone(), tx, trie_layout); - initialization_job.run(last_block_number, last_block_hash)?; - } - - // Execute blocks after initialization using live collector. - // A single collector is shared across all blocks so the in-memory buffer accumulates - // state between iterations (the new async-persistence architecture requires this). - let evm_config = EthEvmConfig::ethereum(chain_spec.clone()); - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; - let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); - let engine_handle = EngineHandle::spawn(evm_config, blockchain_db, storage, pruner); - - for (idx, block_spec) in scenario.blocks_after_initialization.iter().enumerate() { - let block_number = last_block_number + idx as u64 + 1; - let mut block = create_block_from_spec( - block_spec, - block_number, - last_block_hash, - &chain_spec, - key_pair, - &mut nonce_counter, - ); - - // Execute the block to get the correct state root - let execution_output = execute_block(&mut block, &provider_factory, &chain_spec)?; - - // Use the live collector to execute and store trie updates - engine_handle.execute_block(&block)?; - - // Commit the block to the database so subsequent blocks can build on it - commit_block_to_database(&block, &execution_output, &provider_factory)?; - - last_block_hash = block.hash(); - } - - // Drain any pending in-memory blocks to disk before returning. - engine_handle.flush(); - - Ok(()) -} - -/// End-to-end test of a single live collector iteration. -/// (1) Creates a chain with some state -/// (2) Stores the genesis state into storage via initialization -/// (3) Executes a block and calculates the state root using the stored state -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_execute_and_store_block_updates(storage: S) -> Result<(), eyre::Error> -where - S: OpProofsStore + Clone + Send + Sync + std::fmt::Debug + 'static, -{ - // Create a keypair for signing transactions - let secp = Secp256k1::new(); - let key_pair = Keypair::new(&secp, &mut rng()); - let sender = public_key_to_address(key_pair.public_key()); - - // Create chain spec with the sender address funded in genesis - let chain_spec = chain_spec_with_address(sender); - - // Create test database and provider factory - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - - // Insert genesis state into the database - init_genesis(&provider_factory).unwrap(); - - // Define the test scenario: - // - No blocks before initialization - // - Initialization to genesis (block 0) - // - Execute one block with a single transaction after initialization - let recipient = Address::repeat_byte(0x42); - let scenario = TestScenario::new( - vec![], - vec![BlockSpec::new(vec![TxSpec::transfer(recipient, U256::from(1))])], - ); - - run_test_scenario(scenario, provider_factory, chain_spec, key_pair, storage) -} - -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_execute_and_store_block_updates_missing_parent_block( - storage: S, -) -> Result<(), eyre::Error> -where - S: OpProofsStore + Clone + Send + Sync + std::fmt::Debug + 'static, -{ - let secp = Secp256k1::new(); - let key_pair = Keypair::new(&secp, &mut rng()); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - // No blocks before initialization; initialization only inserts genesis. - let scenario = TestScenario::new(vec![], vec![]); - - // Run initialization (block 0 only) - run_test_scenario( - scenario, - provider_factory.clone(), - chain_spec.clone(), - key_pair, - storage.clone(), - )?; - - // Create a block that is sequential but has a wrong parent hash. - let incorrect_block_number = 1; - let incorrect_parent_hash = B256::repeat_byte(0x11); - - let mut nonce_counter = 0; - let incorrect_block = create_block_from_spec( - &BlockSpec::new(vec![]), - incorrect_block_number, - incorrect_parent_hash, - &chain_spec, - key_pair, - &mut nonce_counter, - ); - - let blockchain_db = BlockchainProvider::new(provider_factory).unwrap(); - let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); - let engine_handle = EngineHandle::spawn( - EthEvmConfig::ethereum(chain_spec.clone()), - blockchain_db, - storage, - pruner, - ); - - // EXPECT: ParentHashMismatch (block is at correct number but wrong parent hash) - let err = engine_handle.execute_block(&incorrect_block).unwrap_err(); - - assert!(matches!(err, EngineError::ParentHashMismatch { .. })); - Ok(()) -} - -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_execute_and_store_block_updates_state_root_mismatch( - storage: Arc, -) -> Result<(), eyre::Error> -where - S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, -{ - let secp = Secp256k1::new(); - let key_pair = Keypair::new(&secp, &mut rng()); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - // Run normal scenario: no blocks before initialization, one block after. - let recipient = Address::repeat_byte(0x42); - let scenario = TestScenario::new( - vec![], - vec![BlockSpec::new(vec![TxSpec::transfer(recipient, U256::from(1))])], - ); - - run_test_scenario( - scenario, - provider_factory.clone(), - chain_spec.clone(), - key_pair, - storage.clone(), - )?; - - // Generate a second block normally - let blockchain_db = BlockchainProvider::new(provider_factory.clone()).unwrap(); - let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); - let engine_handle = EngineHandle::spawn( - EthEvmConfig::ethereum(chain_spec.clone()), - blockchain_db, - storage, - pruner, - ); - - // Create the next block — sequential after genesis so the parent hash check passes - // and execution runs, allowing us to verify the state root mismatch path. - let mut nonce_counter = 0; - let last_block_hash = chain_spec.genesis_hash(); - let next_number = 1; - - let mut block = create_block_from_spec( - &BlockSpec::new(vec![]), - next_number, - last_block_hash, - &chain_spec, - key_pair, - &mut nonce_counter, - ); - - // Execute it to compute a correct state root - let _ = execute_block(&mut block, &provider_factory, &chain_spec).unwrap(); - - // Change the state root to induce the error - block.header_mut().state_root = B256::repeat_byte(0xAA); - - // EXPECT: StateRootMismatch - let err = engine_handle.execute_block(&block).unwrap_err(); - - assert!(matches!(err, EngineError::StateRootMismatch { .. })); - Ok(()) -} - -/// Negative test for the body-pruned detection in the engine's execute-block path. -/// The check sits in `tasks::execute_block::run` so it covers both the sync path -/// (`advance_sync` calls into this task) and any direct `EngineHandle::execute_block` -/// caller; this test exercises the latter. -/// -/// Reth returns `Some(block)` with an empty body when the transaction data has been pruned but -/// body indices are still present. Executing that block would produce zero state changes and -/// surface a misleading [`EngineError::StateRootMismatch`] instead of the real cause. The engine -/// detects this and returns [`EngineError::BlockBodyPruned`] instead. -/// -/// We exercise this by constructing a block whose header advertises a non-empty -/// `transactions_root` (i.e. originally contained transactions) but whose body has been -/// stripped — exactly what `recovered_block` would return for a body-pruned block. -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_execute_and_store_block_updates_body_pruned(storage: Arc) -> Result<(), eyre::Error> -where - S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, -{ - let secp = Secp256k1::new(); - let key_pair = Keypair::new(&secp, &mut rng()); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - // Initialize storage at genesis so the engine's tip = genesis. - run_test_scenario( - TestScenario::new(vec![], vec![]), - provider_factory.clone(), - chain_spec.clone(), - key_pair, - storage.clone(), - )?; - - let blockchain_db = BlockchainProvider::new(provider_factory.clone()).unwrap(); - let pruner = OpProofStoragePruner::new(storage.clone(), blockchain_db.clone(), 1000); - let engine_handle = EngineHandle::spawn( - EthEvmConfig::ethereum(chain_spec.clone()), - blockchain_db, - storage, - pruner, - ); - - // Build block 1 normally and execute it so the header carries the real post-state - // values for that block. - let recipient = Address::repeat_byte(0x42); - let mut nonce_counter = 0u64; - let mut block_with_txs = create_block_from_spec( - &BlockSpec::new(vec![TxSpec::transfer(recipient, U256::from(1))]), - 1, - chain_spec.genesis_hash(), - &chain_spec, - key_pair, - &mut nonce_counter, - ); - let _ = execute_block(&mut block_with_txs, &provider_factory, &chain_spec)?; - - // Synthesize a body-pruned block: keep the executed header but rewrite its - // `transactions_root` to a non-canonical-empty value (mimicking a real header for a - // block that originally had transactions) and strip the body. - let mut header = block_with_txs.header().clone(); - header.transactions_root = B256::repeat_byte(0xAB); - let pruned = Block { header, body: BlockBody::default() }.try_into_recovered().unwrap(); - - // EXPECT: BlockBodyPruned at block 1 - let err = engine_handle.execute_block(&pruned).unwrap_err(); - assert!( - matches!(err, EngineError::BlockBodyPruned(1)), - "expected BlockBodyPruned(1), got {err:?}" - ); - Ok(()) -} - -/// Test with multiple blocks before and after initialization -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_multiple_blocks_before_and_after_initialization( - storage: Arc, -) -> Result<(), eyre::Error> -where - S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, -{ - let secp = Secp256k1::new(); - let key_pair = Keypair::new(&secp, &mut rng()); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - // Define the test scenario: - // - Execute 3 blocks before initialization (will be committed to db) - // - Initialization to block 3 - // - Execute 2 more blocks using the live collector - let recipient1 = Address::repeat_byte(0x42); - let recipient2 = Address::repeat_byte(0x43); - let recipient3 = Address::repeat_byte(0x44); - - let scenario = TestScenario::new( - vec![ - BlockSpec::new(vec![TxSpec::transfer(recipient1, U256::from(1))]), - BlockSpec::new(vec![TxSpec::transfer(recipient2, U256::from(2))]), - BlockSpec::new(vec![TxSpec::transfer(recipient3, U256::from(3))]), - ], - vec![ - BlockSpec::new(vec![TxSpec::transfer(recipient1, U256::from(4))]), - BlockSpec::new(vec![TxSpec::transfer(recipient2, U256::from(5))]), - ], - ); - - run_test_scenario(scenario, provider_factory, chain_spec, key_pair, storage) -} - -/// Test with blocks containing multiple transactions -#[test_case(create_mdbx_proofs_storage(); "Mdbx")] -#[test_case(create_mdbx_proofs_storage_v2(); "MdbxV2")] -#[serial] -fn test_blocks_with_multiple_transactions(storage: Arc) -> Result<(), eyre::Error> -where - S: OpProofsStore + Send + Sync + std::fmt::Debug + 'static, -{ - let secp = Secp256k1::new(); - let key_pair = Keypair::new(&secp, &mut rng()); - let sender = public_key_to_address(key_pair.public_key()); - - let chain_spec = chain_spec_with_address(sender); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(&provider_factory).unwrap(); - - let recipient1 = Address::repeat_byte(0x42); - let recipient2 = Address::repeat_byte(0x43); - let recipient3 = Address::repeat_byte(0x44); - - // Block with 3 transactions - let scenario = TestScenario::new( - vec![], - vec![BlockSpec::new(vec![ - TxSpec::transfer(recipient1, U256::from(1)), - TxSpec::transfer(recipient2, U256::from(2)), - TxSpec::transfer(recipient3, U256::from(3)), - ])], - ); - - run_test_scenario(scenario, provider_factory, chain_spec, key_pair, storage) -} From 796f2b3ba2a9be3a6c8747bc3dbefc7b5ae94237 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 14:54:28 +0800 Subject: [PATCH 14/28] build(deps): bump revmc to 520462a4, reth f2eecc6's locked revision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picks up two compiled-code correctness fixes over 56ed0063: revmc#394 (sync stack for diverging builtins) and revmc#400 (account LOG memory operands in the gas analysis), both fixing compiled execution that diverges from the interpreter — consensus-relevant with JIT enabled for canonical execution and payload building. Also includes revmc#403 (native-only LLVM target init). A clean three-commit fast-forward; the pin now matches the revmc revision reth f2eecc6 locks. Verified with just fmt, just clippy, and just test (247/247); the JIT/interpreter differential tests exercise real in-process compilation against the new revision. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d03358c..573841b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9912,7 +9912,7 @@ dependencies = [ [[package]] name = "revmc" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -9932,7 +9932,7 @@ dependencies = [ [[package]] name = "revmc-backend" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "eyre", "ruint", @@ -9941,12 +9941,12 @@ dependencies = [ [[package]] name = "revmc-build" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" [[package]] name = "revmc-builtins" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "paste", "revm-bytecode", @@ -9961,7 +9961,7 @@ dependencies = [ [[package]] name = "revmc-codegen" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "alloy-primitives", "bitflags", @@ -9992,7 +9992,7 @@ dependencies = [ [[package]] name = "revmc-context" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "revm-context", "revm-context-interface", @@ -10007,7 +10007,7 @@ dependencies = [ [[package]] name = "revmc-llvm" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "alloy-primitives", "cc", @@ -10020,7 +10020,7 @@ dependencies = [ [[package]] name = "revmc-runtime" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revmc?rev=56ed00631c4542507f7198bdc9d53dd6471a2ef8#56ed00631c4542507f7198bdc9d53dd6471a2ef8" +source = "git+https://github.com/paradigmxyz/revmc?rev=520462a463523a3bcd0a47226ddbc3200d62232e#520462a463523a3bcd0a47226ddbc3200d62232e" dependencies = [ "alloy-primitives", "crossbeam-channel", diff --git a/Cargo.toml b/Cargo.toml index 60f67b9..4dbfb36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,11 +158,11 @@ reth-optimism-trie = { git = "https://github.com/ethereum-optimism/optimism", re # revm revm-database-interface = { version = "41.0.0", default-features = false } -# revmc JIT: pinned to the fast-forward of reth v2.4.0's locked revision (7e3536d) that adds -# revmc#395 (dynamic-gas failure ordering). revmc#391 (non-blocking runtime controls) is -# already included. reth f2eecc6 locks a newer revmc (520462a4); bump ours deliberately — -# the JIT integration needs revalidation — rather than in lockstep with the reth pin. -revmc = { git = "https://github.com/paradigmxyz/revmc", rev = "56ed00631c4542507f7198bdc9d53dd6471a2ef8", default-features = false, features = [ +# revmc JIT: pinned to the revision reth f2eecc6 locks. Includes revmc#391 (non-blocking +# runtime controls), #395 (dynamic-gas failure ordering), #394 (stack sync for diverging +# builtins), and #400 (LOG memory operands in gas analysis) — the latter two fix compiled +# code diverging from the interpreter. Keep matched to the reth pin's locked revmc. +revmc = { git = "https://github.com/paradigmxyz/revmc", rev = "520462a463523a3bcd0a47226ddbc3200d62232e", default-features = false, features = [ "llvm-prefer-static", ] } From d37f57b392eb42507b1b633ae1376abaf46aeac5 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 15:14:10 +0800 Subject: [PATCH 15/28] fix(cli): honor JIT debug dumps on re-execute --- crates/cli/src/lib.rs | 57 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 8296d2a..c27044a 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -4,10 +4,12 @@ use std::{fmt, path::PathBuf, sync::Arc, time::Duration}; use alloy_consensus::Header; -use clap::Parser; +use clap::{CommandFactory, FromArgMatches}; use reth::{ CliRunner, + args::DatadirArgs, cli::{Cli, Commands}, + dirs::{DataDirPath, MaybePlatformPath}, prometheus_exporter::install_prometheus_recorder, }; use reth_cli::chainspec::ChainSpecParser; @@ -134,6 +136,8 @@ pub struct TaikoCli< > { /// Wrapped `reth` CLI structure containing parsed commands and global options. pub inner: Cli, + /// Parsed data directory for the re-execute command, retained for JIT debug dump placement. + reexecute_datadir: Option>, } impl TaikoCli @@ -143,7 +147,7 @@ where { /// Parsers only the default CLI arguments pub fn parse_args() -> Self { - Self { inner: Cli::::parse() } + Self::try_parse_args_from(std::env::args_os()).unwrap_or_else(|err| err.exit()) } /// Parsers only the default CLI arguments from the given iterator @@ -152,7 +156,14 @@ where I: IntoIterator, T: Into + Clone, { - Cli::::try_parse_from(itr).map(|inner| Self { inner }) + let mut matches = Cli::::command().try_get_matches_from(itr)?; + let reexecute_datadir = matches + .subcommand_matches("re-execute") + .and_then(|matches| matches.get_one::>("datadir")) + .cloned(); + let inner = Cli::::from_arg_matches_mut(&mut matches)?; + + Ok(Self { inner, reexecute_datadir }) } } @@ -161,6 +172,24 @@ impl< Ext: clap::Args + fmt::Debug + TaikoNodeExtArgs, > TaikoCli { + /// Returns the JIT compiler dump directory requested by the re-execute command. + fn reexecute_jit_dump_dir(&self) -> Option { + let Commands::ReExecute(command) = &self.inner.command else { + return None; + }; + if !command.jit.debug { + return None; + } + + let chain = command.chain_spec()?.inner.chain; + let datadir = DatadirArgs { + datadir: self.reexecute_datadir.clone().unwrap_or_default(), + ..Default::default() + } + .resolve_datadir(chain); + Some(datadir.data_dir().join("jit")) + } + /// Execute the configured cli command. /// /// This accepts a closure that is used to launch the node via the @@ -211,6 +240,7 @@ impl< // Install the prometheus recorder to be sure to record all metrics let _ = install_prometheus_recorder(); let rt = runner.runtime(); + let reexecute_jit_dump_dir = self.reexecute_jit_dump_dir(); let components = |spec: Arc| { let evm = TaikoEvmConfig::new(spec.clone()); @@ -266,7 +296,8 @@ impl< .chain_spec() .cloned() .ok_or_else(|| eyre::eyre!("re-execute requires a chain spec"))?; - let evm = evm_config_from_jit_args(chain_spec, &command.jit, None)?; + let evm = + evm_config_from_jit_args(chain_spec, &command.jit, reexecute_jit_dump_dir)?; let components = move |spec: Arc| { let block_reader = Arc::new(ProviderTaikoBlockReader(NoopProvider::< TaikoChainSpec, @@ -304,7 +335,8 @@ mod tests { use super::{ DEFAULT_PROOF_HISTORY_MAX_STARTUP_PRUNE_BLOCKS, - DEFAULT_PROOF_HISTORY_VERIFICATION_INTERVAL, DEFAULT_PROOF_HISTORY_WINDOW, TaikoCliExtArgs, + DEFAULT_PROOF_HISTORY_VERIFICATION_INTERVAL, DEFAULT_PROOF_HISTORY_WINDOW, + TaikoChainSpecParser, TaikoCli, TaikoCliExtArgs, }; use crate::command::TaikoNodeExtArgs; @@ -338,6 +370,21 @@ mod tests { assert_eq!(cli.ext.devnet_unzen_timestamp, 0); } + #[test] + fn test_reexecute_jit_debug_resolves_dump_dir_from_datadir() { + let cli = TaikoCli::::try_parse_args_from([ + "alethia-reth", + "re-execute", + "--datadir", + "/tmp/alethia-reth", + "--jit", + "--jit.debug", + ]) + .expect("re-execute JIT arguments should parse"); + + assert_eq!(cli.reexecute_jit_dump_dir(), Some(PathBuf::from("/tmp/alethia-reth/jit"))); + } + #[test] fn test_parse_devnet_unzen_timestamp_from_env() { let _lock = env_lock(); From 42b26ae2dae6d23fd0cb3270b4ef1de74ef6cce4 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 15 Jul 2026 16:16:22 +0800 Subject: [PATCH 16/28] refactor: simplify consensus error helper and live-collector test fixture - Replace the Taiko-local TaikoConsensusMessage newtype + other_consensus_error helper with reth's built-in ConsensusError::msg (identical Other(MessageError) wrapping); 11 call sites across consensus validation/{mod,anchor}.rs. - Fold the repeated BlockchainProvider::new(factory) construction into the genesis_fixture test helper in proof_history/live.rs (5 collector tests). Behavior-preserving; no consensus-critical path (zk-gas, JIT gating, block executor) touched. Verified with just fmt-check, just clippy (--all-features, missing-docs gate), and just test (--all-features): 248 passed, 0 skipped. Co-Authored-By: Claude Opus 4.8 --- crates/consensus/src/validation/anchor.rs | 18 ++++++++--------- crates/consensus/src/validation/mod.rs | 24 +++-------------------- crates/node/src/proof_history/live.rs | 24 ++++++++++------------- 3 files changed, 21 insertions(+), 45 deletions(-) diff --git a/crates/consensus/src/validation/anchor.rs b/crates/consensus/src/validation/anchor.rs index 1faf974..4fef8d6 100644 --- a/crates/consensus/src/validation/anchor.rs +++ b/crates/consensus/src/validation/anchor.rs @@ -8,8 +8,6 @@ use reth_primitives_traits::{Block, BlockBody, RecoveredBlock, SignedTransaction use alethia_reth_chainspec::{hardfork::TaikoHardforks, spec::TaikoChainSpec}; use alethia_reth_evm::alloy::TAIKO_GOLDEN_TOUCH_ADDRESS; -use super::other_consensus_error; - pub use crate::anchor_constants::{ ANCHOR_V1_SELECTOR, ANCHOR_V1_V2_GAS_LIMIT, ANCHOR_V2_SELECTOR, ANCHOR_V3_SELECTOR, ANCHOR_V3_V4_GAS_LIMIT, ANCHOR_V4_SELECTOR, @@ -44,7 +42,7 @@ pub fn validate_anchor_transaction( // Ensure the value is zero. if anchor_transaction.value() != U256::ZERO { - return Err(other_consensus_error("Anchor transaction value must be zero")); + return Err(ConsensusError::msg("Anchor transaction value must be zero")); } // Ensure the gas limit is correct. @@ -54,7 +52,7 @@ pub fn validate_anchor_transaction( ANCHOR_V1_V2_GAS_LIMIT }; if anchor_transaction.gas_limit() != gas_limit { - return Err(other_consensus_error(format!( + return Err(ConsensusError::msg(format!( "Anchor transaction gas limit must be {gas_limit}, got {}", anchor_transaction.gas_limit() ))); @@ -63,20 +61,20 @@ pub fn validate_anchor_transaction( // Ensure the tip is equal to zero. let anchor_transaction_tip = anchor_transaction .effective_tip_per_gas(ctx.base_fee_per_gas) - .ok_or_else(|| other_consensus_error("Anchor transaction tip must be set to zero"))?; + .ok_or_else(|| ConsensusError::msg("Anchor transaction tip must be set to zero"))?; if anchor_transaction_tip != 0 { - return Err(other_consensus_error(format!( + return Err(ConsensusError::msg(format!( "Anchor transaction tip must be zero, got {anchor_transaction_tip}" ))); } // Ensure the sender is the treasury address. let sender = anchor_transaction.try_recover().map_err(|err| { - other_consensus_error(format!("Anchor transaction sender must be recoverable: {err}")) + ConsensusError::msg(format!("Anchor transaction sender must be recoverable: {err}")) })?; if sender != Address::from(TAIKO_GOLDEN_TOUCH_ADDRESS) { - return Err(other_consensus_error(format!( + return Err(ConsensusError::msg(format!( "Anchor transaction sender must be the treasury address, got {sender}" ))); } @@ -106,7 +104,7 @@ where base_fee_per_gas: block .header() .base_fee_per_gas() - .ok_or_else(|| other_consensus_error("Block base fee per gas must be set"))?, + .ok_or_else(|| ConsensusError::msg("Block base fee per gas must be set"))?, }, ) } @@ -117,7 +115,7 @@ pub(super) fn validate_input_selector( expected_selector: &[u8; 4], ) -> Result<(), ConsensusError> { if !input.starts_with(expected_selector) { - return Err(other_consensus_error(format!( + return Err(ConsensusError::msg(format!( "Anchor transaction input data does not match the expected selector: {expected_selector:?}" ))); } diff --git a/crates/consensus/src/validation/mod.rs b/crates/consensus/src/validation/mod.rs index a006312..57b0781 100644 --- a/crates/consensus/src/validation/mod.rs +++ b/crates/consensus/src/validation/mod.rs @@ -36,24 +36,6 @@ pub use anchor::{ #[cfg(test)] mod tests; -/// Free-form message carried by Taiko-specific [`ConsensusError::Other`] violations. -#[derive(Debug)] -struct TaikoConsensusMessage(String); - -impl core::fmt::Display for TaikoConsensusMessage { - /// Writes the violation message verbatim. - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str(&self.0) - } -} - -impl core::error::Error for TaikoConsensusMessage {} - -/// Builds a [`ConsensusError::Other`] from a Taiko validation message. -pub(crate) fn other_consensus_error(message: impl Into) -> ConsensusError { - ConsensusError::Other(Arc::new(TaikoConsensusMessage(message.into()))) -} - /// Minimal block reader interface used by Taiko consensus. pub trait TaikoBlockReader: Send + Sync + Debug { /// Returns the timestamp of the block referenced by the given hash, if present. @@ -185,7 +167,7 @@ where if self.chain_spec.is_shasta_active(header.timestamp()) && header.extra_data().len() != SHASTA_EXTRA_DATA_LEN { - return Err(other_consensus_error(format!( + return Err(ConsensusError::msg(format!( "invalid Shasta extra-data length: have {}, want {SHASTA_EXTRA_DATA_LEN}", header.extra_data().len() ))); @@ -284,7 +266,7 @@ where return Ok(()); } - Err(other_consensus_error(format!( + Err(ConsensusError::msg(format!( "Unzen block body extends past zk gas truncation point: body has {body_transaction_count} transactions but execution committed {committed_receipt_count}" ))) } @@ -332,7 +314,7 @@ fn validate_no_blob_transactions( transactions: &[Tx], ) -> Result<(), ConsensusError> { if transactions.iter().any(|tx| !is_allowed_tx_type(tx)) { - return Err(other_consensus_error("Blob transactions are not allowed")); + return Err(ConsensusError::msg("Blob transactions are not allowed")); } Ok(()) } diff --git a/crates/node/src/proof_history/live.rs b/crates/node/src/proof_history/live.rs index 2fe6697..1ca5bb8 100644 --- a/crates/node/src/proof_history/live.rs +++ b/crates/node/src/proof_history/live.rs @@ -235,7 +235,7 @@ mod tests { }; use reth_primitives_traits::Block as _; use reth_provider::{ - ProviderFactory, StorageSettingsCache, + StorageSettingsCache, providers::BlockchainProvider, test_utils::{MockNodeTypesWithDB, create_test_provider_factory_with_chain_spec}, }; @@ -262,10 +262,10 @@ mod tests { .expect("empty block recovers without senders") } - /// Genesis-initialized provider factory plus proofs storage seeded at block zero. + /// Genesis-initialized blockchain provider plus proofs storage seeded at block zero. fn genesis_fixture( chain_spec: &Arc, - ) -> (ProviderFactory, OpProofsStorage>) { + ) -> (BlockchainProvider, OpProofsStorage>) { let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&factory).expect("genesis state initializes"); @@ -283,7 +283,8 @@ mod tests { .run(0, chain_spec.genesis_hash()) .expect("proofs storage initializes to genesis"); - (factory, storage) + let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + (provider, storage) } /// Latest block recorded in the proofs storage window. @@ -299,8 +300,7 @@ mod tests { #[test] fn collector_executes_and_stores_an_empty_block() { let chain_spec = test_chain_spec(); - let (factory, storage) = genesis_fixture(&chain_spec); - let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let (provider, storage) = genesis_fixture(&chain_spec); let collector = LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); @@ -314,8 +314,7 @@ mod tests { #[test] fn collector_rejects_a_block_beyond_the_stored_window() { let chain_spec = test_chain_spec(); - let (factory, storage) = genesis_fixture(&chain_spec); - let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let (provider, storage) = genesis_fixture(&chain_spec); let collector = LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); @@ -328,8 +327,7 @@ mod tests { #[test] fn collector_rejects_a_block_with_a_wrong_state_root() { let chain_spec = test_chain_spec(); - let (factory, storage) = genesis_fixture(&chain_spec); - let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let (provider, storage) = genesis_fixture(&chain_spec); let collector = LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); @@ -341,8 +339,7 @@ mod tests { #[test] fn collector_stores_precomputed_updates_and_unwinds_them() { let chain_spec = test_chain_spec(); - let (factory, storage) = genesis_fixture(&chain_spec); - let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let (provider, storage) = genesis_fixture(&chain_spec); let collector = LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); @@ -365,8 +362,7 @@ mod tests { #[test] fn collector_replaces_blocks_after_the_common_ancestor() { let chain_spec = test_chain_spec(); - let (factory, storage) = genesis_fixture(&chain_spec); - let provider = BlockchainProvider::new(factory).expect("blockchain provider"); + let (provider, storage) = genesis_fixture(&chain_spec); let collector = LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); From 0dc78669fbcc97ea782918cd3b0bd1044be02a9c Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Jul 2026 19:41:30 +0800 Subject: [PATCH 17/28] build(deps): pin reth-optimism-trie to OP #21766's merge commit ethereum-optimism/optimism#21766 landed as 4f21ce6b, so the dependency moves off the PR-head rev it was pinned to while that PR was in flight. OP's reth pin is unchanged at f2eecc6 across the merge, so the rev lockstep between reth-optimism-trie and the reth stack still holds and the graph keeps a single reth copy. The crate's only changes since the old rev are internal cleanups (store.rs, store_v2/{backfill,write}.rs); no first-party call sites are affected. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 15 ++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 573841b..6db457e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3409,7 +3409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5750,7 +5750,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6657,7 +6657,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8704,7 +8704,7 @@ dependencies = [ [[package]] name = "reth-optimism-trie" version = "1.11.3" -source = "git+https://github.com/ethereum-optimism/optimism?rev=43795a4eae8bf5515a7ced196879385b05237ba9#43795a4eae8bf5515a7ced196879385b05237ba9" +source = "git+https://github.com/ethereum-optimism/optimism?rev=4f21ce6b9574ffc3473dd3b622ec9f7bd0c72fad#4f21ce6b9574ffc3473dd3b622ec9f7bd0c72fad" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10253,7 +10253,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -10333,7 +10333,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 1.0.8", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -11146,7 +11146,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -12276,7 +12276,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4dbfb36..44ea634 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,13 +147,14 @@ reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4 reth-trie-common = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d", default-features = false } reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "f2eecc65482af4085e43eedb27a32024bf177a0d" } -# Consumed from the Optimism monorepo (path rust/op-reth/crates/trie). The rev is the head of -# ethereum-optimism/optimism#21766, their post-v2.4.0 reth bump: the crate's reth dependencies -# resolve inside that workspace, so this rev and the reth pin above must reference the same -# paradigmxyz/reth commit (spelled identically) or the graph gets two incompatible reth copies. -# Move this to the merge commit once #21766 lands; the Taiko live collector the crate used to -# carry for us lives in crates/node/src/proof_history/live.rs. -reth-optimism-trie = { git = "https://github.com/ethereum-optimism/optimism", rev = "43795a4eae8bf5515a7ced196879385b05237ba9", default-features = false, features = ["serde-bincode-compat"] } +# Consumed from the Optimism monorepo (path rust/op-reth/crates/trie). The rev is the merge +# commit of ethereum-optimism/optimism#21766, their post-v2.4.0 reth bump: the crate's reth +# dependencies resolve inside that workspace, so this rev and the reth pin above must reference +# the same paradigmxyz/reth commit (spelled identically) or the graph gets two incompatible reth +# copies. Future reth bumps are OP-first — match the rev they pin rather than picking our own. +# The Taiko live collector the crate used to carry for us lives in +# crates/node/src/proof_history/live.rs. +reth-optimism-trie = { git = "https://github.com/ethereum-optimism/optimism", rev = "4f21ce6b9574ffc3473dd3b622ec9f7bd0c72fad", default-features = false, features = ["serde-bincode-compat"] } # revm revm-database-interface = { version = "41.0.0", default-features = false } From 7f509779a216694e1ed9f278f78ea6a528c17204 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Jul 2026 20:07:59 +0800 Subject: [PATCH 18/28] fix(node): migrate legacy proof-history databases and survive boundary reorgs Two proof-history availability fixes, both crashing the node via the critical sidecar task: - A database initialized under the previous reth-optimism-trie pin has no LatestBlock row when no block was stored after initialization (the old store wrote only EarliestBlock and fell back to it as latest). The strict reads now make such a database look uninitialized while its completed anchor turns re-initialization into a no-op, so startup fails permanently. Repair it at sidecar startup by re-committing the completed anchor, which writes EarliestBlock and the missing LatestBlock in one transaction after verifying the anchor matches the stored earliest block. - A reorg whose common ancestor is exactly the retained earliest block passed the sidecar guard but was refused by replace_updates (ReorgBaseOutOfWindow), turning an ordinary reorg right after initialization into a crash. The sidecar now routes that boundary through unwind-and-reprocess, and the collector rebuilds the window atomically (unwind to the anchor, then append) when the common ancestor is the hash-matching anchor, failing closed with OutOfOrder when the new chain does not descend from it. Regression tests cover the legacy MDBX layout migration (including mismatching-anchor and no-op cases) and boundary reorgs at the earliest window block. Co-Authored-By: Claude Fable 5 --- crates/node/src/proof_history/live.rs | 110 +++++++++- crates/node/src/proof_history/sidecar.rs | 45 ++-- crates/node/src/proof_history/storage_init.rs | 198 +++++++++++++++++- 3 files changed, 335 insertions(+), 18 deletions(-) diff --git a/crates/node/src/proof_history/live.rs b/crates/node/src/proof_history/live.rs index 1ca5bb8..392eabf 100644 --- a/crates/node/src/proof_history/live.rs +++ b/crates/node/src/proof_history/live.rs @@ -163,7 +163,10 @@ where /// /// This method removes all block updates after the latest common ancestor (the block before /// the first block in `new_blocks`) and replaces them with the updates from the provided new - /// chain. + /// chain. A common ancestor at the retained earliest block is supported as long as the new + /// chain descends from the stored anchor: `replace_updates` refuses that boundary, so the + /// window is rebuilt by unwinding to the anchor and appending the new chain in one + /// transaction. /// /// # Arguments /// @@ -195,8 +198,35 @@ where )); } + let earliest = self.storage.provider_ro()?.get_proof_window()?.earliest; let provider_rw = self.storage.provider_rw()?; - provider_rw.replace_updates(latest_common_block, block_trie_updates)?; + if latest_common_block.number == earliest.number { + // `replace_updates` refuses a common ancestor at the window's earliest block, but + // that is exactly where an ordinary reorg lands right after initialization (or an + // unwind) collapsed the window onto its anchor: the anchor itself stays, only the + // blocks above it are replaced. The new chain must descend from the stored anchor. + if latest_common_block.hash != earliest.hash { + return Err(OpProofsStorageError::OutOfOrder { + block_number: first.block.number, + parent_block_hash: first.parent, + latest_block_hash: earliest.hash, + }); + } + block_trie_updates.sort_unstable_by_key(|(block, _)| block.block.number); + // `unwind_history` only reads the unwind number and the parent that becomes the new + // latest block; the replaced block's hash is not tracked here, so label the unwind + // with the replacement block at that height. + let unwind_to = BlockWithParent::new( + earliest.hash, + NumHash::new(earliest.number.saturating_add(1), first.block.hash), + ); + provider_rw.unwind_history(unwind_to)?; + for (block, diff) in block_trie_updates { + provider_rw.store_trie_updates(block, diff)?; + } + } else { + provider_rw.replace_updates(latest_common_block, block_trie_updates)?; + } provider_rw.commit()?; let write_duration = start.elapsed(); operation_durations.total_duration_seconds = write_duration; @@ -366,8 +396,8 @@ mod tests { let collector = LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); - // `replace_updates` refuses a common ancestor at the window's earliest block (genesis - // here), so grow the window to [0, 2] and reorg block 2 on top of block 1. + // Grow the window to [0, 2] and reorg block 2 on top of block 1: a common ancestor + // strictly above the earliest block takes the `replace_updates` path. let block_one = BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, B256::repeat_byte(1))); let original = @@ -397,4 +427,76 @@ mod tests { collector.unwind_and_store_block_updates(vec![]).expect("empty replacement is a no-op"); assert_eq!(stored_latest(&storage), replacement.block); } + + #[test] + fn collector_replaces_blocks_at_the_earliest_window_boundary() { + let chain_spec = test_chain_spec(); + let (provider, storage) = genesis_fixture(&chain_spec); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + // Window [0, 1]: the genesis anchor plus one stored block. Reorging block 1 makes the + // common ancestor exactly the retained earliest block — the state right after + // initialization, when any reorg of the first collected block lands on the anchor. + let original = + BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, B256::repeat_byte(2))); + collector + .store_block_updates( + original, + TrieUpdatesSorted::default(), + HashedPostStateSorted::default(), + ) + .expect("canonical block stores cleanly"); + + let replacement_one = + BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, B256::repeat_byte(3))); + let replacement_two = + BlockWithParent::new(replacement_one.block.hash, NumHash::new(2, B256::repeat_byte(4))); + collector + .unwind_and_store_block_updates(vec![ + ( + replacement_one, + Arc::new(TrieUpdatesSorted::default()), + Arc::new(HashedPostStateSorted::default()), + ), + ( + replacement_two, + Arc::new(TrieUpdatesSorted::default()), + Arc::new(HashedPostStateSorted::default()), + ), + ]) + .expect("boundary reorg replaces blocks above the retained anchor"); + assert_eq!(stored_latest(&storage), replacement_two.block); + } + + #[test] + fn collector_rejects_a_boundary_reorg_not_descending_from_the_anchor() { + let chain_spec = test_chain_spec(); + let (provider, storage) = genesis_fixture(&chain_spec); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + let original = + BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, B256::repeat_byte(2))); + collector + .store_block_updates( + original, + TrieUpdatesSorted::default(), + HashedPostStateSorted::default(), + ) + .expect("canonical block stores cleanly"); + + // The replacement claims a common ancestor at the earliest height but with a different + // hash than the retained anchor: the new chain does not descend from stored state. + let replacement = + BlockWithParent::new(B256::repeat_byte(0xEE), NumHash::new(1, B256::repeat_byte(3))); + let err = collector + .unwind_and_store_block_updates(vec![( + replacement, + Arc::new(TrieUpdatesSorted::default()), + Arc::new(HashedPostStateSorted::default()), + )]) + .unwrap_err(); + assert!(matches!(err, OpProofsStorageError::OutOfOrder { .. }), "got {err:?}"); + } } diff --git a/crates/node/src/proof_history/sidecar.rs b/crates/node/src/proof_history/sidecar.rs index d796e3b..43cd719 100644 --- a/crates/node/src/proof_history/sidecar.rs +++ b/crates/node/src/proof_history/sidecar.rs @@ -7,9 +7,9 @@ use super::{ storage_init::{ DelayedProofHistoryStart, ProofHistoryInitializationAction, delayed_proof_history_start, finalized_block_number, initialize_historical_proof_history_storage, - initialize_proof_history_storage, proof_history_historical_init_metadata_path, - proof_history_storage_needs_initialization, proof_history_sync_target, - read_historical_init_metadata, + initialize_proof_history_storage, migrate_legacy_proof_history_storage, + proof_history_historical_init_metadata_path, proof_history_storage_needs_initialization, + proof_history_sync_target, read_historical_init_metadata, }, }; use alethia_reth_rpc::proof_state::ProofHistoryReadiness; @@ -283,6 +283,10 @@ where { /// Runs proof-history indexing until the node shuts down. pub(super) async fn run(self, mut shutdown: GracefulShutdown) -> eyre::Result<()> { + // Databases initialized under the previous storage dependency may lack a `LatestBlock` + // row; repair them before any reconciliation reads the storage bounds. + migrate_legacy_proof_history_storage(&self.storage)?; + let collector = LiveTrieCollector::new(self.evm_config.clone(), self.provider.clone(), &self.storage); let mut notifications = self.provider.subscribe_to_canonical_state(); @@ -1019,6 +1023,13 @@ where )); } + // A reorg replacing the whole retained window bases at the earliest stored block, which + // `replace_updates` rejects even though `unwind_history` accepts unwinding one block + // higher. Route that boundary through the unwind path so the reorg still applies. + if old.first().number() == earliest_stored.number + 1 { + return self.reorg_by_unwind_and_reprocess(old, new, collector); + } + let mut block_updates: Vec<( BlockWithParent, Arc, @@ -1027,15 +1038,8 @@ where for (block_number, block) in new.blocks() { let Some(trie_data) = new.trie_data_at(*block_number) else { - // Missing trie data on at least one new block: fall back to - // unwinding the old branch first, then re-process all new - // blocks individually so executions read post-unwind parent - // state instead of stale old-branch state. - collector.unwind_history(old.first().block_with_parent())?; - for block_number in new.blocks().keys() { - self.process_block(*block_number, new, collector)?; - } - return Ok(()); + // Missing trie data on at least one new block. + return self.reorg_by_unwind_and_reprocess(old, new, collector); }; let SortedTrieData { hashed_state, trie_updates } = &trie_data.get().sorted; block_updates.push(( @@ -1052,6 +1056,23 @@ where Ok(()) } + /// Applies a reorg by unwinding the old branch first and reprocessing the new blocks + /// individually, so each block reads post-unwind parent state instead of stale + /// old-branch state. Blocks with notification trie data are stored directly; the rest are + /// re-executed. + fn reorg_by_unwind_and_reprocess( + &self, + old: &Chain, + new: &Chain, + collector: &LiveTrieCollector<'_, Node::Evm, Node::Provider, Storage>, + ) -> eyre::Result<()> { + collector.unwind_history(old.first().block_with_parent())?; + for block_number in new.blocks().keys() { + self.process_block(*block_number, new, collector)?; + } + Ok(()) + } + /// Handles a canonical chain revert notification. fn handle_chain_reverted( &self, diff --git a/crates/node/src/proof_history/storage_init.rs b/crates/node/src/proof_history/storage_init.rs index ee51dd5..ee4693d 100644 --- a/crates/node/src/proof_history/storage_init.rs +++ b/crates/node/src/proof_history/storage_init.rs @@ -7,7 +7,10 @@ use alloy_primitives::B256; use eyre::{WrapErr, eyre}; use reth::providers::{BlockNumReader, DBProvider, DatabaseProviderFactory, HeaderProvider}; use reth_db::Database; -use reth_optimism_trie::{OpProofsStore, api::OpProofsProviderRO}; +use reth_optimism_trie::{ + OpProofsStore, + api::{InitialStateStatus, OpProofsInitProvider, OpProofsProviderRO}, +}; use reth_storage_api::{ ChainStateBlockReader, ChangeSetReader, StorageChangeSetReader, StorageSettingsCache, }; @@ -32,6 +35,60 @@ where super::opt_block(provider_ro.get_latest_block())?.is_none()) } +/// Migrates proof-history storage written before `LatestBlock` was persisted separately. +/// +/// The previous storage dependency recorded only `EarliestBlock` when initialization completed +/// and fell back to it as the latest block, so a database that finished initializing but never +/// stored a live block has no `LatestBlock` row. The current dependency reads `LatestBlock` +/// strictly: such a database looks uninitialized, while its completed anchor makes the +/// initialization job a no-op, leaving startup permanently failing. Re-committing the completed +/// anchor rewrites `EarliestBlock` and the missing `LatestBlock` in one transaction, matching +/// the fallback semantics the database was written under. Returns whether a migration ran. +pub(super) fn migrate_legacy_proof_history_storage(storage: &Storage) -> eyre::Result +where + Storage: OpProofsStore, +{ + let provider_ro = storage.provider_ro()?; + let Some((earliest_number, earliest_hash)) = + super::opt_block(provider_ro.get_earliest_block())? + else { + return Ok(false); + }; + if super::opt_block(provider_ro.get_latest_block())?.is_some() { + return Ok(false); + } + drop(provider_ro); + + let init_provider = storage.initialization_provider()?; + let anchor = init_provider.initial_state_anchor()?; + if !matches!(anchor.status, InitialStateStatus::Completed) { + // Not a completed legacy layout; leave it to the initialization state machine. + return Ok(false); + } + let anchor_block = anchor + .block + .ok_or_else(|| eyre!("completed proof-history initialization has no anchor block"))?; + if anchor_block.number != earliest_number || anchor_block.hash != earliest_hash { + return Err(eyre!( + "legacy proof-history anchor ({}, {:?}) does not match earliest block ({}, {:?}); wipe proof-history storage and restart initialization", + anchor_block.number, + anchor_block.hash, + earliest_number, + earliest_hash + )); + } + + init_provider.commit_initial_state()?; + OpProofsInitProvider::commit(init_provider)?; + info!( + target: "reth::taiko::proof_history", + block = anchor_block.number, + hash = ?anchor_block.hash, + "migrated legacy proof-history storage: recorded the completed anchor as the latest block" + ); + Ok(true) +} + /// File stored beside the proof-history MDBX database to validate historical init resume targets. pub(super) const PROOF_HISTORY_HISTORICAL_INIT_METADATA_FILE: &str = "taiko-historical-init-target"; @@ -431,8 +488,13 @@ mod tests { use super::*; use alloy_consensus::Header; use reth_ethereum_primitives::EthPrimitives; - use reth_optimism_trie::{InMemoryProofsStorage, OpProofsStorage, api::OpProofsInitProvider}; + use reth_optimism_trie::{ + InMemoryProofsStorage, OpProofsStorage, + api::OpProofsInitProvider, + db::{MdbxProofsStorage, ProofWindow, ProofWindowKey, Tables}, + }; use reth_provider::test_utils::MockEthProvider; + use std::sync::Arc; #[test] fn proof_history_storage_initialization_check_tracks_empty_storage() { @@ -596,4 +658,136 @@ mod tests { // unwind `latest_stored` only run on live notifications. assert_eq!(proof_history_sync_target(200, 150), None); } + + /// Writes the pre-`LatestBlock` legacy layout into a fresh proof-history MDBX database, + /// mimicking storage whose initialization completed under the previous + /// `reth-optimism-trie` pin: it recorded the anchor and `EarliestBlock` but never wrote a + /// `LatestBlock` row (reads fell back to the earliest block). + fn write_legacy_mdbx_layout(path: &Path, rows: &[(ProofWindowKey, BlockNumHash)]) { + use reth_db::{ + Database, + mdbx::{DatabaseArguments, init_db_for}, + transaction::{DbTx, DbTxMut}, + }; + + let env = init_db_for::<_, Tables>(path, DatabaseArguments::default()) + .expect("raw proofs database opens"); + let tx = env.tx_mut().expect("write transaction opens"); + for (key, block) in rows { + tx.put::(*key, (*block).into()).expect("legacy row writes"); + } + tx.commit().expect("legacy layout commits"); + } + + /// MDBX proofs storage opened over a directory prepared by [`write_legacy_mdbx_layout`]. + fn legacy_mdbx_storage(path: &Path) -> OpProofsStorage> { + Arc::new(MdbxProofsStorage::new(path).expect("mdbx storage opens")).into() + } + + #[test] + fn legacy_storage_missing_latest_block_migrates_to_earliest_anchor() { + let dir = tempfile::tempdir().unwrap(); + let anchor = BlockNumHash::new(42, B256::with_last_byte(7)); + write_legacy_mdbx_layout( + dir.path(), + &[ + (ProofWindowKey::InitialStateAnchor, anchor), + (ProofWindowKey::EarliestBlock, anchor), + ], + ); + let storage = legacy_mdbx_storage(dir.path()); + + // The strict `LatestBlock` reads make the completed legacy database look uninitialized. + assert!(proof_history_storage_needs_initialization(&storage).unwrap()); + + assert!( + migrate_legacy_proof_history_storage(&storage).expect("legacy migration succeeds"), + "missing latest block must be migrated" + ); + + let provider_ro = storage.provider_ro().unwrap(); + assert_eq!( + super::super::opt_block(provider_ro.get_earliest_block()).unwrap(), + Some((anchor.number, anchor.hash)) + ); + assert_eq!( + super::super::opt_block(provider_ro.get_latest_block()).unwrap(), + Some((anchor.number, anchor.hash)) + ); + drop(provider_ro); + assert!(!proof_history_storage_needs_initialization(&storage).unwrap()); + + // Re-running the migration is a no-op. + assert!(!migrate_legacy_proof_history_storage(&storage).expect("second run is a no-op")); + } + + #[test] + fn legacy_migration_rejects_anchor_mismatching_earliest_block() { + let dir = tempfile::tempdir().unwrap(); + write_legacy_mdbx_layout( + dir.path(), + &[ + ( + ProofWindowKey::InitialStateAnchor, + BlockNumHash::new(42, B256::with_last_byte(7)), + ), + (ProofWindowKey::EarliestBlock, BlockNumHash::new(43, B256::with_last_byte(8))), + ], + ); + let storage = legacy_mdbx_storage(dir.path()); + + let error = migrate_legacy_proof_history_storage(&storage).unwrap_err().to_string(); + + assert!(error.contains("does not match"), "got {error}"); + assert!(error.contains("wipe proof-history storage"), "got {error}"); + } + + #[test] + fn legacy_migration_skips_storage_without_recorded_anchor() { + // Earliest present but no anchor row: initialization status reads `NotStarted`, so the + // migration must leave the database to the initialization state machine. + let dir = tempfile::tempdir().unwrap(); + write_legacy_mdbx_layout( + dir.path(), + &[(ProofWindowKey::EarliestBlock, BlockNumHash::new(42, B256::with_last_byte(7)))], + ); + let storage = legacy_mdbx_storage(dir.path()); + + assert!(!migrate_legacy_proof_history_storage(&storage).unwrap()); + } + + #[test] + fn legacy_migration_skips_empty_storage() { + let storage: OpProofsStorage = + InMemoryProofsStorage::default().into(); + + assert!(!migrate_legacy_proof_history_storage(&storage).unwrap()); + } + + #[test] + fn legacy_migration_skips_in_progress_initialization() { + let storage: OpProofsStorage = + InMemoryProofsStorage::default().into(); + let initializer = storage.initialization_provider().expect("initialization provider"); + initializer + .set_initial_state_anchor(BlockNumHash::new(0, B256::ZERO)) + .expect("set initial state anchor"); + initializer.commit().expect("commit"); + + assert!(!migrate_legacy_proof_history_storage(&storage).unwrap()); + } + + #[test] + fn legacy_migration_skips_storage_with_latest_block() { + let storage: OpProofsStorage = + InMemoryProofsStorage::default().into(); + let initializer = storage.initialization_provider().expect("initialization provider"); + initializer + .set_initial_state_anchor(BlockNumHash::new(0, B256::ZERO)) + .expect("set initial state anchor"); + initializer.commit_initial_state().expect("commit initial state"); + initializer.commit().expect("commit"); + + assert!(!migrate_legacy_proof_history_storage(&storage).unwrap()); + } } From 25fd647fae80300617b26669c9b1d4b27338c78d Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Jul 2026 12:45:23 +0800 Subject: [PATCH 19/28] build: regenerate Cargo.lock for the 1.3.0 workspace version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main's release-please commit (#212) bumped the workspace version 1.2.0 -> 1.3.0 in Cargo.toml without regenerating Cargo.lock. CI checks out the merge of this branch into main, which merges textually clean but leaves Cargo.toml at 1.3.0 and Cargo.lock at 1.2.0, so the check-no-jit job's `cargo check --locked` refused with "cannot update the lock file". This branch is the first to pass --locked in CI, so it is the first to surface the drift; the branch head alone always passed. The delta is only the 12 alethia-reth-* version strings — no dependency resolution changed. Verified: cargo check --locked -p alethia-reth-bin --no-default-features. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6db457e..d7b81aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,7 +67,7 @@ dependencies = [ [[package]] name = "alethia-reth-bin" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-cli", "alethia-reth-node", @@ -79,7 +79,7 @@ dependencies = [ [[package]] name = "alethia-reth-block" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-chainspec", "alethia-reth-evm", @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "alethia-reth-chainspec" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -136,7 +136,7 @@ dependencies = [ [[package]] name = "alethia-reth-cli" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-block", "alethia-reth-chainspec", @@ -162,7 +162,7 @@ dependencies = [ [[package]] name = "alethia-reth-consensus" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-chainspec", "alethia-reth-evm", @@ -182,7 +182,7 @@ dependencies = [ [[package]] name = "alethia-reth-db" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-primitives", "alloy-primitives", @@ -197,7 +197,7 @@ dependencies = [ [[package]] name = "alethia-reth-evm" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-chainspec", "alethia-reth-primitives", @@ -213,7 +213,7 @@ dependencies = [ [[package]] name = "alethia-reth-node" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-block", "alethia-reth-chainspec", @@ -264,7 +264,7 @@ dependencies = [ [[package]] name = "alethia-reth-payload" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-block", "alethia-reth-chainspec", @@ -306,7 +306,7 @@ dependencies = [ [[package]] name = "alethia-reth-primitives" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -330,7 +330,7 @@ dependencies = [ [[package]] name = "alethia-reth-rpc" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alethia-reth-block", "alethia-reth-chainspec", @@ -390,7 +390,7 @@ dependencies = [ [[package]] name = "alethia-reth-rpc-types" -version = "1.2.0" +version = "1.3.0" dependencies = [ "alloy-primitives", "serde", From e71e8f13c009b5e4fad0a71f5cd23d7e8b2b821f Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Jul 2026 12:54:52 +0800 Subject: [PATCH 20/28] fix(node): keep --jit.blocking from enabling JIT on its own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit revmc's runtime coerces `enabled = true` and `jit_hot_threshold = 0` whenever `blocking` is set (revmc-runtime `new_inner`, pinned 520462a4), so forwarding `--jit.blocking` straight through meant the flag alone turned on JIT for canonical execution and payload building — gate 3 (`with_jit_support()`) is already satisfied there — and compiled every contract synchronously on first touch. The "experimental revmc JIT backend" warning is gated on our own `enabled`, so it stayed silent. Upstream reth does not have this hole: `build_evm_config` short-circuits to a disabled factory before `blocking` can reach the runtime. Gating `blocking` on `enabled` closes it while still building the backend from the operator's tuning, so a later `reth_jit` RPC enable starts with the configured thresholds rather than revmc's defaults (which is what an upstream-style early return would have cost). Gates 3 and 4 were unaffected — Unzen and RPC stayed interpreter-only — so this was an opt-in bypass on a hidden flag, not a fork or metering bypass. The new test fails without the fix (backend reports enabled). Co-Authored-By: Claude Opus 4.8 --- crates/node/src/components.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/node/src/components.rs b/crates/node/src/components.rs index a5472ae..ea4977d 100644 --- a/crates/node/src/components.rs +++ b/crates/node/src/components.rs @@ -51,7 +51,11 @@ pub fn evm_config_from_jit_args( code_cache_bytes: jit.code_cache_bytes, idle_evict_duration: jit.idle_evict_duration, debug: jit.debug, - blocking: jit.blocking, + // revmc's runtime coerces `enabled = true` and `jit_hot_threshold = 0` whenever + // `blocking` is set, so forwarding `--jit.blocking` on its own would compile every + // contract on first touch without `--jit` and without the warning below. Gating it + // here keeps `--jit`/`reth_jit` the only way to turn compilation on. + blocking: jit.enabled && jit.blocking, }; let backend = config.build_backend(dump_dir)?; @@ -216,6 +220,30 @@ mod tests { assert!(!disabled.evm_factory().backend().enabled()); } + #[cfg(feature = "jit")] + #[test] + fn evm_config_from_jit_args_keeps_blocking_from_enabling_jit() { + use reth_node_core::args::JitArgs; + + // revmc turns `blocking` into `enabled = true` with a zero hot threshold, so + // `--jit.blocking` alone would otherwise compile every contract without `--jit`. + let blocking_only = evm_config_from_jit_args( + TAIKO_MAINNET.clone(), + &JitArgs { blocking: true, ..Default::default() }, + None, + ) + .expect("jit build constructs a backend"); + assert!(!blocking_only.evm_factory().backend().enabled()); + + let blocking_with_jit = evm_config_from_jit_args( + TAIKO_MAINNET.clone(), + &JitArgs { enabled: true, blocking: true, ..Default::default() }, + None, + ) + .expect("jit build constructs a backend"); + assert!(blocking_with_jit.evm_factory().backend().enabled()); + } + #[cfg(not(feature = "jit"))] #[test] fn evm_config_from_jit_args_rejects_jit_on_non_jit_builds() { From 27170a2e58d4a5ddfcd7f9e9657e37010196cbdd Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Jul 2026 12:55:02 +0800 Subject: [PATCH 21/28] fix(payload): fail closed when the Amsterdam fork is active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EIP-7928 justification rested on a `debug_assert` that could not fire — and not because asserts compile out in release. The builder never calls `with_bal_builder{,_if}` on the `State`, so `bal_builder` is always `None` and `take_built_alloy_bal()` unconditionally yields `None` regardless of fork schedule. The assert restated the builder's own DB construction, not the "Taiko schedules no Amsterdam fork" assumption the comment claimed it guarded. That assumption is operator config, not structure: `TaikoChainSpec::from` delegates to `ChainSpec::from(genesis)`, which maps `amsterdamTime` to `EthereumHardfork::Amsterdam`. Had a genesis activated it, the builder would emit no BAL, the assembler would seal `block_access_list_hash: None`, and reth's post-execution validation would skip the check (it is guarded on `let Some(hash)`) — silently producing non-compliant blocks. Guard the real assumption instead: refuse to build when Amsterdam is active at the payload timestamp. The retained discard is now an honest backstop rather than a tautology, and its comment says so. Tests cover both directions of the activation boundary and pin that the shipped mainnet spec never activates Amsterdam, so the guard cannot reject ordinary builds. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 1 + crates/payload/Cargo.toml | 1 + crates/payload/src/builder/mod.rs | 52 +++++++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7b81aa..0abe90d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,6 +273,7 @@ dependencies = [ "alethia-reth-primitives", "alloy-consensus", "alloy-eips", + "alloy-genesis", "alloy-hardforks", "alloy-primitives", "alloy-rlp", diff --git a/crates/payload/Cargo.toml b/crates/payload/Cargo.toml index 8bbc4e1..45961ec 100644 --- a/crates/payload/Cargo.toml +++ b/crates/payload/Cargo.toml @@ -49,6 +49,7 @@ tracing = { workspace = true } [dev-dependencies] alethia-reth-block = { path = "../block", features = ["test-utils"] } +alloy-genesis = { workspace = true } reth-provider = { workspace = true, features = ["test-utils"] } reth-storage-api = { workspace = true } reth-trie-common = { workspace = true } diff --git a/crates/payload/src/builder/mod.rs b/crates/payload/src/builder/mod.rs index 04c06db..876cc75 100644 --- a/crates/payload/src/builder/mod.rs +++ b/crates/payload/src/builder/mod.rs @@ -1,8 +1,10 @@ use std::sync::Arc; +use alloy_hardforks::EthereumHardforks; use reth_basic_payload_builder::{ BuildArguments, BuildOutcome, MissingPayloadBehaviour, PayloadBuilder, PayloadConfig, }; +use reth_errors::RethError; use reth_ethereum::EthPrimitives; use reth_evm::{ ConfigureEvm, @@ -143,6 +145,26 @@ fn empty_payload_result() -> Result { Err(PayloadBuilderError::MissingPayload) } +/// Rejects payload builds on a chain spec that activates the Amsterdam fork. +/// +/// Taiko schedules no Amsterdam fork, so [`taiko_payload`] never wires an EIP-7928 BAL builder +/// into the `State` and [`TaikoBlockAssembler`] always seals `block_access_list_hash: None`. +/// Nothing downstream enforces that pairing — reth's post-execution validation skips the +/// access-list check whenever the hash is absent — so an activation arriving through genesis +/// config (`amsterdamTime`) would silently produce non-compliant blocks. Fail closed instead. +fn ensure_amsterdam_inactive( + chain_spec: &TaikoChainSpec, + timestamp: u64, +) -> Result<(), PayloadBuilderError> { + if chain_spec.is_amsterdam_active_at_timestamp(timestamp) { + return Err(PayloadBuilderError::Internal(RethError::msg( + "cannot build a Taiko payload with the Amsterdam fork active: EIP-7928 block access lists are unsupported", + ))); + } + + Ok(()) +} + /// Build a Taiko payload for the given parent/header and job attributes. #[inline] fn taiko_payload( @@ -181,6 +203,8 @@ where let attributes = normalize_payload_config(&config)?; let PayloadConfig { parent_header, attributes: _, payload_id, .. } = config; + ensure_amsterdam_inactive(&client.chain_spec(), attributes.timestamp())?; + let mut state_provider = client.state_by_block_hash(parent_header.hash())?; if let Some(execution_cache) = execution_cache { state_provider = Box::new(CachedStateProvider::new( @@ -282,8 +306,9 @@ where builder.finish(state_provider.as_ref(), None)? }; - // Taiko schedules no Amsterdam fork, so the builder never produces an EIP-7928 block - // access list; the named discard is a tripwire for that assumption. + // The Amsterdam guard above rejects these builds up front, and this builder never wires a + // BAL builder into the `State`, so the list is always absent here. The named discard is a + // backstop against either of those changing. let BlockBuilderOutcome { execution_result: _, block, block_access_list, .. } = outcome; debug_assert!( block_access_list.is_none(), @@ -298,8 +323,10 @@ where #[cfg(test)] mod tests { use super::*; + use alethia_reth_chainspec::TAIKO_MAINNET; use alethia_reth_primitives::payload::attributes::{RpcL1Origin, TaikoBlockMetadata}; use alloy_consensus::Header; + use alloy_genesis::{ChainConfig, Genesis}; use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadId}; use reth_basic_payload_builder::PayloadConfig; @@ -372,6 +399,27 @@ mod tests { assert!(matches!(missing_payload_behaviour(), MissingPayloadBehaviour::AwaitInProgress)); } + #[test] + fn amsterdam_activation_rejects_payload_builds() { + let spec = TaikoChainSpec::from(Genesis { + config: ChainConfig { amsterdam_time: Some(100), ..Default::default() }, + ..Default::default() + }); + + assert!(ensure_amsterdam_inactive(&spec, 99).is_ok()); + + let err = ensure_amsterdam_inactive(&spec, 100) + .expect_err("Taiko cannot seal an EIP-7928 block access list"); + assert!(err.to_string().contains("Amsterdam"), "unexpected error: {err}"); + } + + #[test] + fn taiko_mainnet_never_activates_amsterdam() { + // The guard above only fails closed if the shipped specs genuinely leave Amsterdam + // unscheduled; otherwise it would reject every mainnet build. + assert!(ensure_amsterdam_inactive(&TAIKO_MAINNET, u64::MAX).is_ok()); + } + #[test] fn malformed_attrs_still_reject_empty_payloads_with_missing_payload() { let _config = test_payload_config(None, U256::from(1u64), Some(Bytes::from(vec![0x01]))); From b98ef6ae8c81bc6cb71a32718ea811da732cb4ad Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Jul 2026 21:08:15 +0800 Subject: [PATCH 22/28] fix(consensus): reject Amsterdam headers and EIP-7928 fields at import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 27170a2 made payload building fail closed when a genesis config schedules amsterdamTime, but import stayed fail-open: the same misconfigured node would refuse to build yet follow a chain it cannot validate, because the executor never computes a block access list and reth's post-execution validation skips the EIP-7928 comparison whenever the computed hash is absent. Reject Amsterdam-active timestamps in TaikoBeaconConsensus::validate_header — the chokepoint both the engine newPayload flow and staged sync route through — and restore upstream EthBeaconConsensus parity by rejecting stray block_access_list_hash/slot_number header fields, which the v2.4.0 migration's custom validator had silently dropped. No Taiko network schedules Amsterdam: Unzen, the latest fork, activates Osaka-level rules only, and a new test pins that for every shipped spec. Co-Authored-By: Claude Fable 5 --- crates/consensus/src/validation/mod.rs | 23 +++++++ crates/consensus/src/validation/tests.rs | 81 +++++++++++++++++++++++- crates/payload/src/builder/mod.rs | 7 +- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/crates/consensus/src/validation/mod.rs b/crates/consensus/src/validation/mod.rs index 57b0781..804f432 100644 --- a/crates/consensus/src/validation/mod.rs +++ b/crates/consensus/src/validation/mod.rs @@ -4,6 +4,7 @@ use std::{fmt::Debug, sync::Arc}; use alloy_consensus::{ BlockHeader as AlloyBlockHeader, EMPTY_OMMER_ROOT_HASH, constants::MAXIMUM_EXTRA_DATA_SIZE, }; +use alloy_hardforks::EthereumHardforks; use alloy_primitives::B256; use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom}; use reth_consensus_common::validation::{ @@ -173,6 +174,28 @@ where ))); } + // Taiko schedules no Amsterdam fork — Unzen, the latest fork, activates Osaka-level + // execution rules only — so nothing in this client builds or verifies EIP-7928 block + // access lists, and reth's post-execution validation skips the access-list comparison + // whenever the executor supplies no computed hash (always, here). Import is otherwise + // fail-open where payload building already fails closed (`ensure_amsterdam_inactive`), + // so reject the fork at the chokepoint every import path routes through: the engine + // `newPayload` flow and staged sync both call `validate_header`. + if self.chain_spec.is_amsterdam_active_at_timestamp(header.timestamp()) { + return Err(ConsensusError::msg( + "cannot validate a Taiko header with the Amsterdam fork active: EIP-7928 block access lists are unsupported", + )); + } + + // Upstream `EthBeaconConsensus` parity: pre-Amsterdam headers must not carry the + // EIP-7928 header fields. + if header.block_access_list_hash().is_some() { + return Err(ConsensusError::BlockAccessListHashUnexpected); + } + if header.slot_number().is_some() { + return Err(ConsensusError::SlotNumberUnexpected); + } + validate_header_gas(header)?; validate_header_base_fee(header, &self.chain_spec) } diff --git a/crates/consensus/src/validation/tests.rs b/crates/consensus/src/validation/tests.rs index a032b31..bdba0c6 100644 --- a/crates/consensus/src/validation/tests.rs +++ b/crates/consensus/src/validation/tests.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use super::{anchor::validate_input_selector, *}; use alethia_reth_chainspec::{ - TAIKO_DEVNET, TAIKO_MAINNET, hardfork::TaikoHardfork, spec::TaikoChainSpec, + TAIKO_DEVNET, TAIKO_HOODI, TAIKO_MAINNET, TAIKO_MASAYA, hardfork::TaikoHardfork, + spec::TaikoChainSpec, }; use alloy_consensus::{ BlockBody, EMPTY_OMMER_ROOT_HASH, Header, Signed, TxEip4844, TxLegacy, constants::EMPTY_ROOT_HASH, proofs, }; -use alloy_hardforks::ForkCondition; +use alloy_hardforks::{EthereumHardfork, EthereumHardforks, ForkCondition}; use alloy_primitives::{Address, B256, Bytes, ChainId, FixedBytes, Signature, TxKind, U256}; use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator}; use reth_ethereum_primitives::{Block, EthPrimitives, Receipt, TransactionSigned}; @@ -233,6 +234,82 @@ fn pre_shasta_header_has_no_extra_data_len_rule() { .expect("pre-Shasta headers have no extraData length rule"); } +#[test] +fn amsterdam_active_header_is_rejected_at_import() { + let mut chain_spec = devnet_chain_spec(); + chain_spec.inner.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(100)); + let consensus = test_consensus(chain_spec); + let base = Header { + timestamp: 99, + base_fee_per_gas: Some(1), + gas_limit: 30_000_000, + extra_data: shasta_extra_data(), + ..Default::default() + }; + + consensus + .validate_header(&SealedHeader::new_unhashed(base.clone())) + .expect("headers before the Amsterdam activation should validate"); + + let err = consensus + .validate_header(&SealedHeader::new_unhashed(Header { timestamp: 100, ..base })) + .expect_err("Amsterdam-active headers must be rejected at import"); + assert!(err.to_string().contains("Amsterdam"), "unexpected error: {err}"); +} + +#[test] +fn pre_amsterdam_header_rejects_block_access_list_hash() { + let consensus = test_consensus(devnet_chain_spec()); + let header = Header { + timestamp: 1, + base_fee_per_gas: Some(1), + gas_limit: 30_000_000, + extra_data: shasta_extra_data(), + block_access_list_hash: Some(B256::ZERO), + ..Default::default() + }; + + let err = consensus + .validate_header(&SealedHeader::new_unhashed(header)) + .expect_err("pre-Amsterdam headers must not carry a block access list hash"); + assert!(matches!(err, ConsensusError::BlockAccessListHashUnexpected)); +} + +#[test] +fn pre_amsterdam_header_rejects_slot_number() { + let consensus = test_consensus(devnet_chain_spec()); + let header = Header { + timestamp: 1, + base_fee_per_gas: Some(1), + gas_limit: 30_000_000, + extra_data: shasta_extra_data(), + slot_number: Some(1), + ..Default::default() + }; + + let err = consensus + .validate_header(&SealedHeader::new_unhashed(header)) + .expect_err("pre-Amsterdam headers must not carry a slot number"); + assert!(matches!(err, ConsensusError::SlotNumberUnexpected)); +} + +#[test] +fn shipped_taiko_specs_never_activate_amsterdam() { + // Unzen, Taiko's latest fork, activates Osaka-level execution rules only. No shipped spec + // may ever schedule Amsterdam, or `validate_header` would reject every block at import. + for (name, spec) in [ + ("mainnet", &**TAIKO_MAINNET), + ("taiko-hoodi", &**TAIKO_HOODI), + ("devnet", &**TAIKO_DEVNET), + ("masaya", &**TAIKO_MASAYA), + ] { + assert!( + !spec.is_amsterdam_active_at_timestamp(u64::MAX), + "{name} must never activate Amsterdam" + ); + } +} + #[test] fn unzen_post_execution_rejects_body_past_truncation_point() { let consensus = test_consensus(unzen_chain_spec()); diff --git a/crates/payload/src/builder/mod.rs b/crates/payload/src/builder/mod.rs index 876cc75..ce2e913 100644 --- a/crates/payload/src/builder/mod.rs +++ b/crates/payload/src/builder/mod.rs @@ -149,9 +149,10 @@ fn empty_payload_result() -> Result { /// /// Taiko schedules no Amsterdam fork, so [`taiko_payload`] never wires an EIP-7928 BAL builder /// into the `State` and [`TaikoBlockAssembler`] always seals `block_access_list_hash: None`. -/// Nothing downstream enforces that pairing — reth's post-execution validation skips the -/// access-list check whenever the hash is absent — so an activation arriving through genesis -/// config (`amsterdamTime`) would silently produce non-compliant blocks. Fail closed instead. +/// Reth's post-execution validation skips the access-list check whenever the hash is absent, so +/// an activation arriving through genesis config (`amsterdamTime`) would silently produce +/// non-compliant blocks. Fail closed instead, mirroring the import-side rejection in +/// `TaikoBeaconConsensus::validate_header`. fn ensure_amsterdam_inactive( chain_spec: &TaikoChainSpec, timestamp: u64, From 0b294882b21557e35d2e243763fbddac1e92d223 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 18 Jul 2026 15:51:07 +0800 Subject: [PATCH 23/28] fix(evm): finalize the journal when transaction execution errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit transact_raw propagated handler errors with ? before journal.finalize(), while revm's ExecuteEvm::transact finalizes unconditionally ("if the transaction fails, the journal is considered broken") — the shape main inherited by delegating to inner.transact, so the v2.4.0 migration silently changed error-path behavior. The residue cannot corrupt later transactions today: TaikoEvmHandler inherits revm's default run/inspect_run, whose catch_error discards the failed transaction's journal entries, leaving only cold, untouched cache entries holding original DB values — semantically inert for execution and filtered out by State::commit on the way to any bundle state. But payload building (legacy mode) and derived-block execution skip invalid transactions and keep executing on the same EVM, so the next successful transaction's returned state was padded with the failed transaction's loaded accounts, and the safety argument rested on revm internals (discard_tx retaining reverted entries) rather than on our own code. Finalize before propagating the error, restoring the upstream contract and pre-2.4.0 parity. The regression test asserts the observable that actually changes — the journal state map is empty after an errored transact_raw — and fails without the fix; an invalid-then-valid result-level test would pass either way. Co-Authored-By: Claude Fable 5 --- crates/evm/src/alloy.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/evm/src/alloy.rs b/crates/evm/src/alloy.rs index c051ca7..f294f5c 100644 --- a/crates/evm/src/alloy.rs +++ b/crates/evm/src/alloy.rs @@ -321,9 +321,12 @@ where handler.inspect_run(&mut self.inner) } else { handler.run(&mut self.inner) - }?; + }; + // Finalize before propagating errors, mirroring revm's `ExecuteEvm::transact`: payload + // building and derived-block execution skip invalid transactions and keep executing on + // this EVM, and an errored run must not leave the journal for the next transaction. let state = self.ctx_mut().journal_mut().finalize(); - Ok(ResultAndState::new(result, state)) + Ok(ResultAndState::new(result?, state)) } /// Executes a system call. @@ -579,4 +582,34 @@ mod tests { "treasury must be cold again for the first real transaction" ); } + + #[test] + fn errored_transaction_finalizes_the_journal_for_the_next_transaction() { + // Payload building and derived-block execution skip invalid transactions and keep + // executing on the same EVM, so an errored `transact_raw` must leave the journal as + // finalized as a successful one — revm's `ExecuteEvm::transact` finalizes + // unconditionally for exactly this reason. + let broke_caller = Address::with_last_byte(0xBC); + let mut env: EvmEnv = EvmEnv::default(); + env.cfg_env.chain_id = 167_000; + let mut evm = TaikoEvmFactory::default().create_evm(InMemoryDB::default(), env); + + let tx = TxEnv::builder() + .caller(broke_caller) + .kind(TxKind::Call(Address::ZERO)) + // Send value from an unfunded account: validation loads the caller through the + // journal and only then errors, so the failed transaction has journal state to + // leak. A pre-state error (e.g. a chain-id mismatch) would not exercise this. + .value(U256::from(1)) + .gas_limit(21_000) + .chain_id(None) + .build() + .expect("valid tx env"); + evm.transact_raw(tx).expect_err("transaction from an unfunded caller must error"); + + assert!( + evm.ctx().journal().state.is_empty(), + "an errored transaction must finalize the journal before the next transaction runs" + ); + } } From ab46614e4be8e7c14bb489106c101a2f7a08275a Mon Sep 17 00:00:00 2001 From: David Date: Mon, 20 Jul 2026 09:15:52 +0800 Subject: [PATCH 24/28] fix(rpc): reject unsupported engine fields --- crates/block/src/config.rs | 2 + crates/primitives/src/engine/mod.rs | 6 +- crates/primitives/src/engine/types.rs | 13 +- crates/primitives/src/payload/attributes.rs | 5 + crates/rpc/src/engine/validator.rs | 183 ++++++++++++++++++-- 5 files changed, 194 insertions(+), 15 deletions(-) diff --git a/crates/block/src/config.rs b/crates/block/src/config.rs index 0592f44..a926331 100644 --- a/crates/block/src/config.rs +++ b/crates/block/src/config.rs @@ -675,6 +675,8 @@ mod tests { withdrawals_hash: None, header_difficulty, taiko_block: Some(true), + block_access_list: None, + slot_number: None, }, } } diff --git a/crates/primitives/src/engine/mod.rs b/crates/primitives/src/engine/mod.rs index 4590746..bb15917 100644 --- a/crates/primitives/src/engine/mod.rs +++ b/crates/primitives/src/engine/mod.rs @@ -31,8 +31,8 @@ impl PayloadTypes for TaikoEngineTypes { /// Converts a block into an execution payload. /// - /// The Amsterdam block access list is discarded: Taiko networks schedule no Amsterdam - /// fork and [`TaikoExecutionData`] carries no BAL field. + /// The Amsterdam block access list is discarded because Taiko networks schedule no Amsterdam + /// fork; the transport sentinels in [`TaikoExecutionData`] remain empty for locally built data. fn block_to_payload( block: SealedBlock< <::Primitives as NodePrimitives>::Block, @@ -52,6 +52,8 @@ impl PayloadTypes for TaikoEngineTypes { withdrawals_hash, header_difficulty: Some(header_difficulty), taiko_block: Some(true), + block_access_list: None, + slot_number: None, }, } } diff --git a/crates/primitives/src/engine/types.rs b/crates/primitives/src/engine/types.rs index a6e2bdf..086d991 100644 --- a/crates/primitives/src/engine/types.rs +++ b/crates/primitives/src/engine/types.rs @@ -45,6 +45,15 @@ pub struct TaikoExecutionDataSidecar { pub header_difficulty: Option, /// Marker flag indicating whether this payload is a Taiko block. pub taiko_block: Option, + /// Inbound Amsterdam block access list retained only so engine validation can reject it. + #[cfg_attr(feature = "serde", serde(default, skip_serializing))] + pub block_access_list: Option, + /// Inbound Amsterdam slot number retained only so engine validation can reject it. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing, with = "alloy_serde::quantity::opt") + )] + pub slot_number: Option, } impl ExecutionPayloadTr for TaikoExecutionData { @@ -70,7 +79,7 @@ impl ExecutionPayloadTr for TaikoExecutionData { /// Returns the access list associated with the block, if any. fn block_access_list(&self) -> Option<&Bytes> { - None + self.taiko_sidecar.block_access_list.as_ref() } /// Returns the parent beacon block root, if applicable. @@ -100,7 +109,7 @@ impl ExecutionPayloadTr for TaikoExecutionData { /// Returns the slot number for the payload. Taiko payloads do not carry a beacon slot. fn slot_number(&self) -> Option { - None + self.taiko_sidecar.slot_number } } diff --git a/crates/primitives/src/payload/attributes.rs b/crates/primitives/src/payload/attributes.rs index 4953fcb..a707c95 100644 --- a/crates/primitives/src/payload/attributes.rs +++ b/crates/primitives/src/payload/attributes.rs @@ -118,6 +118,11 @@ impl PayloadAttributes for TaikoPayloadAttributes { fn slot_number(&self) -> Option { self.payload_attributes.slot_number() } + + /// Delegates to the wrapped ETH attributes' requested target gas limit. + fn target_gas_limit(&self) -> Option { + self.payload_attributes.target_gas_limit() + } } /// The metadata for a Taiko block, which is generated by the Taiko protocol. diff --git a/crates/rpc/src/engine/validator.rs b/crates/rpc/src/engine/validator.rs index 6c03e16..0b48061 100644 --- a/crates/rpc/src/engine/validator.rs +++ b/crates/rpc/src/engine/validator.rs @@ -26,7 +26,7 @@ use reth_node_builder::{ }; use reth_payload_primitives::{ EngineApiMessageVersion, EngineObjectValidationError, InvalidPayloadAttributesError, - PayloadAttributes, PayloadOrAttributes, + PayloadAttributes, PayloadOrAttributes, VersionSpecificValidationError, }; use reth_primitives_traits::{Block as BlockTrait, SealedBlock}; use std::sync::Arc; @@ -40,6 +40,9 @@ enum TaikoPayloadValidationError { /// Unzen payloads must carry the original header difficulty through the Taiko sidecar. #[error("missing header difficulty for Unzen payload")] MissingUnzenHeaderDifficulty, + /// Taiko payload construction does not consume the post-Amsterdam target-gas-limit attribute. + #[error("target gas limit is unsupported on Taiko")] + TargetGasLimitUnsupported, } /// Builder for [`TaikoEngineValidator`]. @@ -219,20 +222,39 @@ where fn validate_version_specific_fields( &self, _version: EngineApiMessageVersion, - _payload_or_attrs: PayloadOrAttributes<'_, Types::ExecutionData, Types::PayloadAttributes>, + payload_or_attrs: PayloadOrAttributes<'_, Types::ExecutionData, Types::PayloadAttributes>, ) -> Result<(), EngineObjectValidationError> { - // For Taiko, we don't have version-specific validation + let validation_kind = payload_or_attrs.message_validation_kind(); + + if payload_or_attrs.block_access_list().is_some() { + return Err(validation_kind + .to_error(VersionSpecificValidationError::BlockAccessListNotSupported)); + } + if payload_or_attrs.slot_number().is_some() { + return Err( + validation_kind.to_error(VersionSpecificValidationError::SlotNumberNotSupported) + ); + } + if payload_or_attrs.target_gas_limit().is_some() { + return Err(EngineObjectValidationError::InvalidParams(Box::new( + TaikoPayloadValidationError::TargetGasLimitUnsupported, + ))); + } + Ok(()) } /// Ensures that the payload attributes are valid for the given [`EngineApiMessageVersion`]. fn ensure_well_formed_attributes( &self, - _version: EngineApiMessageVersion, - _attributes: &Types::PayloadAttributes, + version: EngineApiMessageVersion, + attributes: &Types::PayloadAttributes, ) -> Result<(), EngineObjectValidationError> { - // Attributes are well-formed if they pass the basic validation - Ok(()) + >::validate_version_specific_fields( + self, + version, + PayloadOrAttributes::from_attributes(attributes), + ) } } @@ -240,15 +262,18 @@ where mod tests { use super::*; use alethia_reth_chainspec::{TAIKO_DEVNET, hardfork::TaikoHardfork}; - use alethia_reth_primitives::engine::{ - TaikoEngineTypes, - types::{TaikoExecutionData, TaikoExecutionDataSidecar}, + use alethia_reth_primitives::{ + engine::{ + TaikoEngineTypes, + types::{TaikoExecutionData, TaikoExecutionDataSidecar}, + }, + payload::attributes::{RpcL1Origin, TaikoBlockMetadata, TaikoPayloadAttributes}, }; use alloy_consensus::{BlockBody, Header, constants::EMPTY_WITHDRAWALS}; use alloy_eips::merge::BEACON_NONCE; use alloy_hardforks::ForkCondition; use alloy_primitives::{Address, B256, Bytes, U256}; - use alloy_rpc_types_engine::ExecutionPayloadV1; + use alloy_rpc_types_engine::{ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes}; use alloy_rpc_types_eth::Withdrawals; use reth_primitives_traits::BlockBody as _; @@ -317,6 +342,140 @@ mod tests { assert_eq!(sealed.header().requests_hash, Some(EMPTY_REQUESTS_HASH)); } + #[test] + fn rejects_slot_number_in_v2_payload_attributes_json() { + let attributes = + payload_attributes_with_extra_field("slotNumber", serde_json::json!("0x1")); + + let error = validate_payload_attributes(&attributes) + .expect_err("Taiko V2 payload attributes must reject slotNumber"); + + assert_eq!( + error.to_string(), + "Payload attributes validation error: slot number not supported in this engine API version" + ); + } + + #[test] + fn rejects_target_gas_limit_in_v2_payload_attributes_json() { + let attributes = + payload_attributes_with_extra_field("targetGasLimit", serde_json::json!("0x1c9c380")); + + let error = validate_payload_attributes(&attributes) + .expect_err("Taiko V2 payload attributes must reject targetGasLimit"); + + assert_eq!(error.to_string(), "Invalid params: target gas limit is unsupported on Taiko"); + } + + #[test] + fn rejects_block_access_list_in_v2_execution_payload_json() { + let payload = execution_data_with_extra_field("blockAccessList", serde_json::json!("0xc0")); + + let error = validate_execution_payload(&payload) + .expect_err("Taiko V2 execution payloads must reject blockAccessList"); + + assert_eq!( + error.to_string(), + "Payload validation error: block access list not supported in this engine API version" + ); + } + + #[test] + fn rejects_slot_number_in_v2_execution_payload_json() { + let payload = execution_data_with_extra_field("slotNumber", serde_json::json!("0x1")); + + let error = validate_execution_payload(&payload) + .expect_err("Taiko V2 execution payloads must reject slotNumber"); + + assert_eq!( + error.to_string(), + "Payload validation error: slot number not supported in this engine API version" + ); + } + + fn validate_payload_attributes( + attributes: &TaikoPayloadAttributes, + ) -> Result<(), EngineObjectValidationError> { + >:: + validate_version_specific_fields( + &TaikoEngineValidator::new(Arc::new(unzen_chain_spec())), + EngineApiMessageVersion::V2, + PayloadOrAttributes::from_attributes(attributes), + ) + } + + fn validate_execution_payload( + payload: &TaikoExecutionData, + ) -> Result<(), EngineObjectValidationError> { + >:: + validate_version_specific_fields( + &TaikoEngineValidator::new(Arc::new(unzen_chain_spec())), + EngineApiMessageVersion::V2, + PayloadOrAttributes::from_execution_payload(payload), + ) + } + + fn payload_attributes_with_extra_field( + field: &str, + value: serde_json::Value, + ) -> TaikoPayloadAttributes { + let mut json = serde_json::to_value(sample_payload_attributes()) + .expect("sample payload attributes must serialize"); + json.as_object_mut() + .expect("payload attributes must serialize as an object") + .insert(field.to_owned(), value); + serde_json::from_value(json).expect("payload attributes with fork field must deserialize") + } + + fn execution_data_with_extra_field( + field: &str, + value: serde_json::Value, + ) -> TaikoExecutionData { + let mut json = serde_json::to_value(sample_unzen_execution_data( + U256::from(7_u64), + Some(U256::from(7_u64)), + Some(B256::ZERO), + )) + .expect("sample execution data must serialize"); + json.as_object_mut() + .expect("execution data must serialize as an object") + .insert(field.to_owned(), value); + serde_json::from_value(json).expect("execution data with fork field must deserialize") + } + + fn sample_payload_attributes() -> TaikoPayloadAttributes { + TaikoPayloadAttributes { + payload_attributes: EthPayloadAttributes { + timestamp: 1, + prev_randao: B256::with_last_byte(0x11), + suggested_fee_recipient: Address::with_last_byte(0x22), + withdrawals: Some(Vec::new()), + parent_beacon_block_root: Some(B256::ZERO), + slot_number: None, + target_gas_limit: None, + }, + base_fee_per_gas: U256::from(1_u64), + block_metadata: TaikoBlockMetadata { + beneficiary: Address::with_last_byte(0x33), + gas_limit: 30_000_000, + timestamp: U256::from(1_u64), + mix_hash: B256::with_last_byte(0x44), + tx_list: None, + extra_data: Bytes::new(), + }, + l1_origin: RpcL1Origin { + block_id: U256::ZERO, + l2_block_hash: B256::ZERO, + l1_block_height: None, + l1_block_hash: None, + build_payload_args_id: [0; 8], + is_forced_inclusion: false, + signature: [0; 65], + }, + anchor_transaction: None, + } + } + fn unzen_chain_spec() -> TaikoChainSpec { let mut chain_spec = (*TAIKO_DEVNET).as_ref().clone(); chain_spec.inner.hardforks.insert(TaikoHardfork::Unzen, ForkCondition::Timestamp(0)); @@ -371,6 +530,8 @@ mod tests { withdrawals_hash: Some(EMPTY_WITHDRAWALS), header_difficulty, taiko_block: Some(true), + block_access_list: None, + slot_number: None, }, } } From 543e4f1d1beed58e6e9b138be21fa795302be396 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 23 Jul 2026 10:04:36 +0800 Subject: [PATCH 25/28] fix(node): require collected blocks to extend the stored proof-history tip The live collector only checked that the parent height fell inside the proof window before executing, acknowledged by a TODO on the missing hash check. On a reorg race the execution and state-root collection ran against a wrong-fork overlay and surfaced late as a confusing state-root mismatch, even though the append-only storage would have refused the block at write time anyway. Resolve the stored tip up front and reject blocks that do not extend it with the same `OutOfOrder` error the store raises, mirroring the boundary check in `unwind_and_store_block_updates`, and make the genesis parent-height subtraction an explicit `UnknownParent` instead of a debug-build overflow. Co-Authored-By: Claude Fable 5 --- crates/node/src/proof_history/live.rs | 72 +++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/crates/node/src/proof_history/live.rs b/crates/node/src/proof_history/live.rs index 392eabf..71b9653 100644 --- a/crates/node/src/proof_history/live.rs +++ b/crates/node/src/proof_history/live.rs @@ -59,7 +59,9 @@ where let window = provider_ro.get_proof_window()?; let (earliest, latest) = (window.earliest.number, window.latest.number); - let parent_block_number = block.number() - 1; + // Genesis has no parent state to execute against. + let parent_block_number = + block.number().checked_sub(1).ok_or(OpProofsStorageError::UnknownParent)?; if parent_block_number < earliest { return Err(OpProofsStorageError::UnknownParent); } @@ -72,11 +74,21 @@ where }); } + // The storage only accepts appends on top of its latest block (`store_trie_updates` + // re-checks this at write time), so require the parent to be the stored tip before + // paying for execution and state-root collection: a reorg race would otherwise surface + // late as a confusing state-root mismatch, and even a matching root could not be stored. + if parent_block_number != latest || block.parent_hash() != window.latest.hash { + return Err(OpProofsStorageError::OutOfOrder { + block_number: block.number(), + parent_block_hash: block.parent_hash(), + latest_block_hash: window.latest.hash, + }); + } + let block_ref = BlockWithParent::new(block.parent_hash(), NumHash::new(block.number(), block.hash())); - // TODO: should we check block hash here? - let state_provider = OpProofsStateProviderRef::new( self.provider.state_by_block_hash(block.parent_hash())?, self.storage.provider_ro()?, @@ -354,6 +366,60 @@ mod tests { assert!(matches!(err, OpProofsStorageError::MissingParentBlock { .. }), "got {err:?}"); } + #[test] + fn collector_rejects_a_genesis_block() { + let chain_spec = test_chain_spec(); + let (provider, storage) = genesis_fixture(&chain_spec); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + // Block zero has no parent: the parent-height subtraction must not wrap into a window + // probe. + let block = empty_block(0, B256::ZERO, chain_spec.genesis_header().state_root); + let err = collector.execute_and_store_block_updates(&block).unwrap_err(); + assert!(matches!(err, OpProofsStorageError::UnknownParent), "got {err:?}"); + } + + #[test] + fn collector_rejects_a_block_whose_parent_is_not_the_stored_tip() { + let chain_spec = test_chain_spec(); + let (provider, storage) = genesis_fixture(&chain_spec); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + // Parent height matches the stored tip (genesis) but the hash belongs to another fork: + // the collector must refuse up front instead of executing toward a state-root mismatch. + let block = empty_block(1, B256::repeat_byte(0xEE), chain_spec.genesis_header().state_root); + let err = collector.execute_and_store_block_updates(&block).unwrap_err(); + assert!(matches!(err, OpProofsStorageError::OutOfOrder { .. }), "got {err:?}"); + } + + #[test] + fn collector_rejects_a_block_executing_inside_the_stored_window() { + let chain_spec = test_chain_spec(); + let (provider, storage) = genesis_fixture(&chain_spec); + let collector = + LiveTrieCollector::new(EthEvmConfig::ethereum(chain_spec.clone()), provider, &storage); + + // Window [0, 1]: a block whose parent is the interior genesis block is a reorg of the + // stored tip. The append-only storage would reject it at write time, so execution must + // be refused up front (reorgs go through `unwind_and_store_block_updates`). + let stored = + BlockWithParent::new(chain_spec.genesis_hash(), NumHash::new(1, B256::repeat_byte(1))); + collector + .store_block_updates( + stored, + TrieUpdatesSorted::default(), + HashedPostStateSorted::default(), + ) + .expect("canonical block stores cleanly"); + + let block = + empty_block(1, chain_spec.genesis_hash(), chain_spec.genesis_header().state_root); + let err = collector.execute_and_store_block_updates(&block).unwrap_err(); + assert!(matches!(err, OpProofsStorageError::OutOfOrder { .. }), "got {err:?}"); + } + #[test] fn collector_rejects_a_block_with_a_wrong_state_root() { let chain_spec = test_chain_spec(); From bdfe424a5955012575773f22e0576e9516f58072 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 23 Jul 2026 10:04:46 +0800 Subject: [PATCH 26/28] docs(block): state the pre-checked-result invariant on commit_transaction `commit_current_transaction_zk_gas` panics if committing would exceed the block zk gas budget, relying on `execute_transaction_without_commit` having pre-checked it while truncation could still be reported. Record that contract on the `commit_transaction` impl so future callers do not feed it results built outside that path. Co-Authored-By: Claude Fable 5 --- crates/block/src/executor.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/block/src/executor.rs b/crates/block/src/executor.rs index f49dedc..552ce98 100644 --- a/crates/block/src/executor.rs +++ b/crates/block/src/executor.rs @@ -444,6 +444,11 @@ where /// Commits a previously executed transaction: updates receipts, gas accounting, and writes the /// buffered state changes to the database. /// + /// `output` must be a result produced by `execute_transaction_without_commit` on this + /// executor: that is the only place the block zk gas budget is pre-checked while truncation + /// can still be reported (committing is infallible), and + /// `commit_current_transaction_zk_gas` panics on a result that skipped the check. + /// /// State hooks fire from the `State` database on commit (reth v2.4.0 moved hook delivery off /// the block executor), so no explicit hook call is needed here. fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { From 64be05ce583d80b60e7242ad805efc0db30720bd Mon Sep 17 00:00:00 2001 From: David Date: Thu, 23 Jul 2026 10:04:46 +0800 Subject: [PATCH 27/28] ci(repo): fail CI when Cargo.lock resolves multiple reth revisions reth-optimism-trie resolves its reth dependencies inside the OP monorepo workspace, so Alethia's reth pin must reference the exact commit OP pins or the graph splits into two incompatible reth copies. Check the lockfile in CI so a drifted pin fails loudly at the source instead of as confusing type errors. Co-Authored-By: Claude Fable 5 --- .github/scripts/check_reth_pin.sh | 19 +++++++++++++++++++ .github/workflows/ci.yml | 2 ++ 2 files changed, 21 insertions(+) create mode 100755 .github/scripts/check_reth_pin.sh diff --git a/.github/scripts/check_reth_pin.sh b/.github/scripts/check_reth_pin.sh new file mode 100755 index 0000000..c00e57a --- /dev/null +++ b/.github/scripts/check_reth_pin.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# Fails when the lockfile resolves more than one paradigmxyz/reth revision. reth-optimism-trie +# resolves its reth dependencies inside the OP monorepo workspace, so the graph only stays +# coherent while Alethia's reth pin references the exact commit OP pins (see the +# reth-optimism-trie note in Cargo.toml); a drifted pin splits the workspace into two +# incompatible reth copies. +set -euo pipefail + +revs=$(grep -oE 'github\.com/paradigmxyz/reth\?[^"]*#[0-9a-f]{40}' Cargo.lock | sed 's/.*#//' | sort -u) +count=$(printf '%s' "$revs" | grep -c . || true) + +if [ "$count" -ne 1 ]; then + echo "Error: expected exactly one paradigmxyz/reth revision in Cargo.lock, found $count:" >&2 + printf '%s\n' "$revs" >&2 + exit 1 +fi + +echo "single reth revision in Cargo.lock: $revs" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cddfc7d..68aff23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,8 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - name: Verify a single reth revision in Cargo.lock + run: .github/scripts/check_reth_pin.sh - uses: rui314/setup-mold@v1 # LLVM is deliberately not installed: this proves the interpreter-only build stays free # of the LLVM toolchain dependency. From d4d6c0188fcd429709b26084f37ca84b91e70b48 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 23 Jul 2026 10:04:46 +0800 Subject: [PATCH 28/28] docs(repo): mark the LLVM installer as CI/Docker-only provisioning The installer force-overwrites the unversioned LLVM symlinks in /usr/bin, which is fine for disposable CI runners and Docker images but harmful on a developer machine. Say so at the top of the script and point developers at distribution packages on PATH instead. Co-Authored-By: Claude Fable 5 --- .github/scripts/install_llvm_ubuntu.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/scripts/install_llvm_ubuntu.sh b/.github/scripts/install_llvm_ubuntu.sh index 510ad88..2d2b6a4 100755 --- a/.github/scripts/install_llvm_ubuntu.sh +++ b/.github/scripts/install_llvm_ubuntu.sh @@ -1,4 +1,9 @@ #!/usr/bin/env bash +# +# CI/Docker provisioning ONLY. This installs apt.llvm.org packages system-wide and +# force-overwrites the /usr/bin/{clang,llvm-config,lld,ld.lld,FileCheck} symlinks, so it must +# not be run on a developer machine: install your distribution's llvm-22 package instead +# (Homebrew `llvm@22` on macOS) and put its bin directory on PATH. set -eo pipefail version=${1:-22}