Skip to content

Commit 7a01415

Browse files
Wollacnategraf
authored andcommitted
WEB3-293: feat: Add risc0-ethereum-trie (#391)
This PR introduces the new `risc0-ethereum-trie` crate consolidating all functionality related to sparse Merkle-Patricia Tries (MPT). Merge #390 first --------- Co-authored-by: Victor Snyder-Graf <[email protected]>
1 parent 03d446a commit 7a01415

File tree

15 files changed

+2625
-9
lines changed

15 files changed

+2625
-9
lines changed

Cargo.toml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ risc0-aggregation = { version = "0.3.0", default-features = false, path = "crate
1515
risc0-build-ethereum = { version = "1.4.0", default-features = false, path = "build" }
1616
risc0-ethereum-contracts = { version = "1.4.0", default-features = false, path = "contracts" }
1717
risc0-forge-ffi = { version = "1.4.0", default-features = false, path = "crates/ffi" }
18-
risc0-steel = { version = "2.0.0", default-features = false, path = "crates/steel" }
1918
risc0-op-steel = { version = "0.2.0", default-features = false, path = "crates/op-steel" }
19+
risc0-steel = { version = "2.0.0", default-features = false, path = "crates/steel" }
2020

2121
# risc0 monorepo dependencies.
2222
risc0-build = { git = "https://github.com/risc0/risc0", branch = "release-1.3", default-features = false }
@@ -28,9 +28,9 @@ risc0-binfmt = { git = "https://github.com/risc0/risc0", branch = "release-1.3",
2828
alloy-consensus = { version = "0.11" }
2929
alloy-eips = { version = "0.11" }
3030
alloy-rlp = { version = "0.3.8" }
31-
alloy-primitives = { version = "0.8.16" }
31+
alloy-primitives = { version = "0.8.18" }
3232
alloy-rpc-types = { version = "0.11" }
33-
alloy-sol-types = { version = "0.8.16" }
33+
alloy-sol-types = { version = "0.8.18" }
3434

3535
# OP Steel
3636
op-alloy-network = { version = "0.10.0" }
@@ -42,23 +42,27 @@ alloy-trie = { version = "0.7.7" }
4242
# Beacon chain support
4343
ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus.git", rev = "cf3c404043230559660810bc0c9d6d5a8498d819" }
4444

45-
hex = "0.4"
4645
anyhow = { version = "1.0" }
46+
arrayvec = "0.7"
4747
bincode = { version = "1.3" }
48+
bytemuck = "1.21"
49+
cfg-if = "1.0"
4850
clap = { version = "4.5", features = ["derive", "env"] }
51+
criterion = "0.5"
52+
hex = "0.4"
53+
itertools = "0.14"
4954
log = "0.4"
50-
revm = { version = "19.2", default-features = false, features = ["std"] }
55+
rand = "0.9"
5156
reqwest = "0.12"
57+
revm = { version = "19.2", default-features = false, features = ["std"] }
58+
rkyv = "0.8"
5259
serde = "1.0"
5360
serde_json = "1.0"
5461
sha2 = { version = "0.10" }
5562
stability = "0.2"
5663
test-log = "0.2.15"
57-
thiserror = { version = "2.0", default-features = false }
64+
thiserror = { version = "2.0" }
5865
tokio = { version = "1.35" }
5966
tracing = "0.1"
6067
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
6168
url = { version = "2.5" }
62-
rand = "0.9"
63-
cfg-if = "1.0"
64-
bytemuck = "1.21"

crates/trie/Cargo.toml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[package]
2+
name = "risc0-ethereum-trie"
3+
version = "0.1.0"
4+
edition = { workspace = true }
5+
license = { workspace = true }
6+
homepage = { workspace = true }
7+
repository = { workspace = true }
8+
9+
[package.metadata.docs.rs]
10+
all-features = true
11+
rustdoc-args = ["--cfg", "docsrs"]
12+
13+
[lints.clippy]
14+
dbg-macro = "warn"
15+
manual-string-new = "warn"
16+
uninlined-format-args = "warn"
17+
redundant-clone = "warn"
18+
missing-const-for-fn = "warn"
19+
20+
[lints.rust]
21+
missing-copy-implementations = "warn"
22+
missing-debug-implementations = "warn"
23+
missing-docs = "warn"
24+
rust-2018-idioms = "warn"
25+
unreachable-pub = "warn"
26+
unused-must-use = "warn"
27+
redundant-lifetimes = "warn"
28+
unnameable-types = "warn"
29+
30+
[lints.rustdoc]
31+
all = "warn"
32+
33+
[profile.bench]
34+
codegen-units = 1
35+
lto = "fat"
36+
37+
[dependencies]
38+
alloy-primitives = { workspace = true, features = ["map"] }
39+
alloy-rlp = { workspace = true, features = ["arrayvec"] }
40+
alloy-trie = { workspace = true }
41+
arrayvec = { workspace = true }
42+
bincode = { workspace = true, optional = true }
43+
itertools = { workspace = true }
44+
rkyv = { workspace = true, optional = true }
45+
serde = { workspace = true, optional = true }
46+
thiserror = { workspace = true }
47+
48+
[dev-dependencies]
49+
alloy-trie = { workspace = true, features = ["ethereum"] }
50+
criterion = { workspace = true }
51+
serde_json = { workspace = true }
52+
53+
[[bench]]
54+
name = "mpt"
55+
harness = false
56+
57+
[features]
58+
default = []
59+
rkyv = ["dep:rkyv"]
60+
serde = ["dep:serde", "dep:bincode", "alloy-primitives/serde", "alloy-trie/serde"]
61+
rlp_serialize = []
62+
orphan = []

crates/trie/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# risc0-ethereum-trie
2+
3+
Fast Merkle-Patricia Trie (MPT) implementation.

crates/trie/benches/mpt.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 RISC Zero, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#![allow(missing_docs)]
16+
17+
use alloy_primitives::{bytes, keccak256, Bytes, B256};
18+
use alloy_trie::{HashBuilder, Nibbles};
19+
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
20+
use risc0_ethereum_trie::{CachedTrie, Trie};
21+
use std::collections::BTreeMap;
22+
23+
const SIZE: usize = 1024;
24+
25+
pub fn trie(c: &mut Criterion) {
26+
let mut g = c.benchmark_group("trie");
27+
28+
g.bench_with_input(BenchmarkId::new("hash_slow", SIZE), &SIZE, |b, &s| {
29+
let trie = create_trie(&create_leaves(s));
30+
b.iter(|| {
31+
_ = black_box(trie.hash_slow());
32+
})
33+
});
34+
g.bench_with_input(BenchmarkId::new("get", SIZE), &SIZE, |b, &s| {
35+
let leaves = create_leaves(s);
36+
let keys: Vec<B256> = leaves.keys().step_by(2).cloned().collect();
37+
let trie = create_trie(&leaves);
38+
b.iter(|| {
39+
keys.iter().for_each(|k| {
40+
_ = black_box(trie.get(k));
41+
})
42+
})
43+
});
44+
g.bench_with_input(BenchmarkId::new("insert", SIZE), &SIZE, |b, &s| {
45+
let leaves = create_leaves(s);
46+
b.iter(|| {
47+
let mut mpt = Trie::default();
48+
leaves.iter().for_each(|(k, v)| mpt.insert(k, v.clone()));
49+
})
50+
});
51+
g.bench_with_input(BenchmarkId::new("remove", SIZE), &SIZE, |b, &s| {
52+
let leaves = create_leaves(s);
53+
let keys: Vec<B256> = leaves.keys().cloned().collect();
54+
let mpt = Trie::from_iter(leaves);
55+
b.iter(|| {
56+
let mut mpt = mpt.clone();
57+
keys.iter().for_each(|k| {
58+
_ = black_box(mpt.remove(k));
59+
})
60+
})
61+
});
62+
}
63+
64+
pub fn cached_trie(c: &mut Criterion) {
65+
let mut g = c.benchmark_group("cached_trie");
66+
67+
g.bench_with_input(BenchmarkId::new("hash_slow", SIZE), &SIZE, |b, &s| {
68+
let trie = create_trie(&create_leaves(s)).into_cached();
69+
b.iter(|| {
70+
_ = black_box(trie.hash_slow());
71+
})
72+
});
73+
g.bench_with_input(BenchmarkId::new("get", SIZE), &SIZE, |b, &s| {
74+
let leaves = create_leaves(s);
75+
let keys: Vec<B256> = leaves.keys().step_by(2).cloned().collect();
76+
let trie = create_trie(&leaves).into_cached();
77+
b.iter(|| {
78+
keys.iter().for_each(|k| {
79+
_ = black_box(trie.get(k));
80+
})
81+
})
82+
});
83+
g.bench_with_input(BenchmarkId::new("insert", SIZE), &SIZE, |b, &s| {
84+
let leaves = create_leaves(s);
85+
b.iter(|| {
86+
let mut mpt = CachedTrie::default();
87+
leaves.iter().for_each(|(k, v)| mpt.insert(k, v.clone()));
88+
})
89+
});
90+
g.bench_with_input(BenchmarkId::new("remove", SIZE), &SIZE, |b, &s| {
91+
let leaves = create_leaves(s);
92+
let keys: Vec<B256> = leaves.keys().cloned().collect();
93+
let mpt = CachedTrie::from_iter(leaves);
94+
b.iter(|| {
95+
let mut mpt = mpt.clone();
96+
keys.iter().for_each(|k| {
97+
_ = black_box(mpt.remove(k));
98+
})
99+
})
100+
});
101+
}
102+
103+
fn create_leaves(size: usize) -> BTreeMap<B256, Bytes> {
104+
(0..size).map(|i| (keccak256(i.to_be_bytes()), bytes!("deadbeaf"))).collect()
105+
}
106+
107+
fn create_trie(leaves: &BTreeMap<B256, Bytes>) -> Trie {
108+
let proof_keys = leaves.keys().step_by(2).map(Nibbles::unpack).collect();
109+
let mut hb = HashBuilder::default().with_proof_retainer(proof_keys);
110+
leaves.iter().for_each(|(k, v)| hb.add_leaf(Nibbles::unpack(k), v));
111+
_ = hb.root();
112+
113+
let proofs = hb.take_proof_nodes().into_nodes_sorted().into_iter().map(|node| node.1);
114+
Trie::from_rlp(proofs).unwrap()
115+
}
116+
117+
criterion_group!(benches, trie, cached_trie);
118+
criterion_main!(benches);

crates/trie/rustfmt.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
reorder_imports = true
2+
use_field_init_shorthand = true
3+
use_small_heuristics = "Max"

crates/trie/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 RISC Zero, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#![doc = include_str!("../README.md")]
16+
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
17+
18+
mod mpt;
19+
20+
#[cfg(feature = "orphan")]
21+
pub use mpt::orphan;
22+
pub use mpt::{CachedTrie, Trie, EMPTY_ROOT_HASH};
23+
24+
pub use alloy_trie::Nibbles;

0 commit comments

Comments
 (0)