diff --git a/Cargo.lock b/Cargo.lock index 7af1efd..00bd78d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,7 +340,7 @@ dependencies = [ "serde", "sha3", "tiny-keccak", - "zkm-zkvm", + "zkm-zkvm 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] @@ -518,7 +518,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "serde_with", @@ -1461,12 +1461,14 @@ version = "0.8.0" source = "git+https://github.com/ziren-patches/bls12_381?branch=patch-0.8.0#bcf9397426410acc171ad625416b508dc913be8e" dependencies = [ "cfg-if", + "digest 0.10.7", "ff 0.13.1", "group 0.13.0", + "hex", "pairing 0.23.0", "rand_core 0.6.4", "subtle", - "zkm-lib", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] @@ -2932,7 +2934,9 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rpc-types", + "aurora-engine-modexp", "bincode", + "bls12_381 0.8.0", "itertools 0.13.0", "kzg-rs", "mpt", @@ -2959,7 +2963,9 @@ dependencies = [ "serde", "serde_json", "serde_with", + "substrate-bn", "thiserror 1.0.69", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", ] [[package]] @@ -3797,7 +3803,7 @@ dependencies = [ "serdect", "sha2", "signature", - "zkm-lib", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] @@ -4112,6 +4118,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-debug", "hex-literal", + "reth-primitives-traits", "reth-trie", "rlp", "serde", @@ -4534,7 +4541,7 @@ dependencies = [ "hex", "primeorder", "sha2", - "zkm-lib", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] @@ -5373,7 +5380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -5406,7 +5413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.111", @@ -7396,7 +7403,7 @@ dependencies = [ "num-bigint 0.4.6", "rand 0.8.5", "rustc-hex", - "zkm-lib", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] @@ -8985,7 +8992,7 @@ dependencies = [ [[package]] name = "zkm-build" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "anyhow", "cargo_metadata", @@ -8996,7 +9003,7 @@ dependencies = [ [[package]] name = "zkm-core-executor" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "anyhow", "bincode", @@ -9034,13 +9041,13 @@ dependencies = [ "zkm-core-jit", "zkm-curves", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", ] [[package]] name = "zkm-core-jit" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "dynasmrt", "hashbrown 0.14.5", @@ -9057,7 +9064,7 @@ dependencies = [ [[package]] name = "zkm-core-machine" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "bincode", "cfg-if", @@ -9103,13 +9110,13 @@ dependencies = [ "zkm-curves", "zkm-derive", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", ] [[package]] name = "zkm-cuda" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "bincode", "ctrlc", @@ -9127,7 +9134,7 @@ dependencies = [ [[package]] name = "zkm-curves" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "cfg-if", "curve25519-dalek", @@ -9145,19 +9152,32 @@ dependencies = [ "tracing", "typenum", "zkm-poly", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", ] [[package]] name = "zkm-derive" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "zkm-lib" +version = "2.0.0" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" +dependencies = [ + "bincode", + "cfg-if", + "elliptic-curve", + "serde", + "sha2", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", +] + [[package]] name = "zkm-lib" version = "2.0.0" @@ -9168,13 +9188,13 @@ dependencies = [ "elliptic-curve", "serde", "sha2", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] name = "zkm-pcs" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "arrayref", "bincode", @@ -9212,19 +9232,39 @@ dependencies = [ "tracing-subscriber", "zkm-derive", "zkm-poly", - "zkm-primitives", - "zkm-zkvm", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", + "zkm-zkvm 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", ] [[package]] name = "zkm-poly" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "itertools 0.13.0", "p3-field", ] +[[package]] +name = "zkm-primitives" +version = "2.0.0" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" +dependencies = [ + "bincode", + "blake3", + "hex", + "lazy_static", + "num-bigint 0.4.6", + "p3-field", + "p3-koala-bear", + "p3-monty-31", + "p3-poseidon2", + "p3-symmetric", + "serde", + "sha2", + "tracing", +] + [[package]] name = "zkm-primitives" version = "2.0.0" @@ -9248,7 +9288,7 @@ dependencies = [ [[package]] name = "zkm-prover" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "anyhow", "bincode", @@ -9278,7 +9318,7 @@ dependencies = [ "zkm-core-executor", "zkm-core-machine", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", "zkm-recursion-circuit", "zkm-recursion-compiler", "zkm-recursion-core", @@ -9288,7 +9328,7 @@ dependencies = [ [[package]] name = "zkm-recursion-circuit" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "hashbrown 0.14.5", "itertools 0.13.0", @@ -9312,7 +9352,7 @@ dependencies = [ "zkm-core-machine", "zkm-derive", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", "zkm-recursion-compiler", "zkm-recursion-core", "zkm-recursion-gnark-ffi", @@ -9321,7 +9361,7 @@ dependencies = [ [[package]] name = "zkm-recursion-compiler" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "backtrace", "itertools 0.13.0", @@ -9334,7 +9374,7 @@ dependencies = [ "vec_map", "zkm-core-machine", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", "zkm-recursion-core", "zkm-recursion-derive", ] @@ -9342,7 +9382,7 @@ dependencies = [ [[package]] name = "zkm-recursion-core" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "backtrace", "bincode", @@ -9375,13 +9415,13 @@ dependencies = [ "zkm-core-machine", "zkm-derive", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", ] [[package]] name = "zkm-recursion-derive" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "quote", "syn 1.0.109", @@ -9390,7 +9430,7 @@ dependencies = [ [[package]] name = "zkm-recursion-gnark-ffi" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "anyhow", "bincode", @@ -9414,7 +9454,7 @@ dependencies = [ [[package]] name = "zkm-sdk" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" dependencies = [ "alloy-primitives", "anyhow", @@ -9454,10 +9494,26 @@ dependencies = [ "zkm-core-machine", "zkm-cuda", "zkm-pcs", - "zkm-primitives", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", "zkm-prover", ] +[[package]] +name = "zkm-zkvm" +version = "2.0.0" +source = "git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm#8a9ab4fed7d83e1acfd08d40ceacd12ddf8a00c0" +dependencies = [ + "bincode", + "cfg-if", + "getrandom 0.2.16", + "lazy_static", + "rand 0.8.5", + "serde", + "sha2", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren?branch=perf%2Fshamir-msm)", +] + [[package]] name = "zkm-zkvm" version = "2.0.0" @@ -9471,8 +9527,8 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "zkm-lib", - "zkm-primitives", + "zkm-lib 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", + "zkm-primitives 2.0.0 (git+https://github.com/ProjectZKM/Ziren)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8c9332b..85a1fdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,11 +47,11 @@ primitives = { path = "./crates/primitives" } provider = { path = "./crates/provider" } # Ziren -zkm-build = { git = "https://github.com/ProjectZKM/Ziren" } -zkm-core-executor = { git = "https://github.com/ProjectZKM/Ziren" } -zkm-prover = { git = "https://github.com/ProjectZKM/Ziren" } -zkm-cuda = { git = "https://github.com/ProjectZKM/Ziren" } -zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren" } +zkm-build = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } +zkm-core-executor = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } +zkm-prover = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } +zkm-cuda = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } +zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } # reth reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", default-features = false } diff --git a/bin/eth-proofs/src/cli.rs b/bin/eth-proofs/src/cli.rs index b92b76d..ad71299 100644 --- a/bin/eth-proofs/src/cli.rs +++ b/bin/eth-proofs/src/cli.rs @@ -87,7 +87,7 @@ pub struct Args { #[clap(long)] pub start_block: Option, - /// Witness preparation concurrency (1 or 2 in latest mode; GPU proving remains serial). + /// Witness preparation concurrency (1 or 2; consecutive results are delivered in order). #[clap(long, default_value_t = 1)] pub prepare_concurrency: usize, diff --git a/bin/eth-proofs/src/consecutive.rs b/bin/eth-proofs/src/consecutive.rs new file mode 100644 index 0000000..b128c6b --- /dev/null +++ b/bin/eth-proofs/src/consecutive.rs @@ -0,0 +1,293 @@ +//! Bounded preparation of consecutive blocks with ordered, durable publication. +use futures::{channel::mpsc, pin_mut, SinkExt, Stream, StreamExt}; +use std::{future::Future, time::Duration}; +use tokio::sync::watch; + +pub async fn run( + blocks: S, + concurrency: usize, + mut output: mpsc::Sender<(u64, T)>, + mut shutdown: watch::Receiver, + mut prepare: P, + mut persist: C, +) -> eyre::Result<()> +where + S: Stream>, + P: FnMut(u64) -> PF, + PF: Future>, + C: FnMut(u64, &T) -> eyre::Result<()>, +{ + eyre::ensure!(concurrency > 0, "preparation concurrency must be positive"); + let prepared = blocks + .map(move |number| { + let pending = number.map(|number| (number, prepare(number))); + async move { + let (number, future) = pending?; + Ok::<_, eyre::Report>((number, future.await?)) + } + }) + .buffered(concurrency); + pin_mut!(prepared); + loop { + if *shutdown.borrow() { + return Ok(()); + } + let next = tokio::select! { + biased; + _ = shutdown.changed() => return Ok(()), + next = prepared.next() => next, + }; + let Some(next) = next else { return Ok(()) }; + let (number, input) = next?; + // Later completions cannot be persisted or published ahead of this block. + persist(number, &input)?; + tokio::select! { + biased; + _ = shutdown.changed() => return Ok(()), + result = output.send((number, input)) => { + if result.is_err() { return Ok(()) } + }, + } + } +} + +/// A preparation failure occupies its original slot until the same block succeeds. +pub async fn retry( + number: u64, + delay: Duration, + mut prepare: P, + mut on_error: E, +) -> T +where + P: FnMut(u64) -> PF, + PF: Future>, + E: FnMut(u64, eyre::Report) -> EF, + EF: Future, +{ + loop { + match prepare(number).await { + Ok(input) => return input, + Err(error) => { + on_error(number, error).await; + tokio::time::sleep(delay).await; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::{stream, FutureExt}; + use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }; + use tokio::sync::Semaphore; + + #[tokio::test] + async fn overlaps_preparation_but_persists_and_delivers_in_block_order() { + let first = Arc::new(Semaphore::new(0)); + let second = Arc::new(Semaphore::new(0)); + let started = Arc::new(Mutex::new(Vec::new())); + let finished = Arc::new(Mutex::new(Vec::new())); + let persisted = Arc::new(Mutex::new(Vec::new())); + let (tx, mut rx) = mpsc::channel(4); + let (_stop, shutdown) = watch::channel(false); + let task = tokio::spawn({ + let first = first.clone(); + let second = second.clone(); + let started = started.clone(); + let finished = finished.clone(); + let persisted = persisted.clone(); + async move { + run( + stream::iter((100..104).map(Ok)), + 2, + tx, + shutdown, + move |number| { + let first = first.clone(); + let second = second.clone(); + let started = started.clone(); + let finished = finished.clone(); + async move { + started.lock().unwrap().push(number); + if number == 100 { + first.acquire().await.unwrap().forget(); + } + if number == 101 { + second.acquire().await.unwrap().forget(); + } + finished.lock().unwrap().push(number); + Ok(number) + } + }, + move |number, _| { + persisted.lock().unwrap().push(number); + Ok(()) + }, + ) + .await + } + }); + tokio::time::timeout(Duration::from_secs(2), async { + while started.lock().unwrap().len() < 2 { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + assert_eq!(*started.lock().unwrap(), vec![100, 101]); + second.add_permits(1); + tokio::time::timeout(Duration::from_secs(2), async { + while !finished.lock().unwrap().contains(&101) { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + assert!(rx.next().now_or_never().is_none()); + assert!(persisted.lock().unwrap().is_empty()); + first.add_permits(1); + let mut received = Vec::new(); + while let Some((number, value)) = rx.next().await { + assert_eq!(number, value); + received.push(number); + } + task.await.unwrap().unwrap(); + assert_eq!(received, vec![100, 101, 102, 103]); + assert_eq!(*persisted.lock().unwrap(), received); + } + + #[tokio::test] + async fn retries_a_failed_block_before_delivering_its_ready_successor() { + let attempts = Arc::new(AtomicUsize::new(0)); + let failures = Arc::new(Mutex::new(Vec::new())); + let (tx, mut rx) = mpsc::channel(4); + let (_stop, shutdown) = watch::channel(false); + let task = tokio::spawn({ + let attempts = attempts.clone(); + let failures = failures.clone(); + async move { + run( + stream::iter([Ok(100), Ok(101)]), + 2, + tx, + shutdown, + move |number| { + let attempts = attempts.clone(); + let failures = failures.clone(); + async move { + Ok(retry( + number, + Duration::from_millis(10), + move |number| { + let failed = number == 100 && + attempts.fetch_add(1, Ordering::SeqCst) == 0; + std::future::ready(if failed { + Err(eyre::eyre!("witness unavailable")) + } else { + Ok(number) + }) + }, + move |number, _| { + failures.lock().unwrap().push(number); + std::future::ready(()) + }, + ) + .await) + } + }, + |_, _| Ok(()), + ) + .await + } + }); + assert_eq!(rx.next().await, Some((100, 100))); + assert_eq!(rx.next().await, Some((101, 101))); + task.await.unwrap().unwrap(); + assert_eq!(*failures.lock().unwrap(), vec![100]); + assert_eq!(attempts.load(Ordering::SeqCst), 2); + } + + #[tokio::test] + async fn shutdown_cancels_pending_work_without_persisting_later_results() { + let started = Arc::new(AtomicUsize::new(0)); + let persisted = Arc::new(AtomicUsize::new(0)); + let (tx, _rx) = mpsc::channel(1); + let (stop, shutdown) = watch::channel(false); + let task = tokio::spawn({ + let started = started.clone(); + let persisted = persisted.clone(); + async move { + run( + stream::iter((100..110).map(Ok)), + 2, + tx, + shutdown, + move |number| { + started.fetch_add(1, Ordering::SeqCst); + async move { + if number == 100 { + std::future::pending::<()>().await; + } + Ok(number) + } + }, + move |_, _| { + persisted.fetch_add(1, Ordering::SeqCst); + Ok(()) + }, + ) + .await + } + }); + tokio::time::timeout(Duration::from_secs(2), async { + while started.load(Ordering::SeqCst) < 2 { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + stop.send(true).unwrap(); + tokio::time::timeout(Duration::from_secs(2), task).await.unwrap().unwrap().unwrap(); + assert_eq!(started.load(Ordering::SeqCst), 2); + assert_eq!(persisted.load(Ordering::SeqCst), 0); + } + + #[tokio::test] + async fn paused_consumer_bounds_preparation_and_shutdown_interrupts_backpressure() { + let started = Arc::new(AtomicUsize::new(0)); + let (tx, _rx) = mpsc::channel(1); + let (stop, shutdown) = watch::channel(false); + let task = tokio::spawn({ + let started = started.clone(); + async move { + run( + stream::iter((100..200).map(Ok)), + 2, + tx, + shutdown, + move |number| { + started.fetch_add(1, Ordering::SeqCst); + std::future::ready(Ok(number)) + }, + |_, _| Ok(()), + ) + .await + } + }); + tokio::time::timeout(Duration::from_secs(2), async { + while started.load(Ordering::SeqCst) < 2 { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(20)).await; + assert!(started.load(Ordering::SeqCst) <= 5, "producer ran past its bounded queue"); + stop.send(true).unwrap(); + tokio::time::timeout(Duration::from_secs(2), task).await.unwrap().unwrap().unwrap(); + } +} diff --git a/bin/eth-proofs/src/main.rs b/bin/eth-proofs/src/main.rs index 6cf9658..5b8f382 100644 --- a/bin/eth-proofs/src/main.rs +++ b/bin/eth-proofs/src/main.rs @@ -9,7 +9,7 @@ use alloy_provider::{Provider, ProviderBuilder, WsConnect}; use clap::Parser; use cli::Args; use eth_proofs::EthProofsClient; -use futures::{channel::mpsc, future::ready, FutureExt, SinkExt, StreamExt}; +use futures::{channel::mpsc, future::ready, FutureExt, StreamExt}; use host_executor::{ alerting::AlertingClient, create_eth_block_execution_strategy_factory, EthExecutorComponents, FullExecutor, @@ -20,6 +20,7 @@ use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, Env use zkm_sdk::{include_elf, ProverClient}; mod cli; +mod consecutive; mod latest; mod state; @@ -57,10 +58,6 @@ async fn main() -> eyre::Result<()> { (1..=2).contains(&args.prepare_concurrency) && args.prefetch_depth > 0, "preparation concurrency must be 1 or 2 and queue depth must be positive" ); - eyre::ensure!( - args.latest_block || args.prepare_concurrency == 1, - "parallel preparation requires --latest-block" - ); let elf = match &args.elf_file { Some(path) => std::fs::read(path)?, None => include_elf!("reth").to_vec(), @@ -162,7 +159,7 @@ async fn main() -> eyre::Result<()> { None }; let head_shutdown = shutdown.clone(); - let mut stream = if state.is_some() { + let stream = if state.is_some() { futures::stream::unfold( (http_provider.clone(), None, head_shutdown), |(provider, mut previous, shutdown)| async move { @@ -231,7 +228,7 @@ async fn main() -> eyre::Result<()> { // idle for the RPC round trips. let executor = Arc::new(executor); let alerting_client = Arc::new(alerting_client); - let (mut tx, mut rx) = mpsc::channel(args.prefetch_depth.max(1)); + let (tx, mut rx) = mpsc::channel(args.prefetch_depth.max(1)); let (latest_tx, mut latest_rx) = tokio::sync::watch::channel(None); // Recover a completed proof before choosing a fresh head. Only this prover // owns the durable cursor; preparations never advance or delete its inputs. @@ -243,7 +240,7 @@ async fn main() -> eyre::Result<()> { } } } - let fetcher = { + let mut fetcher = { let executor = executor.clone(); let alerting_client = alerting_client.clone(); let max_lag = args.max_lag; @@ -277,89 +274,99 @@ async fn main() -> eyre::Result<()> { ) .await; } - // The subscription is only a wake-up: it says how far the chain - // is. Which block to prepare next is a cursor that advances by - // `block_interval`, so falling behind never skips a block — the - // backlog is worked through in order (unless `max_lag` says to - // drop it). A header stream that lags drops headers; the cursor - // does not. - let mut head = 0u64; - let mut next = state.as_ref().and_then(|s| s.next_block()).unwrap_or(0); - loop { - if *preparation_shutdown.borrow() { - break; - } - if next == 0 || next > head { - match stream.next().await { - Some(header) => { - head = head.max(header); - if next == 0 { - // The stream only delivers this instance's - // residue class, so the cursor starts aligned. - next = header - - ((header + block_interval - block_residue) % block_interval); - if let Some(state) = &state { - state.initialize(next)?; + // Headers bound how far ahead we may prepare. The durable cursor is + // advanced only by the prover, after committing each ordered output. + let next = state.as_ref().and_then(|s| s.next_block()).unwrap_or(0); + let blocks = futures::stream::try_unfold((0u64, next, stream), { + let state = state.clone(); + move |(mut head, mut next, mut stream)| { + let state = state.clone(); + async move { + loop { + if next == 0 || next > head { + match stream.next().await { + Some(header) => { + head = head.max(header); + if next == 0 { + next = header - + ((header + block_interval - block_residue) % + block_interval); + if let Some(state) = &state { + state.initialize(next)?; + } + } + continue; + } + None => return Ok::<_, eyre::Report>(None), } } - continue; - } - None => break, - } - } - // Absorb whatever headers are already queued so `head` is fresh. - while let Some(Some(header)) = stream.next().now_or_never() { - head = head.max(header); - } - - let number = next; - next += block_interval; - if max_lag > 0 && head - number > max_lag { - warn!("skipping block {number}: {} behind the newest header", head - number); - continue; - } - - if let Some(state) = &state { - if state.is_committed(number)? { - if tx.send((number, None)).await.is_err() { - break; + while let Some(Some(header)) = stream.next().now_or_never() { + head = head.max(header); + } + let number = next; + next = next + .checked_add(block_interval) + .ok_or_else(|| eyre::eyre!("preparation block cursor overflow"))?; + if max_lag > 0 && head - number > max_lag { + warn!( + "skipping block {number}: {} behind the newest header", + head - number + ); + continue; + } + return Ok(Some((number, (head, next, stream)))); } - continue; } } - // Retry the same block; a transient preparation failure must not skip it. - loop { - let prepared = match executor.wait_for_block(number).await { - Ok(()) => { - if let Some(state) = &state { - match state.input(number)? { - Some(bytes) => Ok(bincode::deserialize(&bytes)?), - None => executor.prepare(number).await, - } - } else { - executor.prepare(number).await - } - } - Err(err) => Err(err), - }; - match prepared { - Ok(client_input) => { - if let Some(state) = &state { - state.save_input(number, &bincode::serialize(&client_input)?)?; + }) + .boxed(); + let persist_state = state.clone(); + consecutive::run( + blocks, + prepare_concurrency, + tx, + preparation_shutdown, + move |number| { + let executor = executor.clone(); + let state = state.clone(); + let alerting_client = alerting_client.clone(); + async move { + if let Some(state) = &state { + if state.is_committed(number)? { + return Ok(None); } - if tx.send((number, Some(client_input))).await.is_err() { - return Ok::<(), eyre::Report>(()); + if let Some(bytes) = state.input(number)? { + return Ok(Some(bincode::deserialize(&bytes)?)); } - break; - } - Err(err) => { - report_error(&alerting_client, number, err).await; - tokio::time::sleep(Duration::from_secs(5)).await; } + let input = consecutive::retry( + number, + Duration::from_secs(5), + move |number| { + let executor = executor.clone(); + async move { + executor.wait_for_block(number).await?; + executor.prepare(number).await + } + }, + move |number, err| { + let alerting_client = alerting_client.clone(); + async move { report_error(&alerting_client, number, err).await } + }, + ) + .await; + Ok(Some(input)) } - } - } - Ok::<(), eyre::Report>(()) + .boxed() + }, + move |number, input| { + if let (Some(state), Some(input)) = (&persist_state, input) { + state.save_input(number, &bincode::serialize(input)?)?; + } + Ok(()) + }, + ) + .await }) }; @@ -440,6 +447,8 @@ async fn main() -> eyre::Result<()> { tokio::time::sleep(Duration::from_secs(3)).await; }; if block.header.hash != client_input.current_block.header.hash_slow() { + fetcher.abort(); + let _ = (&mut fetcher).await; state.discard_inputs_from(number)?; eyre::bail!( "prepared inputs invalidated by a reorg; restart from durable cursor" @@ -463,6 +472,7 @@ async fn main() -> eyre::Result<()> { } if *shutdown.borrow() { fetcher.abort(); + let _ = fetcher.await; } else { fetcher.await??; } diff --git a/bin/eth-proofs/src/state.rs b/bin/eth-proofs/src/state.rs index 571aaad..3cb4146 100644 --- a/bin/eth-proofs/src/state.rs +++ b/bin/eth-proofs/src/state.rs @@ -383,4 +383,28 @@ mod tests { assert!(state.is_committed(10).is_err()); assert!(state.advance(11, true).is_err()); } + + #[test] + fn restart_preserves_prefetch_and_reorg_discards_all_future_inputs() { + let root = tempfile::tempdir().unwrap(); + let state = State::open(root.path(), identity(), Some(10)).unwrap(); + for block in 10..14 { + state.save_input(block, &[block as u8]).unwrap(); + } + state.commit(10, b"proof", b"values", serde_json::json!({"block_number":10})).unwrap(); + state.advance(10, true).unwrap(); + drop(state); + let state = State::open(root.path(), identity(), None).unwrap(); + assert_eq!(state.next_block(), Some(11)); + for block in 11..14 { + assert_eq!(state.input(block).unwrap().unwrap(), [block as u8]); + } + state.discard_inputs_from(11).unwrap(); + assert_eq!(state.next_block(), Some(11)); + for block in 11..14 { + assert!(state.input(block).unwrap().is_none()); + } + assert!(state.is_committed(10).unwrap()); + assert_eq!(state.pending_count().unwrap(), 1); + } } diff --git a/bin/guest/Cargo.lock b/bin/guest/Cargo.lock index 489896c..26a1a2f 100644 --- a/bin/guest/Cargo.lock +++ b/bin/guest/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addchain" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +checksum = "3b2e69442aa5628ea6951fa33e24efe8313f4321a91bd729fc2f75bdfc858570" dependencies = [ "num-bigint 0.3.3", "num-integer", @@ -33,9 +33,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.34" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" +checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -46,16 +46,41 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.8.3" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-eips 1.0.41", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 1.0.41", + "alloy-trie", + "alloy-tx-macros 1.0.41", + "auto_impl", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-consensus" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" +checksum = "2e318e25fb719e747a7e8db1654170fc185024f3ed5b10f86c08d448a912f6e2" dependencies = [ - "alloy-eips", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 1.1.3", "alloy-trie", - "alloy-tx-macros", + "alloy-tx-macros 1.1.3", "auto_impl", "borsh", "c-kzg", @@ -63,25 +88,25 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.6", + "rand 0.8.5", "secp256k1", "serde", "serde_json", "serde_with", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "alloy-consensus-any" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" +checksum = "364380a845193a317bcb7a5398fc86cdb66c47ebe010771dde05f6869bf9e64a" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 1.1.3", "serde", ] @@ -95,7 +120,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -122,36 +147,42 @@ dependencies = [ "k256", "serde", "serde_with", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] -name = "alloy-eip7928" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" +name = "alloy-eips" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", "alloy-primitives", "alloy-rlp", - "borsh", - "once_cell", + "alloy-serde 1.0.41", + "auto_impl", + "c-kzg", + "derive_more", + "either", "serde", - "thiserror 2.0.18", + "serde_with", + "sha2", + "thiserror 2.0.17", ] [[package]] name = "alloy-eips" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" +checksum = "a4c4d7c5839d9f3a467900c625416b24328450c65702eb3d8caff8813e4d1d33" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 1.1.3", "auto_impl", "borsh", "c-kzg", @@ -160,6 +191,7 @@ dependencies = [ "serde", "serde_with", "sha2", + "thiserror 2.0.17", ] [[package]] @@ -168,26 +200,26 @@ version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527b47dc39850c6168002ddc1f7a2063e15d26137c1bb5330f6065a7524c1aa9" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-hardforks", "alloy-primitives", "alloy-sol-types", "auto_impl", "derive_more", "revm", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "alloy-genesis" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" +checksum = "1ba4b1be0988c11f0095a2380aa596e35533276b8fa6c9e06961bbfe0aebcac5" dependencies = [ - "alloy-eips", + "alloy-eips 1.1.3", "alloy-primitives", - "alloy-serde", + "alloy-serde 1.1.3", "alloy-trie", "borsh", "serde", @@ -196,9 +228,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.4.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +checksum = "2d9a33550fc21fd77a3f8b63e99969d17660eec8dcc50a95a80f7c9964f7680b" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -221,34 +253,34 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" +checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" dependencies = [ "alloy-primitives", "alloy-sol-types", "http", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", ] [[package]] name = "alloy-network" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" +checksum = "12aeb37b6f2e61b93b1c3d34d01ee720207c76fe447e2a2c217e433ac75b17f5" dependencies = [ - "alloy-consensus", + "alloy-consensus 1.1.3", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 1.1.3", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 1.1.3", "alloy-signer", "alloy-sol-types", "async-trait", @@ -257,41 +289,41 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "alloy-network-primitives" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" +checksum = "abd29ace62872083e30929cd9b282d82723196d196db589f3ceda67edcc05552" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", - "alloy-serde", + "alloy-serde 1.1.3", "serde", ] [[package]] name = "alloy-primitives" version = "1.4.1" -source = "git+https://github.com/ziren-patches/core.git?branch=patch-alloy-primitives-1.4.1#59313700f710f1373a5b4cdc05c51dc3227d8064" +source = "git+https://github.com/ziren-patches/core.git?branch=patch-alloy-primitives-1.4.1-hasher#8846a1c3b6f6595020ab4189034a6f463ffbffc7" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more", - "foldhash 0.2.0", + "foldhash", "hashbrown 0.16.1", - "indexmap 2.14.0", + "indexmap 2.12.1", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.9.4", + "rand 0.9.2", "ruint", "rustc-hash", "serde", @@ -302,9 +334,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.15" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -313,43 +345,43 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.15" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "alloy-rpc-types" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" +checksum = "39cf1398cb33aacb139a960fa3d8cf8b1202079f320e77e952a0b95967bf7a9f" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 1.1.3", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" +checksum = "6a63fb40ed24e4c92505f488f9dd256e2afaed17faa1b7a221086ebba74f4122" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 1.1.3", ] [[package]] name = "alloy-rpc-types-debug" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2145138f3214928f08cd13da3cb51ef7482b5920d8ac5a02ecd4e38d1a8f6d1e" +checksum = "4936f579d9d10eae01772b2ab3497f9d568684f05f26f8175e12f9a1a2babc33" dependencies = [ "alloy-primitives", "derive_more", @@ -359,12 +391,12 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb9b97b6e7965679ad22df297dda809b11cebc13405c1b537e5cffecc95834fa" +checksum = "4c60bdce3be295924122732b7ecd0b2495ce4790bedc5370ca7019c08ad3f26e" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", "derive_more", @@ -373,30 +405,40 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" +checksum = "9eae0c7c40da20684548cbc8577b6b7447f7bf4ddbac363df95e3da220e41e72" dependencies = [ - "alloy-consensus", + "alloy-consensus 1.1.3", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 1.1.3", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 1.1.3", "alloy-sol-types", "itertools 0.14.0", "serde", "serde_json", "serde_with", - "thiserror 2.0.18", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-serde" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", ] [[package]] name = "alloy-serde" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" +checksum = "c0df1987ed0ff2d0159d76b52e7ddfc4e4fbddacc54d2fbee765e0d14d7c01b5" dependencies = [ "alloy-primitives", "serde", @@ -405,9 +447,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.8.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" +checksum = "6ff69deedee7232d7ce5330259025b868c5e6a52fa8dffda2c861fb3a5889b24" dependencies = [ "alloy-primitives", "async-trait", @@ -415,46 +457,46 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "alloy-sol-macro" -version = "1.5.7" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.5.7" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.14.0", + "indexmap 2.12.1", "proc-macro-error2", "proc-macro2", "quote", - "sha3", - "syn 2.0.117", + "syn 2.0.111", "syn-solidity", + "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.7" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "const-hex", "dunce", @@ -462,18 +504,18 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.5.7" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -490,30 +532,42 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" dependencies = [ "alloy-primitives", "alloy-rlp", + "arrayvec", "derive_more", "nybbles", "serde", "smallvec", - "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-tx-macros" -version = "1.8.3" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" +checksum = "333544408503f42d7d3792bfc0f7218b643d968a03d2c0ed383ae558fb4a76d0" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -525,12 +579,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -660,7 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -698,7 +746,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -758,7 +806,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -768,7 +816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.5", ] [[package]] @@ -778,7 +826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.5", ] [[package]] @@ -788,7 +836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.5", ] [[package]] @@ -802,6 +850,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "async-trait" @@ -811,7 +862,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -832,7 +883,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -855,9 +906,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bincode" @@ -901,9 +952,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ "serde_core", ] @@ -949,8 +1000,10 @@ version = "0.8.0" source = "git+https://github.com/ziren-patches/bls12_381?branch=patch-0.8.0#bcf9397426410acc171ad625416b508dc913be8e" dependencies = [ "cfg-if", + "digest 0.10.7", "ff", "group", + "hex", "pairing", "rand_core 0.6.4", "subtle", @@ -971,42 +1024,32 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ "borsh-derive", - "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", + "syn 2.0.111", ] [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" @@ -1016,9 +1059,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] @@ -1031,7 +1074,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -1042,18 +1085,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] [[package]] name = "c-kzg" -version = "2.1.7" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" dependencies = [ "blst", "cc", @@ -1066,9 +1109,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -1090,9 +1133,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "num-traits", @@ -1102,9 +1145,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.19.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -1120,12 +1163,11 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.36" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", - "konst", ] [[package]] @@ -1189,9 +1231,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "critical-section" @@ -1254,9 +1296,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -1264,27 +1306,28 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ + "fnv", "ident_case", "proc-macro2", "quote", "serde", "strsim", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -1299,9 +1342,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -1320,35 +1363,35 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.6.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "derive_more" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.117", + "syn 2.0.111", "unicode-xid", ] @@ -1381,7 +1424,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -1419,7 +1462,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -1469,7 +1512,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -1500,9 +1543,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -1555,9 +1598,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixed-hash" @@ -1566,7 +1609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.6", + "rand 0.8.5", "rustc-hex", "static_assertions", ] @@ -1577,12 +1620,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" @@ -1606,26 +1643,26 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", "pin-project-lite", - "slab", + "pin-utils", ] [[package]] @@ -1647,9 +1684,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1664,21 +1701,8 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", + "r-efi", "wasip2", - "wasip3", ] [[package]] @@ -1702,12 +1726,14 @@ dependencies = [ name = "guest-executor" version = "0.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 1.1.3", "alloy-evm", "alloy-network", "alloy-primitives", "alloy-rpc-types", + "aurora-engine-modexp", "bincode", + "bls12_381", "itertools 0.13.0", "kzg-rs", "mpt", @@ -1728,7 +1754,9 @@ dependencies = [ "serde", "serde_json", "serde_with", + "substrate-bn", "thiserror 1.0.69", + "zkm-lib", ] [[package]] @@ -1744,7 +1772,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", - "foldhash 0.1.5", ] [[package]] @@ -1753,17 +1780,11 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - [[package]] name = "heck" version = "0.5.0" @@ -1821,9 +1842,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.65" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1845,13 +1866,12 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", - "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1859,9 +1879,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1872,9 +1892,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1886,15 +1906,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1906,15 +1926,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", @@ -1925,12 +1945,6 @@ dependencies = [ "zerovec", ] -[[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" @@ -1950,9 +1964,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1975,7 +1989,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -1997,12 +2011,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -2036,9 +2050,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.18" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" @@ -2052,12 +2066,10 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ - "cfg-if", - "futures-util", "once_cell", "wasm-bindgen", ] @@ -2079,38 +2091,23 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures 0.2.17", ] [[package]] name = "keccak-asm" -version = "0.1.6" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" 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 = "kzg-rs" version = "0.2.7" @@ -2133,35 +2130,29 @@ dependencies = [ "spin 0.9.8", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.186" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libm" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.12.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" @@ -2186,14 +2177,14 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "memchr" -version = "2.8.0" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "modular-bitfield" @@ -2276,9 +2267,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" @@ -2333,9 +2324,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -2343,20 +2334,20 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "nybbles" -version = "0.4.8" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +checksum = "2c4b5ecbd0beec843101bffe848217f770e8b8da81d8355b7d6e226f2199b3dc" dependencies = [ "alloy-rlp", "cfg-if", @@ -2368,9 +2359,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.4" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ "critical-section", "portable-atomic", @@ -2382,15 +2373,15 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726da827358a547be9f1e37c2a756b9e3729cb0350f43408164794b370cad8ae" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 1.1.3", "derive_more", "serde", "serde_with", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -2443,7 +2434,7 @@ dependencies = [ "p3-maybe-rayon", "p3-util", "paste", - "rand 0.10.3", + "rand 0.10.2", "serde", "tracing", ] @@ -2460,7 +2451,7 @@ dependencies = [ "p3-poseidon1", "p3-poseidon2", "p3-symmetric", - "rand 0.10.3", + "rand 0.10.2", ] [[package]] @@ -2472,7 +2463,7 @@ dependencies = [ "p3-field", "p3-maybe-rayon", "p3-util", - "rand 0.10.3", + "rand 0.10.2", "serde", "tracing", ] @@ -2491,7 +2482,7 @@ dependencies = [ "p3-field", "p3-symmetric", "p3-util", - "rand 0.10.3", + "rand 0.10.2", ] [[package]] @@ -2511,7 +2502,7 @@ dependencies = [ "p3-symmetric", "p3-util", "paste", - "rand 0.10.3", + "rand 0.10.2", "serde", "spin 0.10.1", "tracing", @@ -2524,7 +2515,7 @@ source = "git+https://github.com/ProjectZKM/Plonky3?rev=4dd0d47a8176503fbaa7293c dependencies = [ "p3-field", "p3-symmetric", - "rand 0.10.3", + "rand 0.10.2", ] [[package]] @@ -2536,7 +2527,7 @@ dependencies = [ "p3-mds", "p3-symmetric", "p3-util", - "rand 0.10.3", + "rand 0.10.2", ] [[package]] @@ -2593,7 +2584,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -2610,9 +2601,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", "ucd-trie", @@ -2649,7 +2640,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -2663,9 +2654,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" @@ -2679,21 +2676,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2713,16 +2710,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "primeorder" version = "0.13.1" @@ -2746,7 +2733,7 @@ dependencies = [ name = "primitives" version = "0.1.0" dependencies = [ - "alloy-eips", + "alloy-eips 1.1.3", "alloy-genesis", "alloy-rpc-types", "eyre", @@ -2759,9 +2746,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -2785,29 +2772,29 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.11.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ "bit-set", "bit-vec", "bitflags", "num-traits", - "rand 0.9.4", + "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -2824,9 +2811,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.45" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2837,12 +2824,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - [[package]] name = "radium" version = "0.7.0" @@ -2851,9 +2832,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2863,20 +2844,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.5", + "rand_core 0.9.3", "serde", ] [[package]] name = "rand" -version = "0.10.3" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "rand_core 0.10.1", ] @@ -2898,7 +2879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", + "rand_core 0.9.3", ] [[package]] @@ -2907,14 +2888,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.4", "serde", @@ -2932,14 +2913,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.5", + "rand_core 0.9.3", ] [[package]] name = "rayon" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -2972,14 +2953,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reth" @@ -2998,8 +2979,8 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-chains", - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -3017,8 +2998,8 @@ name = "reth-codecs" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-genesis", "alloy-primitives", "alloy-trie", @@ -3037,7 +3018,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -3045,12 +3026,12 @@ name = "reth-consensus" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", + "alloy-consensus 1.1.3", "alloy-primitives", "auto_impl", "reth-execution-types", "reth-primitives-traits", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3058,8 +3039,8 @@ name = "reth-consensus-common" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "reth-chainspec", "reth-consensus", "reth-primitives-traits", @@ -3070,7 +3051,7 @@ name = "reth-db-models" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-eips", + "alloy-eips 1.1.3", "alloy-primitives", "reth-primitives-traits", ] @@ -3083,7 +3064,7 @@ dependencies = [ "reth-consensus", "reth-execution-errors", "reth-storage-errors", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3091,8 +3072,8 @@ name = "reth-ethereum-consensus" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -3119,12 +3100,12 @@ name = "reth-ethereum-primitives" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 1.1.3", "reth-codecs", "reth-primitives-traits", "serde", @@ -3136,8 +3117,8 @@ name = "reth-evm" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-evm", "alloy-primitives", "auto_impl", @@ -3157,8 +3138,8 @@ name = "reth-evm-ethereum" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", @@ -3182,7 +3163,7 @@ dependencies = [ "alloy-rlp", "nybbles", "reth-storage-errors", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3190,8 +3171,8 @@ name = "reth-execution-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-evm", "alloy-primitives", "derive_more", @@ -3209,7 +3190,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "serde_with", - "thiserror 2.0.18", + "thiserror 2.0.17", "url", ] @@ -3218,8 +3199,8 @@ name = "reth-primitives-traits" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -3237,7 +3218,7 @@ dependencies = [ "secp256k1", "serde", "serde_with", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3248,7 +3229,7 @@ dependencies = [ "alloy-primitives", "derive_more", "strum", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3276,8 +3257,8 @@ name = "reth-storage-api" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -3298,7 +3279,7 @@ name = "reth-storage-errors" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-eips", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", "derive_more", @@ -3306,7 +3287,7 @@ dependencies = [ "reth-prune-types", "reth-static-file-types", "revm-database-interface", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3314,8 +3295,8 @@ name = "reth-trie" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 1.1.3", + "alloy-eips 1.1.3", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -3336,7 +3317,7 @@ name = "reth-trie-common" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-consensus", + "alloy-consensus 1.1.3", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -3375,8 +3356,7 @@ dependencies = [ [[package]] name = "revm" version = "31.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb67a5223602113cae59a305acde2d9936bc18f2478dda879a6124b267cebfb6" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "revm-bytecode", "revm-context", @@ -3394,8 +3374,7 @@ dependencies = [ [[package]] name = "revm-bytecode" version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c6b5e6e8dd1e28a4a60e5f46615d4ef0809111c9e63208e55b5c7058200fb0" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "bitvec", "phf", @@ -3406,9 +3385,9 @@ dependencies = [ [[package]] name = "revm-context" version = "11.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92850e150f4f99d46c05a20ad0cd09286a7ad4ee21866fffb87101de6e602231" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ + "alloy-consensus 1.0.41", "bitvec", "cfg-if", "derive-where", @@ -3423,9 +3402,9 @@ dependencies = [ [[package]] name = "revm-context-interface" version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d701e2c2347d65216b066489ab22a0a8e1f7b2568256110d73a7d5eff3385c" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ + "alloy-consensus 1.0.41", "alloy-eip2930", "alloy-eip7702", "auto_impl", @@ -3439,10 +3418,9 @@ dependencies = [ [[package]] name = "revm-database" version = "9.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980d8d6bba78c5dd35b83abbb6585b0b902eb25ea4448ed7bfba6283b0337191" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ - "alloy-eips", + "alloy-eips 1.0.41", "revm-bytecode", "revm-database-interface", "revm-primitives", @@ -3453,8 +3431,7 @@ dependencies = [ [[package]] name = "revm-database-interface" version = "8.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cce03e3780287b07abe58faf4a7f5d8be7e81321f93ccf3343c8f7755602bae" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "auto_impl", "either", @@ -3466,8 +3443,7 @@ dependencies = [ [[package]] name = "revm-handler" version = "12.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45418ed95cfdf0cb19effdbb7633cf2144cab7fb0e6ffd6b0eb9117a50adff6" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "auto_impl", "derive-where", @@ -3485,8 +3461,7 @@ dependencies = [ [[package]] name = "revm-inspector" version = "12.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99801eac7da06cc112df2244bd5a64024f4ef21240e923b26e73c4b4a0e5da6" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "auto_impl", "either", @@ -3502,8 +3477,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22789ce92c5808c70185e3bc49732f987dc6fd907f77828c8d3470b2299c9c65" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -3515,8 +3489,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "29.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968b124028960201abf6d6bf8e223f15fadebb4307df6b7dc9244a0aab5d2d05" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -3537,8 +3510,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "21.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "alloy-primitives", "num_enum", @@ -3549,8 +3521,7 @@ dependencies = [ [[package]] name = "revm-state" version = "8.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8be953b7e374dbdea0773cf360debed8df394ea8d82a8b240a6b5da37592fc" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2-mips32-dispatch#2a9844a05fc7541c05a067f5d3e32fc99489cd6d" dependencies = [ "bitflags", "revm-bytecode", @@ -3588,9 +3559,8 @@ dependencies = [ [[package]] name = "ruint" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +version = "1.17.0" +source = "git+https://github.com/ziren-patches/ruint?branch=patch-1.17.0#572265b0666ae9b1ce749b0f0ce051108ec7253c" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -3605,26 +3575,26 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.6", - "rand 0.9.4", + "rand 0.8.5", + "rand 0.9.2", "rlp", "ruint-macro", "serde_core", "valuable", "zeroize", + "zkm-lib", ] [[package]] name = "ruint-macro" version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +source = "git+https://github.com/ziren-patches/ruint?branch=patch-1.17.0#572265b0666ae9b1ce749b0f0ce051108ec7253c" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-hex" @@ -3647,14 +3617,14 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.28", + "semver 1.0.27", ] [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", @@ -3681,6 +3651,12 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "schemars" version = "0.9.0" @@ -3695,9 +3671,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", "ref-cast", @@ -3733,7 +3709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.6", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -3758,9 +3734,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.28" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "semver-parser" @@ -3807,36 +3783,35 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] name = "serde_with" -version = "3.20.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64", - "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.0", + "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.2.1", + "schemars 1.1.0", "serde_core", "serde_json", "serde_with_macros", @@ -3845,14 +3820,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.20.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -3877,9 +3852,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.9" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.7", "keccak", @@ -3887,9 +3862,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.6" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" dependencies = [ "cc", "cfg-if", @@ -3913,15 +3888,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "slab" -version = "0.4.12" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" @@ -3999,13 +3968,13 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "substrate-bn" version = "0.6.0" -source = "git+https://github.com/ziren-patches/bn?branch=patch-0.6.0#aba71380457d798039111e6cc0fdf2e0718c6766" +source = "git+https://github.com/ziren-patches/bn?branch=patch-0.6.0#509192f90ef14511bfa64681b2d7c8333df13979" dependencies = [ "bytemuck", "byteorder", @@ -4013,7 +3982,7 @@ dependencies = [ "crunchy", "lazy_static", "num-bigint 0.4.6", - "rand 0.8.6", + "rand 0.8.5", "rustc-hex", "zkm-lib", ] @@ -4037,9 +4006,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -4048,14 +4017,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.7" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -4066,7 +4035,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -4077,12 +4046,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.27.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys", @@ -4099,11 +4068,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.17", ] [[package]] @@ -4114,18 +4083,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -4139,30 +4108,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde_core", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -4179,57 +4148,42 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.12.1", "toml_datetime", "toml_parser", - "winnow 1.0.2", + "winnow", ] [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow 1.0.2", + "winnow", ] [[package]] @@ -4251,7 +4205,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -4275,9 +4229,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -4305,15 +4259,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-xid" @@ -4323,9 +4277,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "url" -version = "2.5.8" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -4367,27 +4321,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -4398,9 +4343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4408,60 +4353,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 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.14.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver 1.0.28", -] - [[package]] name = "windows-core" version = "0.62.2" @@ -4483,7 +4394,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -4494,7 +4405,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] @@ -4532,121 +4443,24 @@ dependencies = [ [[package]] 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.2" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" 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" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[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" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "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", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver 1.0.28", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wyz" @@ -4659,9 +4473,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4670,54 +4484,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "zerofrom" -version = "0.1.8" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", "synstructure", ] @@ -4732,20 +4546,20 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -4754,9 +4568,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -4765,19 +4579,18 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.111", ] [[package]] name = "zkm-lib" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" dependencies = [ "bincode", "cfg-if", @@ -4790,7 +4603,6 @@ dependencies = [ [[package]] name = "zkm-primitives" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" dependencies = [ "bincode", "blake3", @@ -4810,26 +4622,19 @@ dependencies = [ [[package]] name = "zkm-zkvm" version = "2.0.0" -source = "git+https://github.com/ProjectZKM/Ziren#0dd65a15db3d7bad5cf582c5dbc7492440ea2830" dependencies = [ "bincode", "cfg-if", - "getrandom 0.2.17", + "getrandom 0.2.16", "lazy_static", "libm", - "rand 0.8.6", + "rand 0.8.5", "serde", "sha2", "zkm-lib", "zkm-primitives", ] -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - [[package]] name = "zstd" version = "0.13.3" diff --git a/bin/guest/Cargo.toml b/bin/guest/Cargo.toml index c4ddad3..6a07ceb 100644 --- a/bin/guest/Cargo.toml +++ b/bin/guest/Cargo.toml @@ -18,7 +18,7 @@ bincode = "1.3.3" guest-executor = { path = "../../crates/executor/guest" } # Ziren -zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren" } +zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } # zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint" } # Statically turns off logging @@ -26,9 +26,35 @@ log = { version = "0.4", features = ["max_level_off", "release_max_level_off"] } tracing = { version = "0.1", features = ["max_level_off", "release_max_level_off"] } [patch.crates-io] -# Precompile patches +ruint = { git = "https://github.com/ziren-patches/ruint", branch = "patch-1.17.0" } +revm = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-bytecode = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-context = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-context-interface = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-database = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-database-interface = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-handler = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-inspector = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-interpreter = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-precompile = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-primitives = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +revm-state = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2-mips32-dispatch" } +# Precompile patches, and the revm workspace with its 32-bit interpreter +# fast paths (gas, stack exchange, memory words) and cheaper jumps (JUMPDEST +# folded into the jump, PUSH2 + JUMP / JUMPI run as one instruction). sha2 = { git = "https://github.com/ziren-patches/RustCrypto-hashes", branch = "patch-sha2-0.10.9", package = "sha2" } bn = { git = "https://github.com/ziren-patches/bn", branch = "patch-0.6.0", package = "substrate-bn" } k256 = { git = "https://github.com/ziren-patches/elliptic-curves", branch = "patch-k256-0.13.4" } p256 = { git = "https://github.com/ziren-patches/elliptic-curves", branch = "patch-p256-0.13.2" } -alloy-primitives-v1-4-1 = { git = "https://github.com/ziren-patches/core.git", package = "alloy-primitives", branch = "patch-alloy-primitives-1.4.1" } +alloy-primitives-v1-4-1 = { git = "https://github.com/ziren-patches/core.git", package = "alloy-primitives", branch = "patch-alloy-primitives-1.4.1-hasher" } + +# The guest's zkvm crates come from the canonical Ziren checkout next to this +# repo, the same way the GPU prover consumes it; check it out at the Ziren +# branch the dependencies above name. Every zkm-zkvm / zkm-lib in the graph +# (the precompile patches declare their own, unbranched, and cargo cannot +# redirect a git source to another branch of the same URL) resolves to this +# one source, so there is exactly one global allocator and the word-wise +# memcmp / zero-copy keccak reach every caller. +[patch."https://github.com/ProjectZKM/Ziren"] +zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint" } +zkm-lib = { path = "../../../Ziren/crates/zkvm/lib" } diff --git a/bin/guest/src/main.rs b/bin/guest/src/main.rs index 15832d3..8c147d6 100644 --- a/bin/guest/src/main.rs +++ b/bin/guest/src/main.rs @@ -5,10 +5,14 @@ use guest_executor::verify_block; use std::sync::Arc; pub fn main() { - // Read the input. - let input = zkm_zkvm::io::read_vec(); + // Read the input. Leaked on purpose: the witness byte streams inside it + // are deserialised as borrows of this buffer rather than copies of it + // (`mpt::arena::input_bytes`), so it has to outlive the whole run. The + // vector is leaked as read: its capacity is the length rounded up to a + // word, and shrinking it to a boxed slice copied the whole input. + let input: &'static [u8] = Vec::leak(zkm_zkvm::io::read_vec()); - let (block_hash, _, _) = verify_block(&input); + let (block_hash, _, _) = verify_block(input); // Commit the block hash. zkm_zkvm::io::commit(&block_hash); diff --git a/crates/executor/guest/Cargo.toml b/crates/executor/guest/Cargo.toml index a3a8692..7b77915 100644 --- a/crates/executor/guest/Cargo.toml +++ b/crates/executor/guest/Cargo.toml @@ -52,9 +52,16 @@ op-alloy-network = { workspace = true, optional = true } op-alloy-rpc-types = { workspace = true, optional = true } kzg-rs.workspace = true +bls12_381 = { git = "https://github.com/ziren-patches/bls12_381", branch = "patch-0.8.0", default-features = false, features = ["groups", "pairings", "alloc", "experimental"] } +aurora-engine-modexp = "1.2.0" +zkm-lib = { git = "https://github.com/ProjectZKM/Ziren", branch = "perf/shamir-msm" } +bn = { git = "https://github.com/ziren-patches/bn", branch = "patch-0.6.0", package = "substrate-bn" } [features] +# Cycle-tracker spans around every precompile call in the zkVM (profiling only: +# the wrapper turns the precompile set into a map probed on every call). +precompile-tracking = [] optimism = [ "dep:op-alloy-network", "dep:op-alloy-rpc-types", diff --git a/crates/executor/guest/examples/convert_input.rs b/crates/executor/guest/examples/convert_input.rs new file mode 100644 index 0000000..5f3c78c --- /dev/null +++ b/crates/executor/guest/examples/convert_input.rs @@ -0,0 +1,54 @@ +use alloy_consensus::{Block, Header}; +use alloy_primitives::{Address, B256}; +use guest_executor::io::{EthClientExecutorInput, RawCode}; +use mpt::WitnessState; +use primitives::genesis::Genesis; +use reth_ethereum_primitives::TransactionSigned; +use revm::state::Bytecode; +use serde::Deserialize; +use serde_with::serde_as; + +#[serde_as] +#[derive(Deserialize)] +struct OldInput { + #[serde_as( + as = "reth_primitives_traits::serde_bincode_compat::Block<'_, TransactionSigned, Header>" + )] + current_block: Block, + #[serde_as(as = "Vec")] + ancestor_headers: Vec
, + parent_state: WitnessState, + bytecodes: Vec, + code_hashes: Vec, + genesis: Genesis, + custom_beneficiary: Option
, + opcode_tracking: bool, +} + +fn main() { + let args: Vec = std::env::args().collect(); + let old: OldInput = bincode::deserialize(&std::fs::read(&args[1]).unwrap()).unwrap(); + let old_codes = old.bytecodes.clone(); + let new = EthClientExecutorInput { + current_block: old.current_block, + ancestor_headers: old.ancestor_headers, + parent_state: old.parent_state, + bytecodes: old.bytecodes.iter().map(|b| RawCode::new(b.original_byte_slice())).collect(), + code_hashes: old.code_hashes, + genesis: old.genesis, + custom_beneficiary: old.custom_beneficiary, + opcode_tracking: old.opcode_tracking, + }; + for (old, new) in old_codes.iter().zip(&new.bytecodes) { + let analyzed = new.analyze(); + assert_eq!(old.original_byte_slice(), analyzed.original_byte_slice()); + assert_eq!( + old.legacy_jump_table().map(|j| j.as_slice().to_vec()), + analyzed.legacy_jump_table().map(|j| j.as_slice().to_vec()), + "jump table" + ); + } + let bytes = bincode::serialize(&new).unwrap(); + std::fs::write(&args[2], &bytes).unwrap(); + println!("wrote {} bytes", bytes.len()); +} diff --git a/crates/executor/guest/src/custom.rs b/crates/executor/guest/src/custom.rs index 3487bdb..def27d9 100644 --- a/crates/executor/guest/src/custom.rs +++ b/crates/executor/guest/src/custom.rs @@ -6,7 +6,7 @@ //! configuring the custom CustomEvmConfig precompiles and instructions. use alloy_evm::{precompiles::PrecompilesMap, EthEvm}; -use kzg_rs::{Bytes32, Bytes48, KzgProof, KzgSettings}; +use kzg_rs::KzgSettings; use reth_evm::{eth::EthEvmBuilder, Database, EvmEnv, EvmFactory}; use revm::{ bytecode::opcode::OpCode, @@ -18,7 +18,10 @@ use revm::{ interpreter_types::{Jumps, LoopControl}, Interpreter, InterpreterTypes, }, - precompile::{Crypto, PrecompileError, PrecompileSpecId, Precompiles}, + precompile::{ + bls12_381::{G1Point, G1PointScalar, G2Point, G2PointScalar}, + Crypto, PrecompileError, PrecompileSpecId, Precompiles, + }, Context, Inspector, }; use revm_primitives::{hardfork::SpecId, Address}; @@ -70,7 +73,7 @@ impl EvmFactory for CustomEvmFactory { PrecompileSpecId::from_spec_id(input.cfg_env.spec), )); - #[cfg(target_os = "zkvm")] + #[cfg(all(target_os = "zkvm", feature = "precompile-tracking"))] precompiles.map_precompiles(|address, p| { use alloy_evm::precompiles::Precompile; use reth_evm::precompiles::PrecompileInput; @@ -157,18 +160,73 @@ impl Inspector for OpCodeTrackingInspect } } -#[derive(Debug)] pub struct CustomCrypto { - kzg_settings: KzgSettings, + kzg: crate::precompiles::KzgVerifier, +} + +impl Debug for CustomCrypto { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("CustomCrypto") + } } impl Default for CustomCrypto { fn default() -> Self { - Self { kzg_settings: KzgSettings::load_trusted_setup_file().unwrap() } + let settings = KzgSettings::load_trusted_setup_file().unwrap(); + Self { kzg: crate::precompiles::KzgVerifier::new(&settings) } } } impl Crypto for CustomCrypto { + fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileError> { + crate::precompiles::bn254_g1_add(p1, p2) + } + + fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileError> { + crate::precompiles::bn254_g1_mul(point, scalar) + } + + fn bls12_381_g1_add(&self, a: G1Point, b: G1Point) -> Result<[u8; 96], PrecompileError> { + crate::precompiles::bls12_381_g1_add(a, b) + } + + fn bls12_381_g1_msm( + &self, + pairs: &mut dyn Iterator>, + ) -> Result<[u8; 96], PrecompileError> { + crate::precompiles::bls12_381_g1_msm(pairs) + } + + fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192], PrecompileError> { + crate::precompiles::bls12_381_g2_add(a, b) + } + + fn bls12_381_g2_msm( + &self, + pairs: &mut dyn Iterator>, + ) -> Result<[u8; 192], PrecompileError> { + crate::precompiles::bls12_381_g2_msm(pairs) + } + + fn bls12_381_pairing_check( + &self, + pairs: &[(G1Point, G2Point)], + ) -> Result { + crate::precompiles::bls12_381_pairing_check(pairs) + } + + fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], PrecompileError> { + crate::precompiles::bls12_381_fp_to_g1(fp) + } + + fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], PrecompileError> { + crate::precompiles::bls12_381_fp2_to_g2(fp2) + } + + fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result, PrecompileError> { + Ok(crate::precompiles::modexp(base, exp, modulus)) + } + fn verify_kzg_proof( &self, z: &[u8; 32], @@ -176,18 +234,9 @@ impl Crypto for CustomCrypto { commitment: &[u8; 48], proof: &[u8; 48], ) -> Result<(), PrecompileError> { - if !KzgProof::verify_kzg_proof( - &Bytes48(*commitment), - &Bytes32(*z), - &Bytes32(*y), - &Bytes48(*proof), - &self.kzg_settings, - ) - .map_err(|err| PrecompileError::other(err.to_string()))? - { + if !self.kzg.verify(z, y, commitment, proof)? { return Err(PrecompileError::BlobVerifyKzgProofFailed); } - Ok(()) } } diff --git a/crates/executor/guest/src/error.rs b/crates/executor/guest/src/error.rs index 557bd8e..23153c0 100644 --- a/crates/executor/guest/src/error.rs +++ b/crates/executor/guest/src/error.rs @@ -11,6 +11,8 @@ pub enum ClientError { MismatchedStateRoot, #[error("Mismatched storage root after executing the block")] MismatchedStorageRoot, + #[error("Invalid state witness: {0}")] + InvalidWitness(String), #[error("unknown chain ID: {}", .0)] UnknownChainId(u64), #[error("Missing bytecode for account {}", .0)] diff --git a/crates/executor/guest/src/executor.rs b/crates/executor/guest/src/executor.rs index 3e4dca8..a5bd665 100644 --- a/crates/executor/guest/src/executor.rs +++ b/crates/executor/guest/src/executor.rs @@ -11,13 +11,13 @@ use reth_evm::{ use reth_evm_ethereum::EthEvmConfig; use reth_execution_types::ExecutionOutcome; use reth_primitives_traits::Block; -use reth_trie::KeccakKeyHasher; use revm::{database::WrapDatabaseRef, install_crypto}; use revm_primitives::Address; use crate::{ custom::{CustomCrypto, CustomEvmFactory}, error::ClientError, + hashed_keys::MemoKeyHasher, into_primitives::FromInput, io::{ClientExecutorInput, TrieDB, WitnessInput}, tracking::OpCodesTrackingBlockExecutor, @@ -52,15 +52,17 @@ where { pub fn execute( &self, - mut input: ClientExecutorInput, + input: ClientExecutorInput, ) -> Result<(Header, B256), ClientError> { let sealed_headers = input.sealed_headers().collect::>(); - // Initialize the witnessed database with verified storage proofs. - let db = profile_report!(INIT_WITNESS_DB, { - let trie_db = input.witness_db(&sealed_headers).unwrap(); - WrapDatabaseRef(trie_db) - }); + // Materialise the witnessed state (every node hash-checked) and wrap it + // as the execution database. + let mut state = profile_report!(INIT_WITNESS_DB, { + mpt::ArenaState::from_witness(&input.parent_state) + .map_err(|e| ClientError::InvalidWitness(e.to_string())) + })?; + let db = WrapDatabaseRef(input.witness_db(&state, &sealed_headers)?); let chain_id: u64 = (&input.genesis).try_into().expect("convert chain id err"); @@ -77,7 +79,7 @@ where profile_report!(BLOCK_EXECUTION, { block_executor.execute(&block) })?; // Validate the block post execution. - profile_report!(VALIDATE_EXECUTION, { + let checked_bloom = profile_report!(VALIDATE_EXECUTION, { C::Primitives::validate_block_post_execution( &block, self.chain_spec.clone(), @@ -85,13 +87,21 @@ where ) })?; - // Accumulate the logs bloom. - let mut logs_bloom = Bloom::default(); - profile_report!(ACCRUE_LOG_BLOOM, { - execution_output.result.receipts.iter().for_each(|r| { - logs_bloom.accrue_bloom(&r.bloom()); - }) - }); + // The logs bloom: the one validation checked against the receipts, + // else accumulated here. A receipt bloom hashes every log address and + // topic, so accumulating it again cost a keccak per log entry. + let logs_bloom = match checked_bloom { + Some(bloom) => bloom, + None => { + let mut logs_bloom = Bloom::default(); + profile_report!(ACCRUE_LOG_BLOOM, { + execution_output.result.receipts.iter().for_each(|r| { + logs_bloom.accrue_bloom(&r.bloom()); + }) + }); + logs_bloom + } + }; // Convert the output to an execution outcome. let executor_outcome = ExecutionOutcome::new( @@ -101,12 +111,12 @@ where vec![execution_output.result.requests], ); - let parent_state_root = input.parent_state.state_root(); + let parent_state_root = input.parent_state.state_root; // Verify the state root. let state_root = profile_report!(COMPUTE_STATE_ROOT, { - input.parent_state.update(&executor_outcome.hash_state_slow::()); - input.parent_state.state_root() + state.update(&executor_outcome.hash_state_slow::()); + state.state_root() }); if state_root != input.current_block.header().state_root() { diff --git a/crates/executor/guest/src/hashed_keys.rs b/crates/executor/guest/src/hashed_keys.rs new file mode 100644 index 0000000..5cae19f --- /dev/null +++ b/crates/executor/guest/src/hashed_keys.rs @@ -0,0 +1,72 @@ +//! Memoised keccak of trie keys. +//! +//! A block hashes the same account addresses and storage slots many times: +//! [`TrieDB`](crate::io::TrieDB) hashes both on every read, and the post-state +//! root pass hashes every changed account and slot again. In the zkVM each of +//! those is a KeccakSponge precompile call, and that chip is about a third of a +//! block's proving area, so a repeat costs far more than the map probe that +//! avoids it (block 25955640: 17,311 of its 22,298 address and slot hashes were +//! repeats). +//! +//! The memo only ever holds digests this execution computed itself, so a hit +//! returns exactly what a fresh keccak would. Off the zkVM the functions hash +//! directly: the host runs one block after another in one process, where a +//! per-thread memo would only grow. + +use alloy_primitives::{keccak256, Address, B256}; +use reth_trie::KeyHasher; + +#[cfg(target_os = "zkvm")] +mod memo { + use std::cell::RefCell; + + use alloy_primitives::{keccak256, map::HashMap, Address, B256}; + + thread_local! { + static ADDRESSES: RefCell> = RefCell::new(HashMap::default()); + static SLOTS: RefCell> = RefCell::new(HashMap::default()); + } + + pub(super) fn address(address: Address) -> B256 { + ADDRESSES.with(|m| *m.borrow_mut().entry(address).or_insert_with(|| keccak256(address))) + } + + pub(super) fn slot(slot: B256) -> B256 { + SLOTS.with(|m| *m.borrow_mut().entry(slot).or_insert_with(|| keccak256(slot))) + } +} + +/// `keccak256(address)`, the account's key in the state trie. +#[inline] +pub fn hashed_address(address: Address) -> B256 { + #[cfg(target_os = "zkvm")] + return memo::address(address); + #[cfg(not(target_os = "zkvm"))] + keccak256(address) +} + +/// `keccak256(slot)`, the slot's key in its storage trie. +#[inline] +pub fn hashed_slot(slot: B256) -> B256 { + #[cfg(target_os = "zkvm")] + return memo::slot(slot); + #[cfg(not(target_os = "zkvm"))] + keccak256(slot) +} + +/// [`KeccakKeyHasher`](reth_trie::KeccakKeyHasher) through the memo: the +/// post-state pass hands it 20-byte addresses and 32-byte slots. +#[derive(Clone, Debug, Default)] +pub struct MemoKeyHasher; + +impl KeyHasher for MemoKeyHasher { + #[inline] + fn hash_key>(bytes: T) -> B256 { + let bytes = bytes.as_ref(); + match bytes.len() { + 20 => hashed_address(Address::from_slice(bytes)), + 32 => hashed_slot(B256::from_slice(bytes)), + _ => keccak256(bytes), + } + } +} diff --git a/crates/executor/guest/src/into_primitives.rs b/crates/executor/guest/src/into_primitives.rs index d074190..c160869 100644 --- a/crates/executor/guest/src/into_primitives.rs +++ b/crates/executor/guest/src/into_primitives.rs @@ -1,8 +1,9 @@ use std::sync::Arc; -use alloy_consensus::{Block, Header, TxEnvelope}; +use alloy_consensus::{Block, BlockHeader, Header, TxEnvelope}; use alloy_network::{Ethereum, Network}; -use reth_chainspec::{ChainSpec, EthChainSpec, NamedChain}; +use alloy_primitives::Bloom; +use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks, NamedChain}; use reth_consensus::HeaderValidator; use reth_consensus_common::validation::validate_body_against_header; use reth_errors::ConsensusError; @@ -39,11 +40,14 @@ pub trait BlockValidator: NodePrimitives { chain_spec: Arc, ) -> Result<(), ConsensusError>; + /// Validates the execution output against the block, and returns the + /// logs bloom that validation checked equal to the receipts' blooms, if + /// it checked one, so the caller need not accumulate it again. fn validate_block_post_execution( block: &RecoveredBlock, chain_spec: Arc, execution_output: &BlockExecutionOutput, - ) -> Result<(), ConsensusError>; + ) -> Result, ConsensusError>; } impl IntoPrimitives for EthPrimitives { @@ -104,13 +108,17 @@ impl BlockValidator for EthPrimitives { block: &RecoveredBlock, chain_spec: Arc, execution_output: &BlockExecutionOutput, - ) -> Result<(), ConsensusError> { + ) -> Result, ConsensusError> { reth_ethereum_consensus::validate_block_post_execution( block, &chain_spec, &execution_output.result.receipts, &execution_output.result.requests, - ) + )?; + // From Byzantium on, `verify_receipts` has compared the bloom of the + // receipts with the header's. + let header = block.header(); + Ok(chain_spec.is_byzantium_active_at_block(header.number()).then(|| header.logs_bloom())) } } @@ -184,12 +192,13 @@ impl BlockValidator block: &RecoveredBlock, chain_spec: Arc, execution_output: &BlockExecutionOutput, - ) -> Result<(), ConsensusError> { + ) -> Result, ConsensusError> { reth_optimism_consensus::validate_block_post_execution( block.header(), &chain_spec, &execution_output.result, - ) + )?; + Ok(None) } } diff --git a/crates/executor/guest/src/io.rs b/crates/executor/guest/src/io.rs index 110f7f4..75c0caa 100644 --- a/crates/executor/guest/src/io.rs +++ b/crates/executor/guest/src/io.rs @@ -3,21 +3,24 @@ use std::iter::once; use alloy_consensus::{Block, BlockHeader, Header}; use alloy_primitives::map::HashMap; use itertools::Itertools; -use mpt::EthereumState; +use mpt::{ArenaState, WitnessState}; use primitives::genesis::Genesis; use reth_errors::ProviderError; use reth_ethereum_primitives::EthPrimitives; use reth_primitives_traits::{NodePrimitives, SealedHeader}; -use reth_trie::{TrieAccount, EMPTY_ROOT_HASH}; use revm::{ + bytecode::{eip7702::EIP7702_MAGIC_BYTES, JumpTable, LegacyAnalyzedBytecode}, state::{AccountInfo, Bytecode}, DatabaseRef, }; -use revm_primitives::{keccak256, Address, B256, U256}; +use revm_primitives::{keccak256, Address, Bytes, B256, U256}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -use crate::error::ClientError; +use crate::{ + error::ClientError, + hashed_keys::{hashed_address, hashed_slot}, +}; pub type EthClientExecutorInput = ClientExecutorInput; @@ -41,10 +44,19 @@ pub struct ClientExecutorInput { /// to provide the parent state root. #[serde_as(as = "Vec")] pub ancestor_headers: Vec
, - /// Network state as of the parent block. - pub parent_state: EthereumState, - /// Account bytecodes. - pub bytecodes: Vec, + /// Network state as of the parent block, in witness form (root hashes and + /// preorder streams of raw RLP nodes); the guest materialises it into an + /// [`ArenaState`], checking every node against the digest that references it. + pub parent_state: WitnessState, + /// Account bytecodes, as deployed: the guest derives the jump table + /// itself, since one taken from the host would decide which jumps are + /// valid without being bound by the code hash. + pub bytecodes: Vec, + /// The code hash the host claims for each entry of `bytecodes`; the guest + /// checks a claim the first time that code executes, so code that is only + /// shipped (touched accounts that never run) is never hashed. + #[serde(default)] + pub code_hashes: Vec, /// The genesis block, as a json string. pub genesis: Genesis, /// The genesis block, as a json string. @@ -60,15 +72,19 @@ impl ClientExecutorInput

