From 8f0ab371d86f622543cc1b68e737f36cb0fe1482 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 4 Aug 2025 10:40:35 +0200 Subject: [PATCH 1/4] precompute preimages --- Cargo.toml | 4 +++ crates/rpc-proxy/Cargo.toml | 4 +++ crates/rpc-proxy/build.rs | 60 ++++++++++++++++++++++++++++++++++++ crates/rpc-proxy/src/trie.rs | 60 +++++++++++++++++++++++++++++++++--- 4 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 crates/rpc-proxy/build.rs diff --git a/Cargo.toml b/Cargo.toml index 05509b96..53b5140c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,9 @@ version = "0.1.0" edition = "2024" rust-version = "1.86" +[profile.dev.build-override] +opt-level = 3 + [workspace.dependencies] # internal crates guests = { path = "guests" } @@ -58,6 +61,7 @@ itertools = "0.14" serde = "1.0" serde_json = "1.0" thiserror = "2" +tiny-keccak = "2.0.2" tokio = { version = "1.46", features = ['full'] } tracing = "0.1" tracing-subscriber = "0.3" diff --git a/crates/rpc-proxy/Cargo.toml b/crates/rpc-proxy/Cargo.toml index 8146a4d8..a8215435 100644 --- a/crates/rpc-proxy/Cargo.toml +++ b/crates/rpc-proxy/Cargo.toml @@ -4,6 +4,10 @@ version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } +[build-dependencies] +alloy-primitives = { workspace = true } +alloy-trie = { workspace = true } + [dependencies] actix-web = "4" alloy = { workspace = true } diff --git a/crates/rpc-proxy/build.rs b/crates/rpc-proxy/build.rs new file mode 100644 index 00000000..b3417d03 --- /dev/null +++ b/crates/rpc-proxy/build.rs @@ -0,0 +1,60 @@ +use alloy_primitives::{B256, U256, keccak256}; +use alloy_trie::Nibbles; +use std::{ + env, + fs::File, + io::Write, + path::Path, + sync::{ + Arc, OnceLock, + atomic::{AtomicUsize, Ordering}, + }, + thread, +}; + +/// The number of nibbles to use for the prefix. +const NIBBLES: usize = 5; +/// The total number of unique prefixes for the given number of nibbles (16^N). +const PREFIX_COUNT: usize = 16usize.pow(NIBBLES as u32); + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("preimages.bin"); + println!("cargo:rerun-if-changed=build.rs"); + + let table: Arc>> = + Arc::new((0..PREFIX_COUNT).map(|_| OnceLock::new()).collect()); + let found = Arc::new(AtomicUsize::new(0)); + + thread::scope(|s| { + let threads = thread::available_parallelism().unwrap().get(); + for tid in 0..threads { + let table = table.clone(); + let found = found.clone(); + + s.spawn(move || { + let mut nonce = tid as u64; + while found.load(Ordering::Relaxed) < PREFIX_COUNT { + let hash = keccak256(B256::from(U256::from(nonce))); + + let nibbles = Nibbles::unpack(hash); + // Calculate the little-endian index from the first N nibbles of the hash. + let idx = (0..NIBBLES) + .map(|i| nibbles.get(i).unwrap() as usize) + .rfold(0, |a, n| (a << 4) | n); + + if table[idx].set(nonce).is_ok() { + found.fetch_add(1, Ordering::Relaxed); + } + nonce += threads as u64; + } + }); + } + }); + + let mut file = File::create(&dest_path).expect("Could not create file"); + for cell in table.iter() { + let nonce_bytes = cell.get().unwrap().to_le_bytes(); + file.write_all(&nonce_bytes).expect("Failed to write to file"); + } +} diff --git a/crates/rpc-proxy/src/trie.rs b/crates/rpc-proxy/src/trie.rs index 18771e74..032f4437 100644 --- a/crates/rpc-proxy/src/trie.rs +++ b/crates/rpc-proxy/src/trie.rs @@ -18,11 +18,16 @@ use alloy::{ primitives::{Address, B256, keccak256, map::B256Set}, providers::Provider, }; +use alloy_primitives::U256; use anyhow::{Context, Result, bail}; -use revm::database::StorageWithOriginalValues; +use revm::{ + bytecode::bitvec::macros::internal::funty::Integral, database::StorageWithOriginalValues, +}; use risc0_ethereum_trie::{Nibbles, Trie, orphan}; use std::collections::HashSet; -use tracing::{debug, trace}; +use tracing::{debug, trace, warn}; + +static LOOKUP: PreimageLookup = PreimageLookup::new(); pub(crate) async fn handle_removed_account( provider: &P, @@ -117,11 +122,15 @@ where return Ok(()); } - debug!(%address, "Using debug_storageRangeAt to find preimages for orphan nodes"); - let mut missing_storage_keys = B256Set::default(); for prefix in unresolvable { - let storage_key = provider.get_next_storage_key(block_hash, address, prefix).await?; + let storage_key = match LOOKUP.find(&prefix) { + Some(preimage) => B256::from(preimage), + None => { + debug!(%address, ?prefix, "Using debug_storageRangeAt to find preimage"); + provider.get_next_storage_key(block_hash, address, prefix).await? + } + }; missing_storage_keys.insert(storage_key); } @@ -138,3 +147,44 @@ where Ok(()) } + +/// A zero-cost wrapper for a precomputed table of Keccak256 pre-images. +/// +/// This struct holds a static reference to the binary data generated by the `build.rs` +/// script. It provides a fast method to find a `U256` pre-image for a given hash prefix. +pub struct PreimageLookup(&'static [u8]); + +impl PreimageLookup { + /// Creates a new lookup table instance at compile time. + pub const fn new() -> Self { + Self(include_bytes!(concat!(env!("OUT_DIR"), "/preimages.bin"))) + } + + /// Returns the number of nibbles the table was precomputed for. + pub const fn precomputed_nibbles(&self) -> usize { + // The table size in bytes is `16^N * 8`. + self.0.len().ilog2().saturating_sub(3) as usize / 4 + } + + /// Finds a pre-image for a given nibble prefix. + /// + /// This function looks up a pre-image from the precomputed table. It correctly handles + /// input prefixes that are shorter than the table's precomputed depth by finding + /// the first corresponding entry. + pub fn find(&self, nibbles: &Nibbles) -> Option { + if nibbles.len() > self.precomputed_nibbles() { + return None; + } + + // Calculate the little-endian index from the input nibbles. + // E.g., for [A, B, C], the index will be 0x...CBA. + let idx = nibbles.to_vec().rfold(0, |a, n| (a << 4) | n); + + // Read the 8-byte nonce from the table at the calculated index. + let start = idx * 8; + let nonce_bytes: [u8; 8] = self.0[start..start + 8].try_into().unwrap(); + let nonce = u64::from_le_bytes(nonce_bytes); + + Some(U256::from(nonce)) + } +} From 3b0a5c915aa3c12e88f01387e1d312aa7efbb0d7 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 4 Aug 2025 12:05:41 +0200 Subject: [PATCH 2/4] fix --- crates/rpc-proxy/src/trie.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/rpc-proxy/src/trie.rs b/crates/rpc-proxy/src/trie.rs index 032f4437..e1ab75cd 100644 --- a/crates/rpc-proxy/src/trie.rs +++ b/crates/rpc-proxy/src/trie.rs @@ -20,12 +20,10 @@ use alloy::{ }; use alloy_primitives::U256; use anyhow::{Context, Result, bail}; -use revm::{ - bytecode::bitvec::macros::internal::funty::Integral, database::StorageWithOriginalValues, -}; +use revm::database::StorageWithOriginalValues; use risc0_ethereum_trie::{Nibbles, Trie, orphan}; use std::collections::HashSet; -use tracing::{debug, trace, warn}; +use tracing::{debug, trace}; static LOOKUP: PreimageLookup = PreimageLookup::new(); @@ -178,7 +176,7 @@ impl PreimageLookup { // Calculate the little-endian index from the input nibbles. // E.g., for [A, B, C], the index will be 0x...CBA. - let idx = nibbles.to_vec().rfold(0, |a, n| (a << 4) | n); + let idx = nibbles.to_vec().iter().rfold(0, |a, n| (a << 4) | *n as usize); // Read the 8-byte nonce from the table at the calculated index. let start = idx * 8; From 8a3e19403ab220aafe2f847efd6f44092e493c99 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 4 Aug 2025 12:17:26 +0200 Subject: [PATCH 3/4] remove tiny-keccak dependency --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 53b5140c..4fe4dcf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ itertools = "0.14" serde = "1.0" serde_json = "1.0" thiserror = "2" -tiny-keccak = "2.0.2" tokio = { version = "1.46", features = ['full'] } tracing = "0.1" tracing-subscriber = "0.3" From 2062d16a2a0f58636533a14c29fe7acdadfe7072 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 4 Aug 2025 15:31:17 +0200 Subject: [PATCH 4/4] add license header --- crates/rpc-proxy/build.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/rpc-proxy/build.rs b/crates/rpc-proxy/build.rs index b3417d03..5a37f8ba 100644 --- a/crates/rpc-proxy/build.rs +++ b/crates/rpc-proxy/build.rs @@ -1,3 +1,17 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use alloy_primitives::{B256, U256, keccak256}; use alloy_trie::Nibbles; use std::{