{ self.ancestor_headers.last().unwrap() } - /// Creates a [`WitnessDb`]. - pub fn witness_db(&self, sealed_headers: &[SealedHeader]) -> Result, ClientError> { - ::witness_db(self, sealed_headers) + /// Creates a [`TrieDB`] over the materialised `state`. + pub fn witness_db<'a>( + &'a self, + state: &'a ArenaState, + sealed_headers: &[SealedHeader], + ) -> Result, ClientError> { + ::witness_db(self, state, sealed_headers) } } impl WitnessInput for ClientExecutorInput

{ #[inline(always)] - fn state(&self) -> &EthereumState { + fn state(&self) -> &WitnessState { &self.parent_state } @@ -78,10 +94,15 @@ impl WitnessInput for ClientExecutorInput

{ } #[inline(always)] - fn bytecodes(&self) -> impl Iterator { + fn bytecodes(&self) -> impl Iterator { self.bytecodes.iter() } + #[inline(always)] + fn code_hashes(&self) -> &[B256] { + &self.code_hashes + } + #[inline(always)] fn sealed_headers(&self) -> impl Iterator { self.ancestor_headers @@ -91,20 +112,126 @@ impl WitnessInput for ClientExecutorInput

{ } } +/// A contract's code as deployed followed by [`RawCode::PADDING`] zero +/// bytes, read from the input without a copy. The zeros are the `STOP`s the +/// interpreter runs into past the end of the code, at most a 32-byte push +/// operand and one `STOP`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RawCode(#[serde(deserialize_with = "mpt::input_bytes")] pub Bytes); + +impl RawCode { + pub const PADDING: usize = 33; + + /// `code` with its padding. + pub fn new(code: &[u8]) -> Self { + let mut padded = Vec::with_capacity(code.len() + Self::PADDING); + padded.extend_from_slice(code); + padded.resize(code.len() + Self::PADDING, 0); + Self(padded.into()) + } + + /// The code as deployed. + pub fn code(&self) -> &[u8] { + &self.0[..self.0.len() - Self::PADDING] + } + + /// The analyzed bytecode: its jump table derived here, the padding + /// checked to be zeros. + /// + /// # Panics + /// Panics if the padding is missing or not zero. + pub fn analyze(&self) -> Bytecode { + let len = self.0.len().checked_sub(Self::PADDING).expect("code padding"); + assert!(self.0[len..].iter().all(|&b| b == 0), "code padding is not zero"); + let code = &self.0[..len]; + if code.starts_with(&EIP7702_MAGIC_BYTES) { + return Bytecode::new_raw(self.0.slice(..len)); + } + let jump_table = JumpTable::from_bytes(jump_table(&self.0, len).into(), len); + Bytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new(self.0.clone(), len, jump_table)) + } +} + +/// Bytes an opcode spans, its own byte included: `n + 1` for `PUSHn`, else 1. +const OPCODE_SPAN: [u8; 256] = { + let mut span = [1u8; 256]; + let mut n = 1; + while n <= 32 { + span[0x5f + n] = n as u8 + 1; + n += 1; + } + span +}; + +#[cfg(target_os = "zkvm")] +core::arch::global_asm!(include_str!("jumpdest.s")); + +#[cfg(target_os = "zkvm")] +extern "C" { + /// [`jump_table_walk`] in assembly (`jumpdest.s`): `code` readable up to + /// `len + 32`, `table` holding `⌈len/8⌉` zeroed bytes. + fn reth_jumpdest_scan(code: *const u8, len: usize, table: *mut u8); +} + +/// The `JUMPDEST` bitmap of the first `len` bytes of `padded`, bit `i & 7` of +/// byte `i >> 3` set when byte `i` starts an instruction and is `0x5b`. +/// +/// `padded` holds the code's [`RawCode::PADDING`] bytes past `len`. The walk +/// is six cycles per opcode in the zkVM against nine compiled, which saves +/// ~5.6 M of the ~400 M cycles of a mainnet block (~3 M opcodes walked). +fn jump_table(padded: &[u8], len: usize) -> Vec { + assert!(len + RawCode::PADDING <= padded.len()); + let mut table = vec![0u8; len.div_ceil(8)]; + // SAFETY: `padded` is readable up to `len + 32` and `table` holds + // `⌈len/8⌉` zeroed bytes, as the walk requires. + #[cfg(target_os = "zkvm")] + unsafe { + reth_jumpdest_scan(padded.as_ptr(), len, table.as_mut_ptr()) + }; + #[cfg(not(target_os = "zkvm"))] + jump_table_walk(padded, len, &mut table); + table +} + +/// The walk of [`jump_table`] in Rust. +/// +/// It reads only bytes below `len`; a push operand may carry the cursor up to +/// 32 bytes past it, which ends the walk. The raw pointers are sound because +/// the cursor is compared against the end before every read and +/// `i >> 3 < ⌈len/8⌉` for every `i < len`. +#[cfg_attr(target_os = "zkvm", allow(dead_code))] +fn jump_table_walk(padded: &[u8], len: usize, table: &mut [u8]) { + assert!(len <= padded.len() && table.len() == len.div_ceil(8)); + let base = padded.as_ptr(); + let end = base.wrapping_add(len); + let mut p = base; + while p < end { + let op = unsafe { *p }; + if op == revm::bytecode::opcode::JUMPDEST { + let i = p as usize - base as usize; + unsafe { *table.get_unchecked_mut(i >> 3) |= 1 << (i & 7) }; + } + p = p.wrapping_add(OPCODE_SPAN[op as usize] as usize); + } +} + #[derive(Debug)] pub struct TrieDB<'a> { - inner: &'a EthereumState, + inner: &'a ArenaState, block_hashes: HashMap, - bytecode_by_hash: HashMap, + /// Keyed by the host's CLAIMED hash; a claim is trusted only after + /// `verified_code` holds its analysis. + bytecode_by_hash: HashMap, + verified_code: core::cell::RefCell>, } impl<'a> TrieDB<'a> { pub fn new( - inner: &'a EthereumState, + inner: &'a ArenaState, block_hashes: HashMap, - bytecode_by_hash: HashMap, + bytecode_by_hash: HashMap, ) -> Self { - Self { inner, block_hashes, bytecode_by_hash } + Self { inner, block_hashes, bytecode_by_hash, verified_code: Default::default() } } } @@ -122,10 +249,10 @@ impl DatabaseRef for TrieDB<'_> { return Ok(Some(AccountInfo::default())) } - let hashed_address = keccak256(address); + let hashed_address = hashed_address(address); let hashed_address = hashed_address.as_slice(); - let account_in_trie = self.inner.state_trie.get_rlp::(hashed_address).unwrap(); + let account_in_trie = self.inner.account(hashed_address).unwrap(); let account = account_in_trie.map(|account_in_trie| AccountInfo { balance: account_in_trie.balance, @@ -138,35 +265,28 @@ impl DatabaseRef for TrieDB<'_> { } /// Get account code by its hash. + /// + /// The claimed hash is checked, and the jump table derived, the first time + /// the code is used; a wrong claim aborts the execution. fn code_by_hash_ref(&self, hash: B256) -> Result { - Ok(self.bytecode_by_hash.get(&hash).map(|code| (*code).clone()).unwrap()) + if let Some(code) = self.verified_code.borrow().get(&hash) { + return Ok(code.clone()); + } + let raw = *self.bytecode_by_hash.get(&hash).expect("bytecode for hash must be provided"); + assert_eq!(keccak256(raw.code()), hash, "bytecode does not match its claimed hash"); + let code = raw.analyze(); + self.verified_code.borrow_mut().insert(hash, code.clone()); + Ok(code) } /// Get storage value of address at index. fn storage_ref(&self, address: Address, index: U256) -> Result { - let hashed_address = keccak256(address); - let hashed_address = hashed_address.as_slice(); - - let storage_trie = match self.inner.storage_tries.get(hashed_address) { - Some(trie) => trie, - None => { - // A witness may omit storage for an absent account or an account - // whose authenticated storage root is empty. Never substitute - // zero for an unresolved account proof or non-empty storage. - let account = self.inner.state_trie.get_rlp::(hashed_address) - .map_err(|err| ProviderError::TrieWitnessError(err.to_string()))?; - if account.map_or(EMPTY_ROOT_HASH, |a| a.storage_root) == EMPTY_ROOT_HASH { - return Ok(U256::ZERO); - } - return Err(ProviderError::TrieWitnessError(format!( - "missing non-empty storage trie for {address}" - ))); - } - }; + let hashed_address = hashed_address(address); - Ok(storage_trie - .get_rlp::(keccak256(index.to_be_bytes::<32>()).as_slice()) - .expect("Can get from MPT") + Ok(self + .inner + .storage(&hashed_address, hashed_slot(B256::from(index.to_be_bytes::<32>())).as_slice()) + .map_err(|err| ProviderError::TrieWitnessError(err.to_string()))? .unwrap_or_default()) } @@ -181,15 +301,18 @@ impl DatabaseRef for TrieDB<'_> { /// A trait for constructing [`WitnessDb`]. pub trait WitnessInput { - /// Gets a reference to the state from which account info and storage slots are loaded. - fn state(&self) -> &EthereumState; + /// Gets the witness state from which account info and storage slots are loaded. + fn state(&self) -> &WitnessState; /// Gets the state trie root hash that the state referenced by /// [state()](trait.WitnessInput#tymethod.state) must conform to. fn state_anchor(&self) -> B256; - /// Gets an iterator over account bytecodes. - fn bytecodes(&self) -> impl Iterator; + /// Gets an iterator over account bytecodes, as deployed. + fn bytecodes(&self) -> impl Iterator; + + /// The claimed code hash of each bytecode, in the same order. + fn code_hashes(&self) -> &[B256]; /// Gets an iterator over references to a consecutive, reverse-chronological block headers /// starting from the current block header. @@ -203,24 +326,29 @@ pub trait WitnessInput { /// implementing this trait causes a zkVM run to cost over 5M cycles more. To avoid this, define /// a method inside the type that calls this trait method instead. #[inline(always)] - fn witness_db(&self, sealed_headers: &[SealedHeader]) -> Result, ClientError> { - let state = self.state(); - - if self.state_anchor() != state.state_root() { + fn witness_db<'a>( + &'a self, + state: &'a ArenaState, + sealed_headers: &[SealedHeader], + ) -> Result, ClientError> { + // `state` was built from `self.state()`: every node keccak-checked + // against its parent's digest, the state trie against + // `state_root`, every storage trie against its account's storage + // root. What remains is binding that root to the parent header. + if self.state_anchor() != self.state().state_root { return Err(ClientError::MismatchedStateRoot); } - for (hashed_address, storage_trie) in state.storage_tries.iter() { - let account = - state.state_trie.get_rlp::(hashed_address.as_slice()).unwrap(); - let storage_root = account.map_or(EMPTY_ROOT_HASH, |a| a.storage_root); - if storage_root != storage_trie.hash() { - return Err(ClientError::MismatchedStorageRoot); - } + // Bytecodes are indexed by the host's claimed hash and verified on + // first use (see `TrieDB::code_by_hash_ref`); hashing every shipped + // contract up front was ~8 M cycles on a reth block, much of it for + // code that never runs. + let claimed = self.code_hashes(); + if claimed.len() != self.bytecodes().count() { + return Err(ClientError::InvalidWitness("code_hashes/bytecodes length mismatch".into())); } - let bytecodes_by_hash = - self.bytecodes().map(|code| (code.hash_slow(), code)).collect::>(); + claimed.iter().copied().zip(self.bytecodes()).collect::>(); // Verify and build block hashes let mut block_hashes: HashMap = HashMap::with_hasher(Default::default()); @@ -246,38 +374,3 @@ pub trait WitnessInput { Ok(TrieDB::new(state, block_hashes, bytecodes_by_hash)) } } - -#[cfg(test)] -mod tests { - use super::*; - - fn state() -> EthereumState { - EthereumState { state_trie: Default::default(), storage_tries: HashMap::default() } - } - - #[test] - fn omitted_storage_for_absent_account_is_zero() { - let state = state(); - let db = TrieDB::new(&state, HashMap::default(), HashMap::default()); - assert_eq!(db.storage_ref(Address::repeat_byte(1), U256::from(7)).unwrap(), U256::ZERO); - } - - #[test] - fn omitted_storage_with_authenticated_empty_root_is_zero() { - let mut state = state(); - let address = Address::repeat_byte(2); - state.state_trie.insert_rlp(keccak256(address).as_slice(), TrieAccount::default()).unwrap(); - let db = TrieDB::new(&state, HashMap::default(), HashMap::default()); - assert_eq!(db.storage_ref(address, U256::from(7)).unwrap(), U256::ZERO); - } - - #[test] - fn omitted_non_empty_storage_is_rejected() { - let mut state = state(); - let address = Address::repeat_byte(3); - let account = TrieAccount { storage_root: B256::repeat_byte(4), ..Default::default() }; - state.state_trie.insert_rlp(keccak256(address).as_slice(), account).unwrap(); - let db = TrieDB::new(&state, HashMap::default(), HashMap::default()); - assert!(db.storage_ref(address, U256::from(7)).is_err()); - } -} diff --git a/crates/executor/guest/src/jumpdest.s b/crates/executor/guest/src/jumpdest.s new file mode 100644 index 0000000..0bc12a9 --- /dev/null +++ b/crates/executor/guest/src/jumpdest.s @@ -0,0 +1,71 @@ +// reth_jumpdest_scan(code: *const u8, len: usize, table: *mut u8) +// +// The JUMPDEST bitmap of `code[..len]` for the zkVM guest: bit `i & 7` of +// `table[i >> 3]` is set when byte `i` starts an instruction and is 0x5b. +// `table` holds ceil(len / 8) zeroed bytes, and `code` must be readable up to +// `len + 32`: the RawCode padding, which the walk reads ahead into. +// +// The compiled walk spends nine instructions per opcode, two of them nops in +// branch delay slots. Here the slots carry work: the cursor advance sits in +// the JUMPDEST test's slot and the next opcode's load in the loop branch's, +// six instructions per opcode. The load ahead reads at most 32 bytes past +// `len` (a PUSH32 at `len - 1`), inside the padding. + .section .rodata.reth_jumpdest_span,"a",@progbits + .p2align 2 +.Lreth_jumpdest_span: + # bytes an opcode spans: n + 1 for PUSHn (0x60..0x7f), else 1 + .fill 96, 1, 1 + .byte 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 + .byte 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33 + .fill 128, 1, 1 + + .text + .globl reth_jumpdest_scan + .p2align 2 + .type reth_jumpdest_scan,@function + .set nomicromips + .set nomips16 + .ent reth_jumpdest_scan +reth_jumpdest_scan: + .frame $sp,0,$ra + .set noreorder + .set nomacro + .set noat + beqz $5, .Lreth_jumpdest_done + addu $9, $4, $5 + lui $10, %hi(.Lreth_jumpdest_span) + addiu $10, $10, %lo(.Lreth_jumpdest_span) + addiu $11, $zero, 0x5b + move $12, $4 + lbu $13, 0($12) +.Lreth_jumpdest_loop: + # $12 cursor, $13 the opcode under it, $9 end + addu $14, $10, $13 + lbu $14, 0($14) + beq $13, $11, .Lreth_jumpdest_mark + addu $12, $12, $14 +.Lreth_jumpdest_next: + sltu $1, $12, $9 + bnez $1, .Lreth_jumpdest_loop + lbu $13, 0($12) +.Lreth_jumpdest_done: + jr $ra + nop +.Lreth_jumpdest_mark: + # the cursor is one past the JUMPDEST: its index is $12 - 1 - code + subu $14, $12, $4 + addiu $14, $14, -1 + srl $15, $14, 3 + addu $15, $6, $15 + lbu $24, 0($15) + andi $14, $14, 7 + addiu $25, $zero, 1 + sllv $25, $25, $14 + or $24, $24, $25 + b .Lreth_jumpdest_next + sb $24, 0($15) + .set at + .set macro + .set reorder + .end reth_jumpdest_scan + .size reth_jumpdest_scan, .-reth_jumpdest_scan diff --git a/crates/executor/guest/src/lib.rs b/crates/executor/guest/src/lib.rs index bb754b7..3fcaa1c 100644 --- a/crates/executor/guest/src/lib.rs +++ b/crates/executor/guest/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(target_os = "zkvm", feature(asm_experimental_arch))] /// Client program input data types. pub mod io; @@ -7,6 +8,8 @@ mod utils; pub mod custom; pub mod error; pub mod executor; +pub mod hashed_keys; +pub mod precompiles; pub mod tracking; mod into_primitives; @@ -17,10 +20,21 @@ use executor::{EthClientExecutor, DESERIALZE_INPUTS}; use io::EthClientExecutorInput; use std::sync::Arc; +/// Verifies an Ethereum mainnet block and returns its hash, its state root and +/// its parent's. +/// +/// The proof commits only the block hash, so the rules the block runs under +/// must not come from the prover: the input's chain must be mainnet, with no +/// beneficiary override (a Clique-only setting that redirects block rewards). pub fn verify_block(input: &[u8]) -> (B256, B256, B256) { println!("cycle-tracker-report-start: {DESERIALZE_INPUTS}"); let input = bincode::deserialize::(input).unwrap(); println!("cycle-tracker-report-end: {DESERIALZE_INPUTS}"); + assert!( + matches!(input.genesis, primitives::genesis::Genesis::Mainnet), + "the guest verifies Ethereum mainnet blocks only" + ); + assert!(input.custom_beneficiary.is_none(), "mainnet blocks pay the header's beneficiary"); // Execute the block. let executor = EthClientExecutor::eth( diff --git a/crates/executor/guest/src/precompiles.rs b/crates/executor/guest/src/precompiles.rs new file mode 100644 index 0000000..be57eac --- /dev/null +++ b/crates/executor/guest/src/precompiles.rs @@ -0,0 +1,563 @@ +//! Precompile arithmetic over the zkVM's curve and field syscalls. +//! +//! revm's default `Crypto` backends run bn254 scalar multiplication in +//! Jacobian coordinates and every BLS12-381 operation on arkworks' pure +//! Montgomery arithmetic, both of which are ordinary MIPS code in the guest. +//! These routines take the same byte inputs and compute with the `bn` +//! fork's affine syscalls and the `bls12_381` fork's syscall-backed field. + +use bls12_381::{ + fp::Fp, fp2::Fp2, hash_to_curve::MapToCurve, multi_miller_loop, G1Affine, G1Projective, + G2Affine, G2Prepared, G2Projective, Gt, Scalar, +}; +use kzg_rs::{ + kzg_proof::{safe_g1_affine_from_bytes, safe_scalar_affine_from_bytes}, + Bytes32, Bytes48, KzgSettings, +}; +use revm::precompile::PrecompileError; + +const FP: usize = 48; + +// --------------------------------------------------------------------------- +// bn254 + +/// The `ecAdd` precompile on two 64-byte points. +pub fn bn254_g1_add(p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileError> { + let a = bn254::point(p1)?; + let b = bn254::point(p2)?; + Ok(bn254::encode(bn254::add(a, b))) +} + +/// The `ecMul` precompile on a 64-byte point and a 32-byte big-endian scalar. +pub fn bn254_g1_mul(point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileError> { + let p = bn254::point(point)?; + Ok(bn254::encode(bn254::mul(p, scalar))) +} + +/// bn254 G1 in affine coordinates over the zkVM's curve precompiles, which +/// add two distinct finite points and double a finite point; infinity and +/// the equal-`x` cases are decided here. A point is `x || y` as 32-bit +/// little-endian words, the layout the precompiles read. +#[cfg(target_os = "zkvm")] +mod bn254 { + use revm::precompile::PrecompileError; + use zkm_lib::{syscall_bn254_add, syscall_bn254_double, syscall_uint256_mulmod}; + + type Words = [u32; 8]; + pub type Point = [u32; 16]; + + /// The base field modulus `p`, little-endian words. + const P: Words = [ + 0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, 0x8181585d, 0xb85045b6, 0xe131a029, + 0x30644e72, + ]; + /// The group order `n`, little-endian words. + const N: Words = [ + 0xf0000001, 0x43e1f593, 0x79b97091, 0x2833e848, 0x8181585d, 0xb85045b6, 0xe131a029, + 0x30644e72, + ]; + const ONE: Words = [1, 0, 0, 0, 0, 0, 0, 0]; + const THREE: Words = [3, 0, 0, 0, 0, 0, 0, 0]; + + fn words(be: &[u8]) -> Words { + let mut w = [0u32; 8]; + for (i, word) in w.iter_mut().enumerate() { + *word = u32::from_be_bytes(be[28 - 4 * i..32 - 4 * i].try_into().expect("4 bytes")); + } + w + } + + fn bytes(w: &Words, be: &mut [u8]) { + for (i, word) in w.iter().enumerate() { + be[28 - 4 * i..32 - 4 * i].copy_from_slice(&word.to_be_bytes()); + } + } + + fn lt(a: &Words, b: &Words) -> bool { + for i in (0..8).rev() { + if a[i] != b[i] { + return a[i] < b[i]; + } + } + false + } + + /// `(a · b) mod m` through the 256-bit multiplier, which reads `b` and + /// `m` as one 16-word block. + fn mul_mod(a: &Words, b: &Words, m: &Words) -> Words { + let mut x = *a; + let mut bm = [0u32; 16]; + bm[..8].copy_from_slice(b); + bm[8..].copy_from_slice(m); + unsafe { syscall_uint256_mulmod(&mut x, bm.as_ptr().cast()) }; + x + } + + /// `(a + b) mod p` for `a, b < p`. + fn add_mod_p(a: &Words, b: &Words) -> Words { + let mut r = [0u32; 8]; + let mut carry = 0u64; + for i in 0..8 { + let s = a[i] as u64 + b[i] as u64 + carry; + r[i] = s as u32; + carry = s >> 32; + } + if carry != 0 || !lt(&r, &P) { + let mut borrow = 0i64; + for i in 0..8 { + let d = r[i] as i64 - P[i] as i64 - borrow; + r[i] = d as u32; + borrow = (d < 0) as i64; + } + } + r + } + + /// A point from its 64-byte big-endian `x || y` encoding, `None` for + /// `(0, 0)`; the coordinates must be canonical and on `y² = x³ + 3`. + pub fn point(be: &[u8]) -> Result, PrecompileError> { + let x = words(&be[..32]); + let y = words(&be[32..64]); + if !lt(&x, &P) || !lt(&y, &P) { + return Err(PrecompileError::Bn254FieldPointNotAMember); + } + if x == [0; 8] && y == [0; 8] { + return Ok(None); + } + let lhs = mul_mod(&y, &y, &P); + let rhs = add_mod_p(&mul_mod(&mul_mod(&x, &x, &P), &x, &P), &THREE); + if lhs != rhs { + return Err(PrecompileError::Bn254AffineGFailedToCreate); + } + let mut p = [0u32; 16]; + p[..8].copy_from_slice(&x); + p[8..].copy_from_slice(&y); + Ok(Some(p)) + } + + pub fn encode(p: Option) -> [u8; 64] { + let mut out = [0u8; 64]; + if let Some(p) = p { + bytes(p[..8].try_into().expect("8 words"), &mut out[..32]); + bytes(p[8..].try_into().expect("8 words"), &mut out[32..]); + } + out + } + + fn double(mut p: Point) -> Point { + unsafe { syscall_bn254_double(&mut p) }; + p + } + + /// `a + b`: equal points are doubled, opposite points (equal `x`) cancel. + pub fn add(a: Option, b: Option) -> Option { + match (a, b) { + (None, b) => b, + (a, None) => a, + (Some(mut a), Some(b)) => { + if a[..8] == b[..8] { + if a[8..] == b[8..] { + Some(double(a)) + } else { + None + } + } else { + unsafe { syscall_bn254_add(&mut a, &b) }; + Some(a) + } + } + } + } + + /// `k · p` by double-and-add. `k` is reduced modulo `n` first, so no + /// partial sum `k' · p` with `k' ≤ k / 2 < n - 1` equals `-p`, the one + /// case the addition cannot take; the partial sum `p` itself is only + /// ever doubled. + pub fn mul(p: Option, k: &[u8]) -> Option { + let p = p?; + let k = mul_mod(&words(k), &ONE, &N); + let mut acc: Option = None; + for i in (0..8).rev() { + for j in (0..32).rev() { + if let Some(a) = acc { + acc = Some(double(a)); + } + if (k[i] >> j) & 1 == 1 { + acc = add(acc, Some(p)); + } + } + } + acc + } +} + +/// bn254 G1 off the zkVM, through the `bn` crate. +#[cfg(not(target_os = "zkvm"))] +mod bn254 { + use bn::{AffineG1, Fq, Fr, G1}; + use revm::precompile::PrecompileError; + + pub type Point = G1; + + pub fn point(be: &[u8]) -> Result, PrecompileError> { + let x = + Fq::from_slice(&be[..32]).map_err(|_| PrecompileError::Bn254FieldPointNotAMember)?; + let y = + Fq::from_slice(&be[32..64]).map_err(|_| PrecompileError::Bn254FieldPointNotAMember)?; + if x.is_zero() && y.is_zero() { + return Ok(None); + } + AffineG1::new(x, y) + .map(|p| Some(p.into())) + .map_err(|_| PrecompileError::Bn254AffineGFailedToCreate) + } + + pub fn encode(p: Option) -> [u8; 64] { + let mut out = [0u8; 64]; + if let Some(p) = p.and_then(AffineG1::from_jacobian) { + p.x().to_big_endian(&mut out[..32]).expect("32 bytes"); + p.y().to_big_endian(&mut out[32..]).expect("32 bytes"); + } + out + } + + pub fn add(a: Option, b: Option) -> Option { + match (a, b) { + (None, b) => b, + (a, None) => a, + (Some(a), Some(b)) => Some(a + b), + } + } + + pub fn mul(p: Option, k: &[u8]) -> Option { + let k = Fr::from_slice(k).expect("32 bytes"); + p.map(|p| p * k) + } +} + +// --------------------------------------------------------------------------- +// BLS12-381 + +fn is_zero(bytes: &[u8]) -> bool { + bytes.iter().all(|&b| b == 0) +} + +fn fp(bytes: &[u8; FP]) -> Result { + Option::from(Fp::from_bytes(bytes)).ok_or(PrecompileError::NonCanonicalFp) +} + +fn fp2(c0: &[u8; FP], c1: &[u8; FP]) -> Result { + Ok(Fp2 { c0: fp(c0)?, c1: fp(c1)? }) +} + +/// A G1 point from its EIP-2537 coordinates, `(0, 0)` being the identity; +/// checked to be canonical and on the curve, and in the subgroup when asked. +fn g1(x: &[u8; FP], y: &[u8; FP], subgroup: bool) -> Result { + if is_zero(x) && is_zero(y) { + return Ok(G1Affine::identity()); + } + let mut bytes = [0u8; 2 * FP]; + bytes[..FP].copy_from_slice(x); + bytes[FP..].copy_from_slice(y); + let p: G1Affine = Option::from(G1Affine::from_uncompressed_unchecked(&bytes)) + .ok_or(PrecompileError::NonCanonicalFp)?; + if !bool::from(p.is_on_curve()) { + return Err(PrecompileError::Bls12381G1NotOnCurve); + } + if subgroup && !bool::from(p.is_torsion_free()) { + return Err(PrecompileError::Bls12381G1NotInSubgroup); + } + Ok(p) +} + +/// A G2 point from its EIP-2537 coordinates `(x0, x1, y0, y1)`. The +/// library's uncompressed form orders each Fp2 as `c1 || c0`. +fn g2( + x0: &[u8; FP], + x1: &[u8; FP], + y0: &[u8; FP], + y1: &[u8; FP], + subgroup: bool, +) -> Result { + if is_zero(x0) && is_zero(x1) && is_zero(y0) && is_zero(y1) { + return Ok(G2Affine::identity()); + } + let mut bytes = [0u8; 4 * FP]; + bytes[..FP].copy_from_slice(x1); + bytes[FP..2 * FP].copy_from_slice(x0); + bytes[2 * FP..3 * FP].copy_from_slice(y1); + bytes[3 * FP..].copy_from_slice(y0); + let p: G2Affine = Option::from(G2Affine::from_uncompressed_unchecked(&bytes)) + .ok_or(PrecompileError::NonCanonicalFp)?; + if !bool::from(p.is_on_curve()) { + return Err(PrecompileError::Bls12381G2NotOnCurve); + } + if subgroup && !bool::from(p.is_torsion_free()) { + return Err(PrecompileError::Bls12381G2NotInSubgroup); + } + Ok(p) +} + +/// The EIP-2537 encoding of a G1 point, zeros for the identity. +fn encode_g1(p: &G1Affine) -> [u8; 2 * FP] { + if bool::from(p.is_identity()) { + return [0u8; 2 * FP]; + } + p.to_uncompressed() +} + +/// The EIP-2537 encoding `x0 || x1 || y0 || y1` of a G2 point. +fn encode_g2(p: &G2Affine) -> [u8; 4 * FP] { + let mut out = [0u8; 4 * FP]; + if bool::from(p.is_identity()) { + return out; + } + let lib = p.to_uncompressed(); + out[..FP].copy_from_slice(&lib[FP..2 * FP]); + out[FP..2 * FP].copy_from_slice(&lib[..FP]); + out[2 * FP..3 * FP].copy_from_slice(&lib[3 * FP..]); + out[3 * FP..].copy_from_slice(&lib[2 * FP..3 * FP]); + out +} + +/// A scalar from 32 big-endian bytes, reduced modulo the group order as +/// EIP-2537 asks. +fn scalar(bytes: &[u8; 32]) -> Scalar { + let mut wide = [0u8; 64]; + for (i, &b) in bytes.iter().enumerate() { + wide[31 - i] = b; + } + Scalar::from_bytes_wide(&wide) +} + +pub type G1Point = ([u8; FP], [u8; FP]); +pub type G2Point = ([u8; FP], [u8; FP], [u8; FP], [u8; FP]); + +pub fn bls12_381_g1_add(a: G1Point, b: G1Point) -> Result<[u8; 2 * FP], PrecompileError> { + let a = g1(&a.0, &a.1, false)?; + let b = g1(&b.0, &b.1, false)?; + Ok(encode_g1(&G1Affine::from(G1Projective::from(a) + b))) +} + +pub fn bls12_381_g2_add(a: G2Point, b: G2Point) -> Result<[u8; 4 * FP], PrecompileError> { + let a = g2(&a.0, &a.1, &a.2, &a.3, false)?; + let b = g2(&b.0, &b.1, &b.2, &b.3, false)?; + Ok(encode_g2(&G2Affine::from(G2Projective::from(a) + b))) +} + +pub fn bls12_381_g1_msm( + pairs: &mut dyn Iterator>, +) -> Result<[u8; 2 * FP], PrecompileError> { + let mut acc = G1Projective::identity(); + for pair in pairs { + let ((x, y), k) = pair?; + let p = g1(&x, &y, true)?; + if bool::from(p.is_identity()) || is_zero(&k) { + continue; + } + acc += p * scalar(&k); + } + Ok(encode_g1(&G1Affine::from(acc))) +} + +pub fn bls12_381_g2_msm( + pairs: &mut dyn Iterator>, +) -> Result<[u8; 4 * FP], PrecompileError> { + let mut acc = G2Projective::identity(); + for pair in pairs { + let ((x0, x1, y0, y1), k) = pair?; + let p = g2(&x0, &x1, &y0, &y1, true)?; + if bool::from(p.is_identity()) || is_zero(&k) { + continue; + } + acc += p * scalar(&k); + } + Ok(encode_g2(&G2Affine::from(acc))) +} + +/// Whether the product of the pairings is the identity. Every point is +/// validated (subgroup included); a pair with an identity contributes the +/// identity and is left out of the Miller loop. +pub fn bls12_381_pairing_check(pairs: &[(G1Point, G2Point)]) -> Result { + let mut g1s = Vec::with_capacity(pairs.len()); + let mut g2s = Vec::with_capacity(pairs.len()); + for ((x, y), (x0, x1, y0, y1)) in pairs { + let p = g1(x, y, true)?; + let q = g2(x0, x1, y0, y1, true)?; + if bool::from(p.is_identity()) || bool::from(q.is_identity()) { + continue; + } + g1s.push(p); + g2s.push(G2Prepared::from(q)); + } + if g1s.is_empty() { + return Ok(true); + } + let terms: Vec<(&G1Affine, &G2Prepared)> = g1s.iter().zip(&g2s).collect(); + Ok(multi_miller_loop(&terms).final_exponentiation() == Gt::identity()) +} + +pub fn bls12_381_fp_to_g1(bytes: &[u8; FP]) -> Result<[u8; 2 * FP], PrecompileError> { + let u = fp(bytes)?; + let p = G1Projective::map_to_curve(&u).clear_h(); + Ok(encode_g1(&G1Affine::from(p))) +} + +pub fn bls12_381_fp2_to_g2(c: ([u8; FP], [u8; FP])) -> Result<[u8; 4 * FP], PrecompileError> { + let u = fp2(&c.0, &c.1)?; + let p = G2Projective::map_to_curve(&u).clear_h(); + Ok(encode_g2(&G2Affine::from(p))) +} + +// --------------------------------------------------------------------------- +// KZG point evaluation + +/// The pairing check of the KZG point-evaluation precompile with its two +/// G2 arguments fixed, so both are prepared once: +/// +/// `e(P - y·G1, G2) · e(-π, τ·G2 - z·G2) = 1` is, by bilinearity, +/// `e(P - y·G1 + z·π, G2) · e(-π, τ·G2) = 1`, +/// +/// which moves the only scalar multiplication to G1, where it is one joint +/// double-and-add over `y` and `z`, and leaves the G2 side constant. +pub struct KzgVerifier { + g2: G2Prepared, + tau_g2: G2Prepared, +} + +impl KzgVerifier { + pub fn new(settings: &KzgSettings) -> Self { + Self { + g2: G2Prepared::from(G2Affine::generator()), + tau_g2: G2Prepared::from(settings.g2_points[1]), + } + } + + /// Whether `proof` opens `commitment` at `z` to `y`; the points are + /// decoded with kzg-rs's checks (canonical, on the curve, in the + /// subgroup), the scalars must be canonical. + pub fn verify( + &self, + z: &[u8; 32], + y: &[u8; 32], + commitment: &[u8; 48], + proof: &[u8; 48], + ) -> Result { + let other = |err: kzg_rs::KzgError| PrecompileError::other(err.to_string()); + let z = safe_scalar_affine_from_bytes(&Bytes32(*z)).map_err(other)?; + let y = safe_scalar_affine_from_bytes(&Bytes32(*y)).map_err(other)?; + let commitment = safe_g1_affine_from_bytes(&Bytes48(*commitment)).map_err(other)?; + let proof = safe_g1_affine_from_bytes(&Bytes48(*proof)).map_err(other)?; + + let minus_g1 = -G1Affine::generator(); + let a = + G1Affine::from(linear_combination(&minus_g1, &y, &proof, &z).add_mixed(&commitment)); + let minus_proof = -proof; + let mut terms: Vec<(&G1Affine, &G2Prepared)> = Vec::with_capacity(2); + if !bool::from(a.is_identity()) { + terms.push((&a, &self.g2)); + } + if !bool::from(minus_proof.is_identity()) { + terms.push((&minus_proof, &self.tau_g2)); + } + if terms.is_empty() { + return Ok(true); + } + Ok(multi_miller_loop(&terms).final_exponentiation() == Gt::identity()) + } +} + +/// `s·a + t·b` by one double-and-add over both scalars (Straus), with +/// `a + b` precomputed; nothing here is secret, so the walk is not +/// constant-time. +fn linear_combination(a: &G1Affine, s: &Scalar, b: &G1Affine, t: &Scalar) -> G1Projective { + let ab = G1Affine::from(G1Projective::from(*a).add_mixed(b)); + let s = s.to_bytes(); + let t = t.to_bytes(); + let mut acc = G1Projective::identity(); + let mut started = false; + for i in (0..256).rev() { + if started { + acc = acc.double(); + } + let sb = (s[i / 8] >> (i % 8)) & 1 == 1; + let tb = (t[i / 8] >> (i % 8)) & 1 == 1; + match (sb, tb) { + (true, true) => acc = acc.add_mixed(&ab), + (true, false) => acc = acc.add_mixed(a), + (false, true) => acc = acc.add_mixed(b), + (false, false) => {} + } + started |= sb | tb; + } + acc +} + +// --------------------------------------------------------------------------- +// modexp + +/// `base^exp mod modulus` as big-endian bytes, at most `modulus.len()` of +/// them. Operands of up to 256 bits run as a square-and-multiply ladder on +/// the uint256 multiplier; anything wider takes the generic implementation. +pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { + #[cfg(target_os = "zkvm")] + if base.len() <= 32 && modulus.len() <= 32 { + return modexp256::modexp(base, exp, modulus); + } + aurora_engine_modexp::modexp(base, exp, modulus) +} + +#[cfg(target_os = "zkvm")] +mod modexp256 { + use zkm_lib::syscall_uint256_mulmod; + + type Words = [u32; 8]; + + /// Big-endian bytes (at most 32) as little-endian words. + fn words(be: &[u8]) -> Words { + let mut padded = [0u8; 32]; + padded[32 - be.len()..].copy_from_slice(be); + let mut w = [0u32; 8]; + for (i, word) in w.iter_mut().enumerate() { + *word = u32::from_be_bytes(padded[28 - 4 * i..32 - 4 * i].try_into().expect("4 bytes")); + } + w + } + + /// `(a · b) mod m` for `m ≠ 0`; the multiplier reads `b` and `m` as one + /// 16-word block. + fn mul_mod(a: &Words, b: &Words, m: &Words) -> Words { + let mut x = *a; + let mut bm = [0u32; 16]; + bm[..8].copy_from_slice(b); + bm[8..].copy_from_slice(m); + unsafe { syscall_uint256_mulmod(&mut x, bm.as_ptr().cast()) }; + x + } + + pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { + let m = words(modulus); + if m == [0; 8] || m == [1, 0, 0, 0, 0, 0, 0, 0] { + return Vec::new(); + } + let b = mul_mod(&words(base), &[1, 0, 0, 0, 0, 0, 0, 0], &m); + let mut r: Words = [1, 0, 0, 0, 0, 0, 0, 0]; + let mut started = false; + for &byte in exp { + for j in (0..8).rev() { + if started { + r = mul_mod(&r, &r, &m); + } + if (byte >> j) & 1 == 1 { + r = mul_mod(&r, &b, &m); + started = true; + } + } + } + let mut out = [0u8; 32]; + for (i, word) in r.iter().enumerate() { + out[28 - 4 * i..32 - 4 * i].copy_from_slice(&word.to_be_bytes()); + } + out[32 - modulus.len()..].to_vec() + } +} diff --git a/crates/executor/host/examples/witness_census.rs b/crates/executor/host/examples/witness_census.rs new file mode 100644 index 0000000..34ee4eb --- /dev/null +++ b/crates/executor/host/examples/witness_census.rs @@ -0,0 +1,127 @@ +//! Native census of a cached client input's witness: how many trie nodes of +//! each kind the guest's arena loader walks, how many bytes, and how long the +//! native load takes. `cargo run --release --example witness_census -- ` +use std::time::Instant; + +use guest_executor::io::EthClientExecutorInput; +use mpt::ArenaState; + +const TAG_DIGEST: u8 = 0; +const TAG_NODE: u8 = 1; + +#[derive(Default, Debug)] +struct Census { + nodes: usize, + nulls: usize, + digests: usize, + leaves: usize, + extensions: usize, + branches: usize, + inline_children: usize, + node_bytes: usize, + keccak_blocks: usize, +} + +fn varint(buf: &[u8], pos: &mut usize) -> usize { + let (mut v, mut shift) = (0usize, 0); + loop { + let b = buf[*pos]; + *pos += 1; + v |= ((b & 0x7f) as usize) << shift; + if b & 0x80 == 0 { + return v; + } + shift += 7; + } +} + +fn items(payload: &[u8]) -> usize { + let mut rest = payload; + let mut n = 0; + while !rest.is_empty() { + let h = alloy_rlp::Header::decode(&mut rest).unwrap(); + rest = &rest[h.payload_length..]; + n += 1; + } + n +} + +fn walk(stream: &[u8], c: &mut Census) { + let mut pos = 0; + while pos < stream.len() { + let tag = stream[pos]; + pos += 1; + match tag { + TAG_DIGEST => c.digests += 1, + TAG_NODE => { + let len = varint(stream, &mut pos); + let node = &stream[pos..pos + len]; + pos += len; + c.nodes += 1; + c.node_bytes += len; + c.keccak_blocks += len.div_ceil(136); + let mut b = node; + let h = alloy_rlp::Header::decode(&mut b).unwrap(); + let payload = &b[..h.payload_length]; + if !h.list { + c.nulls += 1; + continue; + } + match items(payload) { + 2 => { + let mut r = payload; + let ph = alloy_rlp::Header::decode(&mut r).unwrap(); + let path = &r[..ph.payload_length]; + if path[0] & 0x20 == 0 { + c.extensions += 1 + } else { + c.leaves += 1 + } + } + 17 => { + c.branches += 1; + let mut r = payload; + for _ in 0..16 { + let ch = alloy_rlp::Header::decode(&mut r).unwrap(); + if ch.list { + c.inline_children += 1; + } + r = &r[ch.payload_length..]; + } + } + n => panic!("node with {n} items"), + } + } + _ => panic!("bad tag"), + } + } +} + +fn main() { + let path = std::env::args().nth(1).expect("input.bin"); + let bytes = std::fs::read(&path).unwrap(); + let t = Instant::now(); + let input: EthClientExecutorInput = bincode::deserialize(&bytes).unwrap(); + println!("input {} B, bincode decode {:?}", bytes.len(), t.elapsed()); + let w = &input.parent_state; + let mut state = Census::default(); + walk(&w.state_nodes, &mut state); + println!("state trie: {} B stream, {state:?}", w.state_nodes.len()); + let mut storage = Census::default(); + let mut storage_bytes = 0; + for sw in &w.storage { + storage_bytes += sw.nodes.len(); + walk(&sw.nodes, &mut storage); + } + println!("storage tries: {} tries, {storage_bytes} B streams, {storage:?}", w.storage.len()); + println!( + "bytecodes: {} ({} code bytes), txs {}", + input.bytecodes.len(), + input.bytecodes.iter().map(|b| b.code().len()).sum::(), + input.current_block.body.transactions.len() + ); + let t = Instant::now(); + let st = ArenaState::from_witness(w).unwrap(); + println!("native ArenaState::from_witness: {:?}", t.elapsed()); + std::hint::black_box(st); +} diff --git a/crates/executor/host/src/full_executor.rs b/crates/executor/host/src/full_executor.rs index 72c810a..1a5ffc6 100644 --- a/crates/executor/host/src/full_executor.rs +++ b/crates/executor/host/src/full_executor.rs @@ -113,10 +113,25 @@ pub trait BlockExecutor { info!("cycles: {:?}", proof_with_cycles.1); - let proving_duration = proving_start.elapsed(); + // Prefer the prover's own measure when it has one. This process + // times the whole call, which with a pipelined caller includes the + // request waiting in the prover's queue; the prover times only the + // proving. Everything downstream — the eth-proofs submission + // among it — should report proving, not queueing. + let proving_duration = self + .client() + .take_prove_ms() + .map(Duration::from_millis) + .unwrap_or_else(|| proving_start.elapsed()); let proof_bytes = bincode::serialize(&proof_with_cycles.0.proof).unwrap(); let public_values_bytes = bincode::serialize(&proof_with_cycles.0.public_values).unwrap(); + // Size census: dump the exact bytes that get submitted so the + // proof's composition can be measured offline. + if let Ok(path) = std::env::var("ZIREN_COMPRESS_PROOF_OUT") { + std::fs::write(&path, &proof_bytes)?; + info!("wrote {} proof bytes to {path}", proof_bytes.len()); + } hooks .on_proving_end( @@ -485,7 +500,15 @@ fn try_load_input_from_cache( if cache_path.exists() { // TODO: prune the cache if invalid instead let mut cache_file = std::fs::File::open(cache_path)?; - let client_input = bincode::deserialize_from(&mut cache_file)?; + let client_input = match bincode::deserialize_from(&mut cache_file) { + Ok(input) => input, + Err(err) => { + tracing::warn!( + "cached input for block {block_number} is unreadable ({err}); refetching" + ); + return Ok(None); + } + }; Ok(Some(client_input)) } else { diff --git a/crates/executor/host/src/host_executor.rs b/crates/executor/host/src/host_executor.rs index 421e69b..8c24c67 100644 --- a/crates/executor/host/src/host_executor.rs +++ b/crates/executor/host/src/host_executor.rs @@ -76,22 +76,41 @@ impl HostExecutor { let chain_id: u64 = (&genesis).try_into().unwrap(); tracing::debug!("chain id: {}", chain_id); - // Fetch the current block and the previous block from the provider. + // Fetch the current block, the previous block and (with the + // execution-witness feature) the witness concurrently. Only the + // witness DB construction needs the parent's state root; the three RPC + // round trips themselves are independent, and run serially they were + // ~4 s of the ~4.2 s an uncached block spends before proving starts. tracing::info!("[{}] fetching the current block and the previous block", block_number); - let rpc_block = provider - .get_block_by_number(block_number.into()) - .full() - .await? - .ok_or(HostError::ExpectedBlock(block_number))?; + let fetch_current = async { + provider + .get_block_by_number(block_number.into()) + .full() + .await? + .ok_or(HostError::ExpectedBlock(block_number)) + }; + let fetch_previous = async { + provider + .get_block_by_number((block_number - 1).into()) + .full() + .await? + .ok_or(HostError::ExpectedBlock(block_number)) + .map(C::Primitives::into_primitive_block) + }; - let current_block = C::Primitives::into_primitive_block(rpc_block.clone()); + #[cfg(not(feature = "execution-witness"))] + let (rpc_block, previous_block) = futures::try_join!(fetch_current, fetch_previous)?; + #[cfg(feature = "execution-witness")] + let (rpc_block, previous_block, execution_witness) = { + let fetch_witness = async { + rpc_db::ExecutionWitnessRpcDb::<_, N>::fetch_witness(debug_provider, block_number) + .await + .map_err(HostError::from) + }; + futures::try_join!(fetch_current, fetch_previous, fetch_witness)? + }; - let previous_block = provider - .get_block_by_number((block_number - 1).into()) - .full() - .await? - .ok_or(HostError::ExpectedBlock(block_number)) - .map(C::Primitives::into_primitive_block)?; + let current_block = C::Primitives::into_primitive_block(rpc_block.clone()); tracing::info!("[{}] create rpc db", block_number); #[cfg(not(feature = "execution-witness"))] @@ -101,12 +120,12 @@ impl HostExecutor { previous_block.header().state_root(), ); #[cfg(feature = "execution-witness")] - let rpc_db = rpc_db::ExecutionWitnessRpcDb::new( + let rpc_db = rpc_db::ExecutionWitnessRpcDb::from_witness( debug_provider, block_number, previous_block.header().state_root(), - ) - .await?; + execution_witness, + ); tracing::info!("[{}] create rpc db done", block_number); let cache_db = CacheDB::new(&rpc_db); @@ -216,8 +235,15 @@ impl HostExecutor { let client_input = ClientExecutorInput { current_block: C::Primitives::into_input_block(current_block), ancestor_headers, - parent_state: state, - bytecodes: rpc_db.bytecodes(), + // Witness form: root hashes + preorder streams of raw RLP nodes; the + // guest builds its arena tries from it, hash-checking every node. + parent_state: state.to_witness_state(), + code_hashes: rpc_db.bytecodes().iter().map(|b| b.hash_slow()).collect(), + bytecodes: rpc_db + .bytecodes() + .iter() + .map(|b| guest_executor::io::RawCode::new(b.original_byte_slice())) + .collect(), genesis, custom_beneficiary, opcode_tracking, diff --git a/crates/mpt/Cargo.toml b/crates/mpt/Cargo.toml index f54e51c..c340c70 100644 --- a/crates/mpt/Cargo.toml +++ b/crates/mpt/Cargo.toml @@ -23,6 +23,7 @@ alloy-rpc-types-debug = {workspace = true, optional = true } [dev-dependencies] hex-literal.workspace = true +reth-primitives-traits.workspace = true [features] default = ["execution-witness"] diff --git a/crates/mpt/src/arena.rs b/crates/mpt/src/arena.rs new file mode 100644 index 0000000..0211249 --- /dev/null +++ b/crates/mpt/src/arena.rs @@ -0,0 +1,1364 @@ +//! Arena-backed Merkle Patricia trie for the guest. +//! +//! The guest used to deserialize the witness as a tree of boxed [`MptNode`]s +//! (one heap allocation and two ~112-byte copies per node, 16 of them per +//! branch) and then RLP-encode and keccak every node again to check the state +//! root. On a reth block that was ~37 M cycles of decoding plus ~31 M of +//! hashing for ~9.6 K nodes — a third of the block. +//! +//! Here the host ships each trie as a *preorder stream* of raw RLP nodes +//! ([`WitnessState`]), and the guest builds an [`ArenaTrie`] from it in one +//! pass: nodes live in one `Vec`, children are indices, every node is +//! keccak-checked exactly once against the digest its parent carries (the +//! root against the anchor), and the verified digest is kept as the node's +//! reference so the state-root pass only re-hashes what the block mutated. +//! No per-node allocation of children, no hash-map lookups. +//! +//! [`MptNode`] stays the host-side representation; it is also the test +//! oracle for this module. + +use alloy_primitives::{map::HashMap, Bytes, B256, U256}; +use alloy_rlp::Encodable; +use core::mem; +use reth_trie::{HashedPostState, HashedStorage, TrieAccount, EMPTY_ROOT_HASH}; +use serde::{Deserialize, Serialize}; + +use super::mpt::{ + keccak, lcp, prefix_nibs, to_encoded_path, Error, MptNode, MptNodeData, MptNodeReference, + RlpBytes, EMPTY_ROOT, +}; + +/// Stream tag: the referenced node is not part of the witness (stays a digest). +/// The RLP prefix of a 32-byte string: how a trie node references a child +/// that lives outside it. +const DIGEST_STRING_CODE: u8 = alloy_rlp::EMPTY_STRING_CODE + 32; + +const TAG_DIGEST: u8 = 0; +/// Stream tag: a node follows as `varint(len) || rlp`. +const TAG_NODE: u8 = 1; + +/// The state tries in witness form: for the state trie and every storage +/// trie, the root hash and a preorder stream of raw RLP nodes. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct WitnessState { + pub state_root: B256, + #[serde(deserialize_with = "input_bytes")] + pub state_nodes: Bytes, + /// One entry per storage trie, sorted by hashed address so the input + /// bytes are reproducible. + pub storage: Vec, +} + +/// One account's storage trie in witness form: its hashed address, its root +/// and the preorder stream of its raw RLP nodes. +/// +/// Bincode writes a struct as its fields in order, so this is byte-identical +/// to the `(B256, B256, Bytes)` tuple it replaced and cached inputs stay +/// readable. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct StorageWitness { + pub hashed_address: B256, + pub root: B256, + #[serde(deserialize_with = "input_bytes")] + pub nodes: Bytes, +} + +/// Deserialise a byte string into `Bytes` without copying it in the guest. +/// +/// The program input is read once and leaked by `main`, so it outlives the +/// run and bincode's borrowed slices of it can be kept as they are: the +/// witness streams are 6-7 MB per reth block, and copying them out of the +/// input buffer cost ~7 M cycles. Off the zkVM the bytes are copied. +pub fn input_bytes<'de, D: serde::Deserializer<'de>>(d: D) -> Result { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = Bytes; + + fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("a byte string") + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result { + #[cfg(target_os = "zkvm")] + { + // SAFETY: the guest leaks its input buffer (bin/guest/src/main.rs), + // so every slice of it lives as long as the program. + let v: &'static [u8] = + unsafe { core::mem::transmute::<&'de [u8], &'static [u8]>(v) }; + Ok(Bytes::from_static(v)) + } + #[cfg(not(target_os = "zkvm"))] + { + Ok(Bytes::copy_from_slice(v)) + } + } + + fn visit_bytes(self, v: &[u8]) -> Result { + Ok(Bytes::copy_from_slice(v)) + } + + fn visit_byte_buf(self, v: Vec) -> Result { + Ok(Bytes::from(v)) + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(b) = seq.next_element::()? { + out.push(b); + } + Ok(Bytes::from(out)) + } + } + d.deserialize_bytes(V) +} + +// --------------------------------------------------------------------------- +// Host side: MptNode -> preorder stream +// --------------------------------------------------------------------------- + +fn put_varint(out: &mut Vec, mut v: usize) { + while v >= 0x80 { + out.push((v as u8) | 0x80); + v >>= 7; + } + out.push(v as u8); +} + +fn is_hashed(node: &MptNode) -> bool { + matches!(node.reference(), MptNodeReference::Digest(_)) +} + +/// Emit `node` (which its parent references by digest, or the root) and, +/// recursively, every digest-referenced descendant, in preorder. Children +/// inlined in their parent's RLP need no entry: an inline node cannot carry a +/// digest child, so its whole subtree is inside the parent's bytes. +fn emit_node(node: &MptNode, out: &mut Vec) { + match node.as_data() { + MptNodeData::Digest(_) => out.push(TAG_DIGEST), + _ => { + let rlp = node.to_rlp(); + out.push(TAG_NODE); + put_varint(out, rlp.len()); + out.extend_from_slice(&rlp); + emit_children(node, out); + } + } +} + +fn emit_children(node: &MptNode, out: &mut Vec) { + match node.as_data() { + MptNodeData::Branch(children) => { + for child in children.iter().flatten() { + if is_hashed(child) { + emit_node(child, out); + } else { + emit_children(child, out); + } + } + } + MptNodeData::Extension(_, child) => { + if is_hashed(child) { + emit_node(child, out); + } else { + emit_children(child, out); + } + } + _ => {} + } +} + +/// The preorder node stream of a trie rooted at `root`. +pub fn witness_stream(root: &MptNode) -> Bytes { + let mut out = Vec::new(); + emit_node(root, &mut out); + out.into() +} + +// --------------------------------------------------------------------------- +// Guest side: arena trie +// --------------------------------------------------------------------------- + +type NodeId = u32; +const NONE: NodeId = u32::MAX; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Node { + Null, + /// Compact-encoded path (as in the RLP) and the value, both slices of + /// the witness stream for a node read from it. + Leaf(Bytes, Bytes), + /// Compact-encoded path and the child. + Extension(Bytes, NodeId), + Branch([NodeId; 16]), + Digest(B256), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Ref { + /// Encodings shorter than 32 bytes are inlined by the parent. + Bytes(Vec), + Digest(B256), +} + +impl Ref { + fn encode(&self, out: &mut Vec) { + match self { + Ref::Bytes(b) => out.extend_from_slice(b), + Ref::Digest(d) => push_digest(out, &d.0), + } + } + fn length(&self) -> usize { + match self { + Ref::Bytes(b) => b.len(), + Ref::Digest(_) => 33, + } + } +} + +/// Appends the RLP of a 32-byte string, `0xa0` then `digest`, to `out`. +/// +/// The digest is moved as eight unaligned words rather than byte by byte: +/// a reth block's state-root pass writes ~100 K of them. The writes are +/// sound because `out` has room for 33 bytes after the reservation, every +/// word of `digest` is in bounds, and the length is set to what was written. +#[inline] +fn push_digest(out: &mut Vec, digest: &[u8; 32]) { + out.reserve(33); + let len = out.len(); + unsafe { + let dst = out.as_mut_ptr().add(len); + *dst = DIGEST_STRING_CODE; + let src = digest.as_ptr().cast::(); + let dst = dst.add(1).cast::(); + for w in 0..8 { + dst.add(w).write_unaligned(src.add(w).read_unaligned()); + } + out.set_len(len + 33); + } +} + +/// A child slot whose top bit is set holds no node: the low bits are the +/// byte offset, in `ArenaTrie::stream`, of the 32-byte digest the parent's +/// RLP references (a sibling outside the witness). The witness carries far +/// more such digests than nodes (a reth block: ~160 K digests against ~20 K +/// nodes), and materialising each as a node cost two arena pushes and a +/// reallocation or two per trie. +const DIGEST_BIT: NodeId = 1 << 31; + +/// A Merkle Patricia trie whose nodes live in one arena. +#[derive(Debug, Clone)] +pub struct ArenaTrie { + nodes: Vec, + /// Cached reference of each node; `None` after a mutation on its path. + refs: Vec>, + root: NodeId, + /// The witness stream the digest slots point into (empty for a trie + /// built by insertion). + stream: Bytes, +} + +impl Default for ArenaTrie { + fn default() -> Self { + Self { nodes: vec![Node::Null], refs: vec![None], root: 0, stream: Bytes::new() } + } +} + +struct Stream<'a> { + buf: &'a [u8], + pos: usize, +} + +impl<'a> Stream<'a> { + #[inline] + fn peek(&self) -> Option { + self.buf.get(self.pos).copied() + } + fn byte(&mut self) -> Result { + let b = *self.buf.get(self.pos).ok_or(Error::WitnessFormat("truncated stream"))?; + self.pos += 1; + Ok(b) + } + fn varint(&mut self) -> Result { + let mut v = 0usize; + let mut shift = 0; + loop { + let b = self.byte()?; + v |= ((b & 0x7f) as usize) << shift; + if b & 0x80 == 0 { + return Ok(v); + } + shift += 7; + if shift > 28 { + return Err(Error::WitnessFormat("varint too long")); + } + } + } + fn node(&mut self) -> Result<&'a [u8], Error> { + let len = self.varint()?; + let bytes = + self.buf.get(self.pos..self.pos + len).ok_or(Error::WitnessFormat("truncated node"))?; + self.pos += len; + Ok(bytes) + } +} + +/// Split one RLP item off the front of `buf`: (header, payload, whole item). +#[inline] +fn take_item<'a>( + buf: &mut &'a [u8], +) -> Result<(alloy_rlp::Header, &'a [u8], &'a [u8]), alloy_rlp::Error> { + // Fast paths. Nearly every item inside a witness trie node is either an + // absent child (`0x80`) or a 32-byte digest (`0xa0 ..`), and there are + // ~200 K of them per reth block: the generic header decoder's bounds + // checks and length arithmetic are most of what the witness load spends. + match buf.first() { + Some(&alloy_rlp::EMPTY_STRING_CODE) => { + let (whole, rest) = buf.split_at(1); + *buf = rest; + return Ok((alloy_rlp::Header { list: false, payload_length: 0 }, &whole[1..], whole)); + } + Some(&DIGEST_STRING_CODE) if buf.len() >= 33 => { + let (whole, rest) = buf.split_at(33); + *buf = rest; + return Ok((alloy_rlp::Header { list: false, payload_length: 32 }, &whole[1..], whole)); + } + _ => {} + } + let start = *buf; + let header = alloy_rlp::Header::decode(buf)?; + let payload = buf.get(..header.payload_length).ok_or(alloy_rlp::Error::InputTooShort)?; + *buf = &buf[header.payload_length..]; + let whole = &start[..start.len() - buf.len()]; + Ok((header, payload, whole)) +} + +impl ArenaTrie { + /// Build the trie from its root hash and preorder node stream, checking + /// every node's keccak against the digest that references it. + pub fn from_stream(root: B256, stream: Bytes) -> Result { + // Real nodes only: a witnessed node is a few hundred bytes on average + // (branches dominate), so this never reallocates. + let cap = stream.len() / 96 + 8; + let mut trie = ArenaTrie { + nodes: Vec::with_capacity(cap), + refs: Vec::with_capacity(cap), + root: NONE, + stream: stream.clone(), + }; + let mut s = Stream { buf: &stream, pos: 0 }; + let id = trie.build(&mut s, root, None)?; + if s.pos != stream.len() { + return Err(Error::WitnessFormat("trailing bytes")); + } + trie.root = id; + Ok(trie) + } + + #[inline] + fn is_digest(id: NodeId) -> bool { + id != NONE && id & DIGEST_BIT != 0 + } + + /// The digest a digest slot references. + #[inline] + fn digest_at(&self, id: NodeId) -> B256 { + let off = (id & !DIGEST_BIT) as usize; + B256::from_slice(&self.stream[off..off + 32]) + } + + #[inline] + fn push(&mut self, node: Node, r: Option) -> NodeId { + let id = self.nodes.len() as NodeId; + self.nodes.push(node); + self.refs.push(r); + id + } + + /// Next stream entry, which the parent references by `expected`. A + /// digest entry becomes `slot` when the parent offers one (the offset of + /// the digest in its own RLP), else a `Node::Digest` (the root). + fn build( + &mut self, + s: &mut Stream<'_>, + expected: B256, + slot: Option, + ) -> Result { + match s.byte()? { + TAG_DIGEST => Ok(match slot { + Some(id) => id, + None => self.push(Node::Digest(expected), Some(Ref::Digest(expected))), + }), + TAG_NODE => { + let bytes = s.node()?; + if !eq32(&keccak(bytes), &expected.0) { + return Err(Error::WitnessNodeMismatch(expected)); + } + let r = if bytes.len() < 32 { + Ref::Bytes(bytes.to_vec()) + } else { + Ref::Digest(expected) + }; + let mut buf = bytes; + let (header, payload, _) = take_item(&mut buf)?; + if !buf.is_empty() { + return Err(Error::WitnessFormat("node has trailing bytes")); + } + self.decode_item(header, payload, r, s) + } + _ => Err(Error::WitnessFormat("bad tag")), + } + } + + /// A child item inside a node's RLP: absent, a digest (whose node is the + /// next stream entry), or an inline node. + fn child( + &mut self, + header: alloy_rlp::Header, + payload: &[u8], + whole: &[u8], + s: &mut Stream<'_>, + ) -> Result { + if header.list { + return self.decode_item(header, payload, Ref::Bytes(whole.to_vec()), s); + } + match payload.len() { + 0 => Ok(NONE), + 32 => { + // Where this digest sits in the stream, for a digest slot. + let off = payload.as_ptr() as usize - self.stream.as_ptr() as usize; + self.build(s, B256::from_slice(payload), Some(DIGEST_BIT | off as NodeId)) + } + _ => Err(alloy_rlp::Error::UnexpectedLength.into()), + } + } + + /// A child item already split off its node: absent and digest-of-digest + /// children are resolved without a call, the rest through `child`. + #[inline] + fn common_child( + &mut self, + header: alloy_rlp::Header, + payload: &[u8], + whole: &[u8], + s: &mut Stream<'_>, + ) -> Result { + if !header.list { + if payload.is_empty() { + return Ok(NONE); + } + if payload.len() == 32 && s.peek() == Some(TAG_DIGEST) { + s.pos += 1; + let off = payload.as_ptr() as usize - self.stream.as_ptr() as usize; + return Ok(DIGEST_BIT | off as NodeId); + } + } + self.child(header, payload, whole, s) + } + + /// Decode one node item into the arena. + fn decode_item( + &mut self, + header: alloy_rlp::Header, + payload: &[u8], + r: Ref, + s: &mut Stream<'_>, + ) -> Result { + if !header.list { + return match payload.len() { + 0 => Ok(self.push(Node::Null, Some(r))), + _ => Err(alloy_rlp::Error::UnexpectedString.into()), + }; + } + // ONE pass. A node is either 2 items (leaf or extension) or 17 (a + // branch), and the first two tell them apart: a second item that does + // not end the payload means a branch. Counting the items first, as + // this did, parsed every node twice. + let mut rest = payload; + let (h0, p0, w0) = take_item(&mut rest)?; + let (h1, p1, w1) = take_item(&mut rest)?; + if rest.is_empty() { + // Item 0 is the compact-encoded path; its high nibble says whether + // item 1 is a value (leaf) or a child (extension). + if h0.list || p0.is_empty() { + return Err(alloy_rlp::Error::UnexpectedList.into()); + } + return if p0[0] & 0x20 == 0 { + let child = self.child(h1, p1, w1, s)?; + if child == NONE { + return Err(Error::WitnessFormat("extension without child")); + } + let prefix = self.stream.slice_ref(p0); + Ok(self.push(Node::Extension(prefix, child), Some(r))) + } else { + if h1.list { + return Err(alloy_rlp::Error::UnexpectedList.into()); + } + let (prefix, value) = (self.stream.slice_ref(p0), self.stream.slice_ref(p1)); + Ok(self.push(Node::Leaf(prefix, value), Some(r))) + }; + } + // A branch: 16 children then an empty value. The children are walked + // in order because `child` may consume the next stream entry. The + // two common children, absent (`0x80`) and a digest (`0xa0 ..`) whose + // stream entry is itself a digest, are handled here without the + // generic item split and the two calls behind it: they are ~95% of + // the ~160 K branch children of a reth block. + let mut children = [NONE; 16]; + children[0] = self.common_child(h0, p0, w0, s)?; + children[1] = self.common_child(h1, p1, w1, s)?; + for slot in children[2..].iter_mut() { + match rest.first() { + Some(&alloy_rlp::EMPTY_STRING_CODE) => { + rest = &rest[1..]; + } + Some(&DIGEST_STRING_CODE) if rest.len() >= 33 && s.peek() == Some(TAG_DIGEST) => { + s.pos += 1; + let off = rest.as_ptr() as usize + 1 - self.stream.as_ptr() as usize; + *slot = DIGEST_BIT | off as NodeId; + rest = &rest[33..]; + } + _ => { + let (h, item, whole) = take_item(&mut rest)?; + *slot = self.child(h, item, whole, s)?; + } + } + } + let (h, value, _) = take_item(&mut rest)?; + if h.list || !value.is_empty() { + return Err(Error::WitnessFormat("branch node with value")); + } + if !rest.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength.into()); + } + Ok(self.push(Node::Branch(children), Some(r))) + } + + // -- reads --------------------------------------------------------------- + + /// The value stored under `key`, if any. + pub fn get(&self, key: &[u8]) -> Result, Error> { + let (nibs, n) = nibbles(key); + self.get_internal(self.root, &nibs[..n]) + } + + /// The RLP-decoded value stored under `key`, if any. + pub fn get_rlp(&self, key: &[u8]) -> Result, Error> { + match self.get(key)? { + Some(mut bytes) => Ok(Some(T::decode(&mut bytes)?)), + None => Ok(None), + } + } + + fn get_internal(&self, id: NodeId, key_nibs: &[u8]) -> Result, Error> { + if Self::is_digest(id) { + return Err(Error::NodeNotResolved(self.digest_at(id))); + } + match &self.nodes[id as usize] { + Node::Null => Ok(None), + Node::Branch(children) => match key_nibs.split_first() { + Some((i, tail)) => match children[*i as usize] { + NONE => Ok(None), + child => self.get_internal(child, tail), + }, + None => Ok(None), + }, + Node::Leaf(prefix, value) => { + if strip_prefix_nibs(key_nibs, prefix) == Some(&[][..]) { + Ok(Some(&value[..])) + } else { + Ok(None) + } + } + Node::Extension(prefix, child) => match strip_prefix_nibs(key_nibs, prefix) { + Some(tail) => self.get_internal(*child, tail), + None => Ok(None), + }, + Node::Digest(digest) => Err(Error::NodeNotResolved(*digest)), + } + } + + /// Whether the trie holds no key. + pub fn is_empty(&self) -> bool { + matches!(self.nodes[self.root as usize], Node::Null) + } + + /// Remove every key. + pub fn clear(&mut self) { + self.nodes[self.root as usize] = Node::Null; + self.refs[self.root as usize] = None; + } + + // -- hashing ------------------------------------------------------------- + + /// The root hash. + pub fn hash(&mut self) -> B256 { + match self.nodes[self.root as usize] { + Node::Null => EMPTY_ROOT, + _ => match self.reference(self.root) { + Ref::Digest(d) => d, + Ref::Bytes(b) => keccak(b).into(), + }, + } + } + + fn reference(&mut self, id: NodeId) -> Ref { + if Self::is_digest(id) { + return Ref::Digest(self.digest_at(id)); + } + self.ensure_reference(id); + self.refs[id as usize].clone().expect("the reference is cached") + } + + /// Caches the reference of the node `id` (not a digest slot). + fn ensure_reference(&mut self, id: NodeId) { + if self.refs[id as usize].is_some() { + return; + } + let r = match &self.nodes[id as usize] { + Node::Null => Ref::Bytes(vec![alloy_rlp::EMPTY_STRING_CODE]), + Node::Digest(d) => Ref::Digest(*d), + _ => { + let encoded = self.encode(id); + if encoded.len() < 32 { + Ref::Bytes(encoded) + } else { + Ref::Digest(keccak(&encoded).into()) + } + } + }; + self.refs[id as usize] = Some(r); + } + + fn reference_length(&mut self, id: NodeId) -> usize { + if Self::is_digest(id) { + return 33; + } + self.ensure_reference(id); + self.refs[id as usize].as_ref().expect("the reference is cached").length() + } + + /// Writes the RLP reference of `id` to `out`: a digest slot's 32 bytes + /// from the witness stream, else the cached (or now computed) reference. + fn write_reference(&mut self, id: NodeId, out: &mut Vec) { + if Self::is_digest(id) { + let off = (id & !DIGEST_BIT) as usize; + let digest: &[u8; 32] = self.stream[off..off + 32].try_into().expect("32 bytes"); + push_digest(out, digest); + return; + } + self.ensure_reference(id); + self.refs[id as usize].as_ref().expect("the reference is cached").encode(out); + } + + fn encode(&mut self, id: NodeId) -> Vec { + match &self.nodes[id as usize] { + Node::Null => vec![alloy_rlp::EMPTY_STRING_CODE], + Node::Digest(d) => d.to_rlp(), + Node::Leaf(prefix, value) => { + let payload = prefix[..].length() + value[..].length(); + let mut out = Vec::with_capacity(payload + 3); + alloy_rlp::Header { list: true, payload_length: payload }.encode(&mut out); + prefix[..].encode(&mut out); + value[..].encode(&mut out); + out + } + Node::Extension(prefix, child) => { + let child = *child; + let prefix_len = prefix[..].length(); + let payload = prefix_len + self.reference_length(child); + let mut out = Vec::with_capacity(payload + 3); + alloy_rlp::Header { list: true, payload_length: payload }.encode(&mut out); + let Node::Extension(prefix, _) = &self.nodes[id as usize] else { + unreachable!("the node is an extension") + }; + prefix[..].encode(&mut out); + self.write_reference(child, &mut out); + out + } + Node::Branch(children) => { + let children = *children; + // Two passes over the children, sizing then writing, so a + // digest slot never has to become a `Ref`: its 32 bytes are + // copied straight from the witness stream into the output. + let mut payload = 1; // the empty value + for child in children.iter() { + payload += if *child == NONE { 1 } else { self.reference_length(*child) }; + } + let mut out = Vec::with_capacity(payload + 3); + alloy_rlp::Header { list: true, payload_length: payload }.encode(&mut out); + for child in children.iter() { + if *child == NONE { + out.push(alloy_rlp::EMPTY_STRING_CODE); + } else { + self.write_reference(*child, &mut out); + } + } + out.push(alloy_rlp::EMPTY_STRING_CODE); + out + } + } + } + + // -- writes -------------------------------------------------------------- + + /// Insert or update `key`; `true` if the trie changed. + pub fn insert(&mut self, key: &[u8], value: Vec) -> Result { + assert!(!value.is_empty(), "value must not be empty"); + let (nibs, n) = nibbles(key); + self.insert_internal(self.root, &nibs[..n], value.into()) + } + + /// Insert or update `key` with the RLP encoding of `value`. + pub fn insert_rlp(&mut self, key: &[u8], value: impl Encodable) -> Result { + let (nibs, n) = nibbles(key); + self.insert_internal(self.root, &nibs[..n], value.to_rlp().into()) + } + + #[inline] + fn leaf(&mut self, nibs: &[u8], value: Bytes) -> NodeId { + self.push(Node::Leaf(to_encoded_path(nibs, true).into(), value), None) + } + + fn insert_internal( + &mut self, + id: NodeId, + key_nibs: &[u8], + value: Bytes, + ) -> Result { + if Self::is_digest(id) { + return Err(Error::NodeNotResolved(self.digest_at(id))); + } + let changed = match &mut self.nodes[id as usize] { + Node::Null => { + self.nodes[id as usize] = Node::Leaf(to_encoded_path(key_nibs, true).into(), value); + true + } + Node::Branch(children) => { + let Some((i, tail)) = key_nibs.split_first() else { + return Err(Error::ValueInBranch); + }; + let child = children[*i as usize]; + if child != NONE { + if !self.insert_internal(child, tail, value)? { + return Ok(false); + } + } else { + let (i, tail) = (*i as usize, tail.to_vec()); + let leaf = self.leaf(&tail, value); + if let Node::Branch(children) = &mut self.nodes[id as usize] { + children[i] = leaf; + } + } + true + } + Node::Leaf(prefix, old_value) => { + let self_nibs = prefix_nibs(prefix); + let common_len = lcp(&self_nibs, key_nibs); + if common_len == self_nibs.len() && common_len == key_nibs.len() { + if *old_value == value { + return Ok(false); + } + *old_value = value; + } else if common_len == self_nibs.len() || common_len == key_nibs.len() { + return Err(Error::ValueInBranch); + } else { + let old_value = mem::take(old_value); + let split = common_len + 1; + let mut children = [NONE; 16]; + children[self_nibs[common_len] as usize] = + self.leaf(&self_nibs[split..], old_value); + children[key_nibs[common_len] as usize] = self.leaf(&key_nibs[split..], value); + self.replace_with_branch(id, &self_nibs[..common_len], children); + } + true + } + Node::Extension(prefix, existing_child) => { + let existing_child = *existing_child; + let self_nibs = prefix_nibs(prefix); + let common_len = lcp(&self_nibs, key_nibs); + if common_len == self_nibs.len() { + if !self.insert_internal(existing_child, &key_nibs[common_len..], value)? { + return Ok(false); + } + } else if common_len == key_nibs.len() { + return Err(Error::ValueInBranch); + } else { + let split = common_len + 1; + let mut children = [NONE; 16]; + children[self_nibs[common_len] as usize] = if split < self_nibs.len() { + self.push( + Node::Extension( + to_encoded_path(&self_nibs[split..], false).into(), + existing_child, + ), + None, + ) + } else { + existing_child + }; + children[key_nibs[common_len] as usize] = self.leaf(&key_nibs[split..], value); + self.replace_with_branch(id, &self_nibs[..common_len], children); + } + true + } + Node::Digest(digest) => return Err(Error::NodeNotResolved(*digest)), + }; + if changed { + self.refs[id as usize] = None; + } + Ok(changed) + } + + /// Node `id` becomes `branch`, under an extension for `prefix_nibs` when + /// that is non-empty. + fn replace_with_branch(&mut self, id: NodeId, prefix_nibs: &[u8], children: [NodeId; 16]) { + if prefix_nibs.is_empty() { + self.nodes[id as usize] = Node::Branch(children); + } else { + let branch = self.push(Node::Branch(children), None); + self.nodes[id as usize] = + Node::Extension(to_encoded_path(prefix_nibs, false).into(), branch); + } + } + + /// Delete `key`; `true` if it was present. + pub fn delete(&mut self, key: &[u8]) -> Result { + let (nibs, n) = nibbles(key); + self.delete_internal(self.root, &nibs[..n]) + } + + fn delete_internal(&mut self, id: NodeId, key_nibs: &[u8]) -> Result { + if Self::is_digest(id) { + return Err(Error::NodeNotResolved(self.digest_at(id))); + } + match &self.nodes[id as usize] { + Node::Null => return Ok(false), + Node::Branch(children) => { + let Some((i, tail)) = key_nibs.split_first() else { + return Err(Error::ValueInBranch); + }; + let child = children[*i as usize]; + if child == NONE || !self.delete_internal(child, tail)? { + return Ok(false); + } + let i = *i as usize; + if matches!(self.nodes[child as usize], Node::Null) { + if let Node::Branch(children) = &mut self.nodes[id as usize] { + children[i] = NONE; + } + } + let Node::Branch(children) = &self.nodes[id as usize] else { unreachable!() }; + let mut remaining = children.iter().enumerate().filter(|(_, c)| **c != NONE); + // there is always at least one child left + let (index, orphan) = remaining.next().map(|(i, c)| (i, *c)).unwrap(); + if remaining.next().is_none() { + // one child left: the branch collapses into it + let new = if Self::is_digest(orphan) { + // a digest slot has no node: the branch becomes an + // extension to the digest + Node::Extension(to_encoded_path(&[index as u8], false).into(), orphan) + } else { + match mem::replace(&mut self.nodes[orphan as usize], Node::Null) { + Node::Leaf(prefix, value) => { + let mut nibs = vec![index as u8]; + nibs.extend(prefix_nibs(&prefix)); + Node::Leaf(to_encoded_path(&nibs, true).into(), value) + } + Node::Extension(prefix, child) => { + let mut nibs = vec![index as u8]; + nibs.extend(prefix_nibs(&prefix)); + Node::Extension(to_encoded_path(&nibs, false).into(), child) + } + node @ (Node::Branch(_) | Node::Digest(_)) => { + // the orphan keeps its slot; the branch becomes an extension to it + self.nodes[orphan as usize] = node; + Node::Extension( + to_encoded_path(&[index as u8], false).into(), + orphan, + ) + } + Node::Null => unreachable!(), + } + }; + self.nodes[id as usize] = new; + } + } + Node::Leaf(prefix, _) => { + if strip_prefix_nibs(key_nibs, prefix) != Some(&[][..]) { + return Ok(false); + } + self.nodes[id as usize] = Node::Null; + } + Node::Extension(prefix, child) => { + let child = *child; + let mut self_nibs = prefix_nibs(prefix); + let Some(tail) = strip_prefix_nibs(key_nibs, prefix) else { return Ok(false) }; + if !self.delete_internal(child, tail)? { + return Ok(false); + } + // an extension points to a branch or digest; re-establish that + let new = match mem::replace(&mut self.nodes[child as usize], Node::Null) { + Node::Null => Some(Node::Null), + Node::Leaf(prefix, value) => { + self_nibs.extend(prefix_nibs(&prefix)); + Some(Node::Leaf(to_encoded_path(&self_nibs, true).into(), value)) + } + Node::Extension(prefix, grandchild) => { + self_nibs.extend(prefix_nibs(&prefix)); + Some(Node::Extension(to_encoded_path(&self_nibs, false).into(), grandchild)) + } + node @ (Node::Branch(_) | Node::Digest(_)) => { + self.nodes[child as usize] = node; + None + } + }; + if let Some(new) = new { + self.nodes[id as usize] = new; + } + } + Node::Digest(digest) => return Err(Error::NodeNotResolved(*digest)), + } + self.refs[id as usize] = None; + Ok(true) + } +} + +/// 32-byte equality as eight word compares: the generic `memcmp` takes the +/// byte loop for these stack arrays. +#[inline] +fn eq32(a: &[u8; 32], b: &[u8; 32]) -> bool { + let mut acc = 0u32; + for i in (0..32).step_by(4) { + acc |= u32::from_ne_bytes([a[i], a[i + 1], a[i + 2], a[i + 3]]) ^ + u32::from_ne_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]); + } + acc == 0 +} + +/// The nibbles of `key` in a stack buffer, and how many: keys are 32-byte +/// hashes, so the buffer needs no allocation. +/// +/// # Panics +/// Panics if `key` is longer than 64 bytes. +#[inline] +fn nibbles(key: &[u8]) -> ([u8; 128], usize) { + assert!(key.len() <= 64, "key too long"); + let mut out = [0u8; 128]; + for (i, b) in key.iter().enumerate() { + out[2 * i] = b >> 4; + out[2 * i + 1] = b & 0xf; + } + (out, 2 * key.len()) +} + +/// `key_nibs` with a compact-encoded `prefix` stripped off the front, without +/// materialising the prefix's nibbles. +fn strip_prefix_nibs<'a>(key_nibs: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> { + let (first, tail) = prefix.split_first()?; + let mut rest = key_nibs; + if first & 0x10 != 0 { + let (k, r) = rest.split_first()?; + if *k != first & 0xf { + return None; + } + rest = r; + } + for byte in tail { + let (k, r) = rest.split_first_chunk::<2>()?; + if k[0] != byte >> 4 || k[1] != byte & 0xf { + return None; + } + rest = r; + } + Some(rest) +} + +// --------------------------------------------------------------------------- +// The guest's state: arena tries + the update logic of `EthereumState` +// --------------------------------------------------------------------------- + +/// State and storage tries materialised from a [`WitnessState`]. +#[derive(Debug, Clone, Default)] +pub struct ArenaState { + pub state_trie: ArenaTrie, + pub storage_tries: HashMap, +} + +impl ArenaState { + /// Build every trie, checking each node against the digest that references + /// it and each trie against its root; the state root is checked against + /// `witness.state_root`, the storage roots against the account leaves. + pub fn from_witness(witness: &WitnessState) -> Result { + let state_trie = ArenaTrie::from_stream(witness.state_root, witness.state_nodes.clone())?; + let mut storage_tries = + HashMap::with_capacity_and_hasher(witness.storage.len(), Default::default()); + for StorageWitness { hashed_address, root, nodes } in &witness.storage { + let expected = state_trie + .get_rlp::(hashed_address.as_slice())? + .map_or(EMPTY_ROOT_HASH, |a| a.storage_root); + if *root != expected { + return Err(Error::WitnessNodeMismatch(expected)); + } + storage_tries.insert(*hashed_address, ArenaTrie::from_stream(*root, nodes.clone())?); + } + Ok(Self { state_trie, storage_tries }) + } + + /// The account under `hashed_address`, if any. + pub fn account(&self, hashed_address: &[u8]) -> Result, Error> { + self.state_trie.get_rlp::(hashed_address) + } + + /// The storage slot `hashed_slot` of `hashed_address`, if set. + /// + /// A witness may omit the storage trie of an absent account or of one + /// whose authenticated storage root is empty; their slots are zero. Any + /// other missing trie is an unresolved storage root, never a zero. + pub fn storage( + &self, + hashed_address: &B256, + hashed_slot: &[u8], + ) -> Result, Error> { + match self.storage_tries.get(hashed_address) { + Some(trie) => trie.get_rlp::(hashed_slot), + None => { + let root = self + .account(hashed_address.as_slice())? + .map_or(EMPTY_ROOT_HASH, |account| account.storage_root); + if root == EMPTY_ROOT_HASH { + Ok(None) + } else { + Err(Error::NodeNotResolved(root)) + } + } + } + } + + /// Apply the post-state diff (same semantics as `EthereumState::update`). + pub fn update(&mut self, post_state: &HashedPostState) { + let no_storage = HashedStorage::new(false); + for (hashed_address, account) in post_state.accounts.iter() { + match account { + Some(account) => { + let state_storage = + post_state.storages.get(hashed_address).unwrap_or(&no_storage); + let storage_root = + self.storage_root_after_update(*hashed_address, state_storage); + + if account.is_empty() && storage_root == EMPTY_ROOT_HASH { + self.state_trie.delete(hashed_address.as_slice()).unwrap(); + self.storage_tries.remove(hashed_address); + continue; + } + + let state_account = TrieAccount { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.get_bytecode_hash(), + }; + self.state_trie.insert_rlp(hashed_address.as_slice(), state_account).unwrap(); + } + None => { + self.state_trie.delete(hashed_address.as_slice()).unwrap(); + self.storage_tries.remove(hashed_address); + } + } + } + } + + fn storage_root_after_update( + &mut self, + hashed_address: B256, + state_storage: &HashedStorage, + ) -> B256 { + if state_storage.is_empty() { + if let Some(storage_trie) = self.storage_tries.get_mut(&hashed_address) { + return storage_trie.hash(); + } + return self + .state_trie + .get_rlp::(hashed_address.as_slice()) + .unwrap() + .map(|account| account.storage_root) + .unwrap_or(EMPTY_ROOT_HASH); + } + + let storage_trie = self.storage_tries.entry(hashed_address).or_default(); + if state_storage.wiped { + storage_trie.clear(); + } + for (key, value) in state_storage.storage.iter() { + let key = key.as_slice(); + if value.is_zero() { + storage_trie.delete(key).unwrap(); + } else { + storage_trie.insert_rlp(key, *value).unwrap(); + } + } + storage_trie.hash() + } + + /// The state root. + pub fn state_root(&mut self) -> B256 { + self.state_trie.hash() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::EthereumState; + use alloy_primitives::{keccak256, U256}; + use reth_trie::Nibbles; + + fn state_with(account: Option<(B256, TrieAccount)>) -> ArenaState { + let mut state = ArenaState::default(); + if let Some((hashed_address, account)) = account { + state.state_trie.insert_rlp(hashed_address.as_slice(), account).unwrap(); + } + state + } + + #[test] + fn omitted_storage_for_absent_account_is_zero() { + let state = state_with(None); + let slot = keccak256(U256::from(7).to_be_bytes::<32>()); + assert_eq!(state.storage(&B256::repeat_byte(1), slot.as_slice()).unwrap(), None); + } + + #[test] + fn omitted_storage_with_authenticated_empty_root_is_zero() { + let hashed_address = B256::repeat_byte(2); + let state = state_with(Some((hashed_address, TrieAccount::default()))); + let slot = keccak256(U256::from(7).to_be_bytes::<32>()); + assert_eq!(state.storage(&hashed_address, slot.as_slice()).unwrap(), None); + } + + #[test] + fn omitted_non_empty_storage_is_rejected() { + let hashed_address = B256::repeat_byte(3); + let account = TrieAccount { storage_root: B256::repeat_byte(4), ..Default::default() }; + let state = state_with(Some((hashed_address, account))); + let slot = keccak256(U256::from(7).to_be_bytes::<32>()); + assert!(state.storage(&hashed_address, slot.as_slice()).is_err()); + } + + fn key(i: u64) -> B256 { + keccak256(i.to_be_bytes()) + } + + fn rng(state: &mut u64) -> u64 { + *state ^= *state << 13; + *state ^= *state >> 7; + *state ^= *state << 17; + *state + } + + fn oracle_trie(n: u64, seed: u64) -> MptNode { + let mut trie = MptNode::default(); + let mut s = seed | 1; + for i in 0..n { + trie.insert_rlp(key(i).as_slice(), U256::from(rng(&mut s))).unwrap(); + } + trie + } + + fn arena_of(trie: &MptNode) -> ArenaTrie { + ArenaTrie::from_stream(trie.hash(), witness_stream(trie)).unwrap() + } + + #[test] + fn stream_roundtrip_matches_oracle() { + for &n in &[0u64, 1, 2, 3, 17, 300, 2000] { + let oracle = oracle_trie(n, 7 + n); + let mut arena = arena_of(&oracle); + assert_eq!(arena.hash(), oracle.hash(), "n={n}"); + for i in 0..n + 50 { + let k = key(i); + assert_eq!( + arena.get(k.as_slice()).unwrap(), + oracle.get(k.as_slice()).unwrap(), + "n={n} key {i}" + ); + } + } + } + + #[test] + fn mutations_match_oracle() { + let mut oracle = oracle_trie(400, 99); + let mut arena = arena_of(&oracle); + let mut s = 12345u64; + for step in 0..3000u64 { + let r = rng(&mut s); + let i = r % 700; // hits existing (0..400) and fresh keys + let k = key(i); + match r % 5 { + 0 | 1 => { + let v = U256::from(rng(&mut s)); + let a = arena.insert_rlp(k.as_slice(), v).unwrap(); + let b = oracle.insert_rlp(k.as_slice(), v).unwrap(); + assert_eq!(a, b, "insert step {step}"); + } + 2 | 3 => { + let a = arena.delete(k.as_slice()).unwrap(); + let b = oracle.delete(k.as_slice()).unwrap(); + assert_eq!(a, b, "delete step {step}"); + } + _ => { + assert_eq!(arena.get(k.as_slice()).unwrap(), oracle.get(k.as_slice()).unwrap()); + } + } + if step % 97 == 0 { + assert_eq!(arena.hash(), oracle.hash(), "root after step {step}"); + } + } + assert_eq!(arena.hash(), oracle.hash()); + assert_eq!(arena.is_empty(), oracle.is_empty()); + } + + /// Replace the child at `nibble` of the root branch (after descending any + /// root extension) with an unresolved digest, like a partial witness. + fn plant_digest(trie: &mut MptNode, nibble: usize) -> bool { + let mut node = trie; + loop { + match node.data_mut() { + MptNodeData::Extension(_, child) => node = child, + MptNodeData::Branch(children) => { + return match &mut children[nibble] { + Some(child) => { + let h = child.hash(); + *child.data_mut() = MptNodeData::Digest(h); + true + } + None => false, + } + } + _ => return false, + } + } + } + + #[test] + fn unresolved_subtrees_match_oracle() { + let mut oracle = oracle_trie(600, 5); + let full_root = oracle.hash(); + assert!(plant_digest(&mut oracle, 3)); + assert!(plant_digest(&mut oracle, 11)); + assert_eq!(oracle.hash(), full_root, "digest planting keeps the root"); + let mut arena = arena_of(&oracle); + assert_eq!(arena.hash(), full_root); + let mut resolved = 0; + let mut unresolved = 0; + for i in 0..600u64 { + let k = key(i); + match (arena.get(k.as_slice()), oracle.get(k.as_slice())) { + (Ok(a), Ok(b)) => { + assert_eq!(a, b); + resolved += 1; + } + (Err(Error::NodeNotResolved(a)), Err(Error::NodeNotResolved(b))) => { + assert_eq!(a, b); + unresolved += 1; + } + (a, b) => panic!("key {i}: {a:?} vs {b:?}"), + } + } + assert!(resolved > 0 && unresolved > 0, "{resolved} {unresolved}"); + // mutations on resolved paths still agree + for i in 0..600u64 { + let k = key(i); + if arena.get(k.as_slice()).is_ok() { + let v = U256::from(i * 31 + 7); + arena.insert_rlp(k.as_slice(), v).unwrap(); + oracle.insert_rlp(k.as_slice(), v).unwrap(); + } + } + assert_eq!(arena.hash(), oracle.hash()); + } + + #[test] + fn tampered_node_is_rejected() { + let oracle = oracle_trie(50, 3); + let mut stream = witness_stream(&oracle).to_vec(); + // flip a byte inside the first node's payload + let last = stream.len() - 1; + stream[last] ^= 1; + assert!(matches!( + ArenaTrie::from_stream(oracle.hash(), Bytes::from(stream.clone())), + Err(Error::WitnessNodeMismatch(_)) + )); + assert!(ArenaTrie::from_stream(B256::ZERO, witness_stream(&oracle)).is_err()); + } + + #[test] + fn state_update_matches_oracle() { + use reth_primitives_traits::Account; + let mut s = 777u64; + let mut eager = EthereumState::default(); + for i in 0..120u64 { + let addr = key(i); + let mut storage = MptNode::default(); + for j in 0..(i % 9) { + storage + .insert_rlp(key(1000 + i * 10 + j).as_slice(), U256::from(rng(&mut s))) + .unwrap(); + } + let account = TrieAccount { + nonce: i, + balance: U256::from(rng(&mut s)), + storage_root: storage.hash(), + code_hash: key(5000 + i), + }; + eager.state_trie.insert_rlp(addr.as_slice(), account).unwrap(); + eager.storage_tries.insert(addr, storage); + } + let witness = eager.to_witness_state(); + let mut arena = ArenaState::from_witness(&witness).unwrap(); + assert_eq!(arena.state_root(), eager.state_root()); + + let mut post = HashedPostState::default(); + for i in 0..120u64 { + let addr = key(i); + match i % 4 { + 0 => { + post.accounts.insert(addr, None); + } + 1 => { + post.accounts.insert( + addr, + Some(Account { + nonce: i + 1, + balance: U256::from(rng(&mut s)), + bytecode_hash: Some(key(5000 + i)), + }), + ); + let mut hs = HashedStorage::new(false); + for j in 0..6u64 { + let v = if j % 2 == 0 { U256::ZERO } else { U256::from(rng(&mut s)) }; + hs.storage.insert(key(1000 + i * 10 + j), v); + } + post.storages.insert(addr, hs); + } + 2 => { + post.accounts.insert( + addr, + Some(Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None }), + ); + post.storages.insert(addr, HashedStorage::new(true)); + } + _ => { + post.accounts.insert( + key(9000 + i), + Some(Account { nonce: 1, balance: U256::from(5), bytecode_hash: None }), + ); + } + } + } + eager.update(&post); + arena.update(&post); + assert_eq!(arena.state_root(), eager.state_root()); + let _ = Nibbles::default(); + } +} diff --git a/crates/mpt/src/lib.rs b/crates/mpt/src/lib.rs index 3f3415b..45a77e4 100644 --- a/crates/mpt/src/lib.rs +++ b/crates/mpt/src/lib.rs @@ -8,16 +8,19 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "execution-witness")] mod execution_witness; +/// Arena-backed trie built from a preorder RLP witness stream (the guest's form). +mod arena; /// Module containing MPT code adapted from `zeth`. mod mpt; +pub use arena::{input_bytes, witness_stream, ArenaState, ArenaTrie, StorageWitness, WitnessState}; pub use mpt::Error; use mpt::{ - mpt_from_proof, parse_proof, proofs_to_tries, resolve_nodes, transition_proofs_to_tries, - MptNode, + extend_trie_from_proof, mpt_from_proof, node_from_digest, parse_proof, proofs_to_tries, + resolve_nodes, transition_proofs_to_tries, MptNode, }; /// Ethereum state trie and account storage tries. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct EthereumState { pub state_trie: MptNode, pub storage_tries: HashMap, @@ -72,6 +75,25 @@ impl EthereumState { Ok(state) } + /// Resolves missing account trie nodes from an EIP-1186 account proof. + pub fn extend_from_account_proof( + &mut self, + proof: &EIP1186AccountProofResponse, + ) -> Result<(), FromProofError> { + self.state_trie = extend_trie_from_proof(&self.state_trie, &proof.account_proof)?; + let hashed_address = keccak256(proof.address); + for storage_proof in &proof.storage_proof { + let storage_trie = self + .storage_tries + .entry(hashed_address) + .or_insert_with(|| node_from_digest(proof.storage_hash)); + if !storage_proof.proof.is_empty() { + *storage_trie = extend_trie_from_proof(storage_trie, &storage_proof.proof)?; + } + } + Ok(()) + } + #[cfg(feature = "execution-witness")] pub fn from_execution_witness( witness: &alloy_rpc_types_debug::ExecutionWitness, @@ -158,6 +180,27 @@ impl EthereumState { pub fn state_root(&self) -> B256 { self.state_trie.hash() } + + /// The witness form the guest consumes: root hashes plus preorder streams + /// of raw RLP nodes for the state trie and every storage trie (sorted by + /// hashed address so the bytes are reproducible). + pub fn to_witness_state(&self) -> WitnessState { + let mut storage: Vec = self + .storage_tries + .iter() + .map(|(addr, trie)| StorageWitness { + hashed_address: *addr, + root: trie.hash(), + nodes: witness_stream(trie), + }) + .collect(); + storage.sort_by(|a, b| a.hashed_address.cmp(&b.hashed_address)); + WitnessState { + state_root: self.state_trie.hash(), + state_nodes: witness_stream(&self.state_trie), + storage, + } + } } #[derive(Debug, thiserror::Error)] @@ -180,6 +223,7 @@ pub enum FromProofError { #[cfg(test)] mod tests { use alloy_primitives::{b256, U256}; + use reth_primitives_traits::Account; use super::*; @@ -217,10 +261,10 @@ mod tests { }, ) .unwrap(); - let mut post_state = HashedPostState::default() - .with_accounts([(hashed_address, Some(Default::default()))]); - post_state.accounts.get_mut(&hashed_address).unwrap().as_mut().unwrap().balance = - U256::from(2); + let post_state = HashedPostState::default().with_accounts([( + hashed_address, + Some(Account { nonce: 0, balance: U256::from(2), bytecode_hash: None }), + )]); state.update(&post_state); diff --git a/crates/mpt/src/mpt.rs b/crates/mpt/src/mpt.rs index 39ed00a..5b3a577 100644 --- a/crates/mpt/src/mpt.rs +++ b/crates/mpt/src/mpt.rs @@ -141,6 +141,12 @@ pub enum Error { /// value provides details about the unresolved node. #[error("reached an unresolved node: {0:#}")] NodeNotResolved(B256), + /// A witness node's keccak does not match the digest that references it. + #[error("witness node does not match digest {0}")] + WitnessNodeMismatch(B256), + /// The witness node stream is malformed. + #[error("malformed witness stream: {0}")] + WitnessFormat(&'static str), /// Occurs when a value is unexpectedly found in a branch node. #[error("branch node with value")] ValueInBranch, @@ -339,6 +345,12 @@ impl MptNode { rlp::decode(bytes.as_ref()).map_err(Error::from) } + /// Mutable access to the node data (tests plant unresolved digests with it). + pub(crate) fn data_mut(&mut self) -> &mut MptNodeData { + self.invalidate_ref_cache(); + &mut self.data + } + /// Retrieves the underlying data of the node. /// /// This method provides a reference to the node's data, allowing for inspection and @@ -770,7 +782,7 @@ impl MptNode { Ok(true) } - fn invalidate_ref_cache(&mut self) { + pub(crate) fn invalidate_ref_cache(&mut self) { self.cached_reference.lock().unwrap().take(); } @@ -879,7 +891,7 @@ pub fn to_encoded_path(mut nibs: &[u8], is_leaf: bool) -> Vec { } /// Returns the length of the common prefix. -fn lcp(a: &[u8], b: &[u8]) -> usize { +pub(crate) fn lcp(a: &[u8], b: &[u8]) -> usize { for (i, (a, b)) in iter::zip(a, b).enumerate() { if a != b { return i; @@ -888,7 +900,7 @@ fn lcp(a: &[u8], b: &[u8]) -> usize { cmp::min(a.len(), b.len()) } -fn prefix_nibs(prefix: &[u8]) -> Vec { +pub(crate) fn prefix_nibs(prefix: &[u8]) -> Vec { let (extension, tail) = prefix.split_first().unwrap(); // the first bit of the first nibble denotes the parity let is_odd = extension & (1 << 4) != 0; @@ -993,6 +1005,24 @@ pub fn resolve_nodes(root: &MptNode, node_store: &HashMap], +) -> Result { + let proof_nodes = parse_proof(proof)?; + let proof_trie = mpt_from_proof(&proof_nodes)?; + if proof_trie.hash() != root.hash() { + return Err(FromProofError::MismatchedStateRoot(proof_trie.hash(), root.hash())) + } + + let mut node_store = HashMap::with_hasher(Default::default()); + proof_nodes.into_iter().for_each(|node| { + node_store.insert(node.reference(), node); + }); + + Ok(resolve_nodes(root, &node_store)) +} + /// Returns a list of all possible nodes that can be created by shortening the path of the /// given node. /// When nodes in an MPT are deleted, leaves or extensions may be extended. To still be @@ -1203,7 +1233,7 @@ fn add_orphaned_leafs( } /// Creates a new MPT node from a digest. -fn node_from_digest(digest: B256) -> MptNode { +pub(crate) fn node_from_digest(digest: B256) -> MptNode { match digest { EMPTY_ROOT | B256::ZERO => MptNode::default(), _ => MptNodeData::Digest(digest).into(), @@ -1344,6 +1374,32 @@ mod tests { trie.get(b"a0").unwrap_err(); } + #[test] + pub fn test_extend_trie_from_proof_resolves_digest_child() { + let mut full_trie = MptNode::default(); + full_trie.insert_rlp(b"aa", 0u8).unwrap(); + full_trie.insert_rlp(b"ab", 1u8).unwrap(); + full_trie.insert_rlp(b"ba", 2u8).unwrap(); + + let expected_hash = full_trie.hash(); + let mut partial_trie = full_trie.clone(); + let MptNodeData::Extension(_, node) = &mut partial_trie.data else { + panic!("extension expected") + }; + let missing_node = (**node).clone(); + **node = MptNodeData::Digest(missing_node.hash()).into(); + + partial_trie.get(b"aa").unwrap_err(); + + let proof = vec![full_trie.to_rlp(), missing_node.to_rlp()]; + let resolved_trie = extend_trie_from_proof(&partial_trie, &proof).unwrap(); + + assert_eq!(resolved_trie.hash(), expected_hash); + assert_eq!(resolved_trie.get_rlp::(b"aa").unwrap(), Some(0)); + assert_eq!(resolved_trie.get_rlp::(b"ab").unwrap(), Some(1)); + assert_eq!(resolved_trie.get_rlp::(b"ba").unwrap(), Some(2)); + } + #[test] pub fn test_branch_value() { let mut trie = MptNode::default(); diff --git a/crates/storage/rpc-db/src/execution_witness.rs b/crates/storage/rpc-db/src/execution_witness.rs index 8e1c2e4..db45dab 100644 --- a/crates/storage/rpc-db/src/execution_witness.rs +++ b/crates/storage/rpc-db/src/execution_witness.rs @@ -1,4 +1,8 @@ -use std::{collections::BTreeMap, marker::PhantomData, sync::RwLock}; +use std::{ + collections::BTreeMap, + marker::PhantomData, + sync::{Arc, RwLock}, +}; use alloy_consensus::Header; use alloy_eips::BlockNumberOrTag; @@ -7,7 +11,7 @@ use alloy_provider::{Network, Provider}; use alloy_rlp::{Decodable, Encodable}; use alloy_rpc_types::Header as RpcHeader; use alloy_rpc_types_debug::ExecutionWitness; -use alloy_trie::TrieAccount; +use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH}; use async_trait::async_trait; use mpt::EthereumState; use reth_storage_errors::{db::DatabaseError, ProviderError}; @@ -22,12 +26,12 @@ use crate::{RpcDb, RpcDbError}; pub struct ExecutionWitnessRpcDb { /// The provider which fetches data. pub provider: P, - /// Parent block used to fetch bytecode omitted from the execution witness. + /// The block to fetch missing pre-state proofs from. pub state_block_number: u64, /// The cached state. - pub state: EthereumState, + pub state: Arc>, /// The cached bytecodes. - pub codes: RwLock>, + pub codes: Arc>>, pub ancestor_headers: BTreeMap, @@ -37,10 +41,31 @@ pub struct ExecutionWitnessRpcDb { impl + Clone, N: Network> ExecutionWitnessRpcDb { /// Create a new [`ExecutionWitnessRpcDb`]. pub async fn new(provider: P, block_number: u64, state_root: B256) -> Result { + let execution_witness = Self::fetch_witness(&provider, block_number).await?; + Ok(Self::from_witness(provider, block_number, state_root, execution_witness)) + } + + /// Fetch the execution witness on its own. It depends only on the block + /// number, so the host can run it concurrently with the block fetches + /// and pass the result to [`Self::from_witness`] once the parent's state + /// root is known — the three RPC round trips were strictly serial before. + pub async fn fetch_witness( + provider: &P, + block_number: u64, + ) -> Result { tracing::info!("Fetching execution witness for block {}", block_number); - let execution_witness = fetch_execution_witness(&provider, block_number).await?; + let execution_witness = fetch_execution_witness(provider, block_number).await?; tracing::info!("Fetched execution witness for block done {}", block_number); + Ok(execution_witness) + } + /// Build the database from an already-fetched witness. + pub fn from_witness( + provider: P, + block_number: u64, + state_root: B256, + execution_witness: ExecutionWitness, + ) -> Self { let state = EthereumState::from_execution_witness(&execution_witness, state_root); let codes = execution_witness @@ -59,56 +84,137 @@ impl + Clone, N: Network> ExecutionWitnessRpcDb { let db = Self { provider, state_block_number: block_number.saturating_sub(1), - state, - codes: RwLock::new(codes), + state: Arc::new(RwLock::new(state)), + codes: Arc::new(RwLock::new(codes)), ancestor_headers, phantom: PhantomData, }; - Ok(db) + db + } + + /// Fetches and merges a missing account proof into the cached pre-state trie. + async fn fetch_missing_account_proof(&self, address: Address) -> Result<(), RpcDbError> { + tracing::warn!( + "Fetching missing account proof for address {} at block {}", + address, + self.state_block_number + ); + let proof = self + .provider + .get_proof(address, vec![]) + .number(self.state_block_number) + .await + .map_err(|err| RpcDbError::GetProofError(address, err.to_string()))?; + + self.state.write().map_err(|_| RpcDbError::Poisoned)?.extend_from_account_proof(&proof)?; + + Ok(()) } - fn account_code( + /// Fetches and merges a missing storage proof into the cached pre-state trie. + async fn fetch_missing_storage_proof( &self, address: Address, - expected_hash: B256, - ) -> Result, ProviderError> { - if expected_hash == KECCAK_EMPTY || expected_hash == B256::ZERO { - return Ok(None) - } - if let Some(code) = self - .codes - .read() - .map_err(|_| ProviderError::TrieWitnessError("bytecode cache lock poisoned".into()))? - .get(&expected_hash) - .cloned() - { - return Ok(Some(code)) - } + index: StorageKey, + ) -> Result<(), RpcDbError> { + tracing::warn!( + "Fetching missing storage proof for address {} slot {} at block {}", + address, + B256::from(index), + self.state_block_number + ); + let proof = self + .provider + .get_proof(address, vec![B256::from(index)]) + .number(self.state_block_number) + .await + .map_err(|err| RpcDbError::GetProofError(address, err.to_string()))?; + + self.state.write().map_err(|_| RpcDbError::Poisoned)?.extend_from_account_proof(&proof)?; + + Ok(()) + } - tracing::warn!(%address, %expected_hash, block = self.state_block_number, - "Fetching bytecode omitted from execution witness"); + fn fetch_missing_account_proof_blocking(&self, address: Address) -> Result<(), ProviderError> { let handle = tokio::runtime::Handle::try_current().map_err(|_| { - ProviderError::Database(DatabaseError::Other("no tokio runtime found".into())) + ProviderError::Database(DatabaseError::Other("no tokio runtime found".to_string())) })?; - let code = tokio::task::block_in_place(|| { - handle.block_on(async { - self.provider.get_code_at(address).number(self.state_block_number).await - }) + tokio::task::block_in_place(|| handle.block_on(self.fetch_missing_account_proof(address))) + .map_err(|err| ProviderError::Database(DatabaseError::Other(err.to_string()))) + } + + fn fetch_missing_storage_proof_blocking( + &self, + address: Address, + index: StorageKey, + ) -> Result<(), ProviderError> { + let handle = tokio::runtime::Handle::try_current().map_err(|_| { + ProviderError::Database(DatabaseError::Other("no tokio runtime found".to_string())) + })?; + tokio::task::block_in_place(|| { + handle.block_on(self.fetch_missing_storage_proof(address, index)) }) - .map_err(|err| ProviderError::Database(DatabaseError::Other(err.to_string())))?; - let actual_hash = keccak256(&code); - if actual_hash != expected_hash { - return Err(ProviderError::TrieWitnessError(format!( - "Fetched code hash mismatch for {address}: expected {expected_hash}, got {actual_hash}" - ))) + .map_err(|err| ProviderError::Database(DatabaseError::Other(err.to_string()))) + } + + async fn fetch_account_code(&self, address: Address) -> Result { + let code = self + .provider + .get_code_at(address) + .number(self.state_block_number) + .await + .map_err(|err| RpcDbError::GetCodeError(address, err.to_string()))?; + + Ok(Bytecode::new_raw(code)) + } + + fn fetch_account_code_blocking( + &self, + address: Address, + expected_hash: B256, + ) -> Result { + let handle = tokio::runtime::Handle::try_current().map_err(|_| { + ProviderError::Database(DatabaseError::Other("no tokio runtime found".to_string())) + })?; + let bytecode = + tokio::task::block_in_place(|| handle.block_on(self.fetch_account_code(address))) + .map_err(|err| ProviderError::Database(DatabaseError::Other(err.to_string())))?; + + cache_verified_code(&self.codes, address, expected_hash, bytecode) + } + + fn complete_account_code( + &self, + address: Address, + account: Option, + ) -> Result, ProviderError> { + let Some(mut account) = account else { return Ok(None) }; + + if account.code.is_none() && should_fetch_code(account.code_hash) { + if let Some(code) = cached_code(&self.codes, account.code_hash)? { + account.code = Some(code); + } else { + tracing::warn!( + "Code {} not found in execution witness for address {}; fetching fallback code", + account.code_hash, + address + ); + account.code = Some(self.fetch_account_code_blocking(address, account.code_hash)?); + } } - let bytecode = Bytecode::new_raw(code); - self.codes + + Ok(Some(account)) + } + + fn ensure_empty_storage_trie(&self, hashed_address: B256) -> Result<(), ProviderError> { + self.state .write() - .map_err(|_| ProviderError::TrieWitnessError("bytecode cache lock poisoned".into()))? - .insert(expected_hash, bytecode.clone()); - Ok(Some(bytecode)) + .map_err(|_| poisoned_provider_error())? + .storage_tries + .entry(hashed_address) + .or_default(); + Ok(()) } } @@ -175,6 +281,155 @@ impl FlexibleExecutionWitnessHeader { const SYSTEM_ADDRESS: Address = alloy_primitives::address!("0xfffffffffffffffffffffffffffffffffffffffe"); +#[derive(Debug)] +enum AccountReadError { + Trie(mpt::Error), + Provider(ProviderError), +} + +impl AccountReadError { + fn unresolved_node(&self) -> Option { + match self { + Self::Trie(mpt::Error::NodeNotResolved(digest)) => Some(*digest), + Self::Trie(_) | Self::Provider(_) => None, + } + } + + fn into_provider_error(self) -> ProviderError { + match self { + Self::Trie(err) => ProviderError::TrieWitnessError(err.to_string()), + Self::Provider(err) => err, + } + } +} + +fn read_account_info_from_state( + state: &EthereumState, + address_hash: B256, + codes: &HashMap, +) -> Result, AccountReadError> { + if let Some(mut bytes) = + state.state_trie.get(address_hash.as_ref()).map_err(AccountReadError::Trie)? + { + let account = TrieAccount::decode(&mut bytes) + .map_err(ProviderError::from) + .map_err(AccountReadError::Provider)?; + let account_info = + account_info_from_trie_account(codes, account).map_err(AccountReadError::Provider)?; + + Ok(Some(account_info)) + } else { + Ok(None) + } +} + +fn account_info_from_trie_account( + codes: &HashMap, + account: TrieAccount, +) -> Result { + Ok(AccountInfo { + balance: account.balance, + nonce: account.nonce, + code_hash: account.code_hash, + code: codes.get(&account.code_hash).cloned(), + }) +} + +fn cached_code( + codes: &RwLock>, + code_hash: B256, +) -> Result, ProviderError> { + Ok(codes.read().map_err(|_| poisoned_provider_error())?.get(&code_hash).cloned()) +} + +fn cache_verified_code( + codes: &RwLock>, + address: Address, + expected_hash: B256, + bytecode: Bytecode, +) -> Result { + let actual_hash = bytecode.hash_slow(); + if actual_hash != expected_hash { + return Err(ProviderError::TrieWitnessError(format!( + "Fetched code hash mismatch for {address}: expected {expected_hash}, got {actual_hash}" + ))) + } + codes.write().map_err(|_| poisoned_provider_error())?.insert(expected_hash, bytecode.clone()); + + Ok(bytecode) +} + +fn should_fetch_code(code_hash: B256) -> bool { + code_hash != KECCAK_EMPTY && code_hash != B256::ZERO +} + +#[derive(Debug)] +enum StorageRead { + Value(StorageValue), + MissingEmptyTrie, +} + +#[derive(Debug)] +enum StorageReadError { + Trie(mpt::Error), + Provider(ProviderError), + MissingTrie { storage_root: B256 }, +} + +impl StorageReadError { + fn unresolved_node(&self) -> Option { + match self { + Self::Trie(mpt::Error::NodeNotResolved(digest)) => Some(*digest), + Self::Trie(_) | Self::Provider(_) | Self::MissingTrie { .. } => None, + } + } + + fn into_provider_error(self) -> ProviderError { + match self { + Self::Trie(err) => ProviderError::TrieWitnessError(err.to_string()), + Self::Provider(err) => err, + Self::MissingTrie { storage_root } => ProviderError::TrieWitnessError(format!( + "storage trie not found for root {storage_root}" + )), + } + } +} + +fn read_storage_from_state( + state: &EthereumState, + hashed_address: B256, + hashed_slot: B256, +) -> Result { + if let Some(storage_trie) = state.storage_tries.get(&hashed_address) { + if let Some(mut value) = + storage_trie.get(hashed_slot.as_slice()).map_err(StorageReadError::Trie)? + { + return U256::decode(&mut value) + .map(StorageRead::Value) + .map_err(ProviderError::from) + .map_err(StorageReadError::Provider) + } + + return Ok(StorageRead::Value(U256::ZERO)) + } + + let storage_root = state + .state_trie + .get_rlp::(hashed_address.as_slice()) + .map_err(StorageReadError::Trie)? + .map_or(EMPTY_ROOT_HASH, |account| account.storage_root); + + if storage_root == EMPTY_ROOT_HASH || storage_root == B256::ZERO { + Ok(StorageRead::MissingEmptyTrie) + } else { + Err(StorageReadError::MissingTrie { storage_root }) + } +} + +fn poisoned_provider_error() -> ProviderError { + ProviderError::Database(DatabaseError::Other("poisoned lock".to_string())) +} + impl + Clone, N: Network> DatabaseRef for ExecutionWitnessRpcDb { type Error = ProviderError; @@ -184,30 +439,41 @@ impl + Clone, N: Network> DatabaseRef for ExecutionWitnessRpcDb

self.complete_account_code(address, account), + Err(err) => { + let Some(digest) = err.unresolved_node() else { + return Err(err.into_provider_error()) + }; + + tracing::warn!( + "Account trie reached unresolved node {} for address {}; fetching fallback proof", + digest, + address + ); + self.fetch_missing_account_proof_blocking(address)?; + + let account = { + let state = self.state.read().map_err(|_| poisoned_provider_error())?; + let codes = self.codes.read().map_err(|_| poisoned_provider_error())?; + read_account_info_from_state(&state, hash, &codes) + .map_err(AccountReadError::into_provider_error)? + }; + self.complete_account_code(address, account) + } } } fn code_by_hash_ref(&self, code_hash: B256) -> Result { self.codes .read() - .map_err(|_| ProviderError::TrieWitnessError("bytecode cache lock poisoned".into()))? + .map_err(|_| poisoned_provider_error())? .get(&code_hash) .ok_or_else(|| { ProviderError::TrieWitnessError(format!("Code not found for {code_hash}")) @@ -223,15 +489,51 @@ impl + Clone, N: Network> DatabaseRef for ExecutionWitnessRpcDb

Ok(value), + Ok(StorageRead::MissingEmptyTrie) => { + self.ensure_empty_storage_trie(hashed_address)?; + Ok(U256::ZERO) + } + Err(err) => { + if let Some(digest) = err.unresolved_node() { + tracing::warn!( + "Storage trie reached unresolved node {} for address {} slot {}; fetching fallback proof", + digest, + address, + slot + ); + } else if let StorageReadError::MissingTrie { storage_root } = err { + tracing::warn!( + "Storage trie with root {} is missing for address {} slot {}; fetching fallback proof", + storage_root, + address, + slot + ); + } else { + return Err(err.into_provider_error()) + } + + self.fetch_missing_storage_proof_blocking(address, index)?; + + let state = self.state.read().map_err(|_| poisoned_provider_error())?; + match read_storage_from_state(&state, hashed_address, hashed_slot) + .map_err(StorageReadError::into_provider_error)? + { + StorageRead::Value(value) => Ok(value), + StorageRead::MissingEmptyTrie => { + drop(state); + self.ensure_empty_storage_trie(hashed_address)?; + Ok(U256::ZERO) + } + } + } } } @@ -251,11 +553,11 @@ where N: Network, { async fn state(&self, _bundle_state: &BundleState) -> Result { - Ok(self.state.clone()) + Ok(self.state.read().map_err(|_| RpcDbError::Poisoned)?.clone()) } fn bytecodes(&self) -> Vec { - self.codes.read().expect("bytecode cache lock poisoned").values().cloned().collect() + self.codes.read().expect("codes lock poisoned").values().cloned().collect() } async fn ancestor_headers(&self) -> Result, RpcDbError> { @@ -265,148 +567,12 @@ where #[cfg(test)] mod tests { - use std::{ - io::{Read, Write}, - net::TcpListener, - thread, - time::{Duration, Instant}, - }; - use alloy_consensus::Header; use alloy_primitives::hex::FromHex; use alloy_rlp::Decodable; use super::*; - fn code_rpc(code: &str) -> (String, thread::JoinHandle) { - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let url = format!("http://{}", listener.local_addr().unwrap()); - listener.set_nonblocking(true).unwrap(); - let code = code.to_string(); - let worker = thread::spawn(move || { - let deadline = Instant::now() + Duration::from_secs(5); - let mut stream = loop { - match listener.accept() { - Ok((stream, _)) => break stream, - Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { - assert!(Instant::now() < deadline, "no bytecode RPC request received"); - thread::sleep(Duration::from_millis(5)); - } - Err(err) => panic!("{err}"), - } - }; - stream.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); - let mut bytes = Vec::new(); - let request = loop { - let mut buffer = [0u8; 1024]; - let count = stream.read(&mut buffer).unwrap(); - assert!(count > 0); - bytes.extend_from_slice(&buffer[..count]); - if let Some(end) = bytes.windows(4).position(|w| w == b"\r\n\r\n") { - let headers = String::from_utf8_lossy(&bytes[..end]); - let length: usize = headers - .lines() - .find_map(|line| { - line.to_ascii_lowercase() - .strip_prefix("content-length:") - .map(|value| value.trim().parse().unwrap()) - }) - .unwrap(); - if bytes.len() >= end + 4 + length { - break serde_json::from_slice::( - &bytes[end + 4..end + 4 + length], - ) - .unwrap() - } - } - }; - let response = - serde_json::json!({"jsonrpc": "2.0", "id": request["id"], "result": code}) - .to_string(); - write!(stream, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", response.len(), response).unwrap(); - request - }); - (url, worker) - } - - #[tokio::test(flavor = "multi_thread")] - async fn missing_code_uses_parent_block_and_is_cached_for_guest_input() { - let (url, worker) = code_rpc("0x6000"); - let code = Bytecode::new_raw(alloy_primitives::bytes!("6000")); - let hash = code.hash_slow(); - let address = Address::repeat_byte(0x11); - let mut db = ExecutionWitnessRpcDb { - provider: alloy_provider::ProviderBuilder::new().connect_http(url.parse().unwrap()), - state_block_number: 26_167_641, - state: EthereumState { - state_trie: Default::default(), - storage_tries: HashMap::default(), - }, - codes: RwLock::new(HashMap::default()), - ancestor_headers: BTreeMap::new(), - phantom: PhantomData, - }; - db.state - .state_trie - .insert_rlp( - keccak256(address).as_slice(), - TrieAccount { - nonce: 1, - balance: U256::from(1), - code_hash: hash, - ..Default::default() - }, - ) - .unwrap(); - - assert_eq!(db.basic_ref(address).unwrap().unwrap().code, Some(code.clone())); - let request = worker.join().unwrap(); - assert_eq!(request["method"], "eth_getCode"); - assert_eq!(request["params"], serde_json::json!([address, "0x18f4959"])); - // The RPC listener is gone: both paths must use the verified cache. - assert_eq!(db.basic_ref(address).unwrap().unwrap().code, Some(code.clone())); - assert_eq!(db.code_by_hash_ref(hash).unwrap(), code.clone()); - assert_eq!(db.bytecodes(), vec![code]); - assert!(db.basic_ref(Address::repeat_byte(0x22)).unwrap().is_none()); - assert!(db.account_code(address, KECCAK_EMPTY).unwrap().is_none()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn wrong_fallback_code_is_rejected_and_not_exported() { - let (url, worker) = code_rpc("0x6001"); - let expected_hash = keccak256(alloy_primitives::bytes!("6000")); - let address = Address::repeat_byte(0x11); - let mut db = ExecutionWitnessRpcDb { - provider: alloy_provider::ProviderBuilder::new().connect_http(url.parse().unwrap()), - state_block_number: 26_167_641, - state: EthereumState { - state_trie: Default::default(), - storage_tries: HashMap::default(), - }, - codes: RwLock::new(HashMap::default()), - ancestor_headers: BTreeMap::new(), - phantom: PhantomData, - }; - db.state - .state_trie - .insert_rlp( - keccak256(address).as_slice(), - TrieAccount { - nonce: 1, - balance: U256::from(1), - code_hash: expected_hash, - ..Default::default() - }, - ) - .unwrap(); - - let error = db.basic_ref(address).unwrap_err(); - worker.join().unwrap(); - assert!(error.to_string().contains("Fetched code hash mismatch")); - assert!(db.bytecodes().is_empty()); - assert!(db.code_by_hash_ref(expected_hash).is_err()); - } - #[test] fn execution_witness_accepts_rpc_header_objects_and_null_keys() { let json = r#"{