Skip to content

Commit 588faf9

Browse files
bertmillerdvush
andauthored
feat(eth-sparse-mpt): add ~3x faster v3 state-root implementation and harness tools (#887)
## 📝 Summary I will state upfront that this code is almost entirely written with Codex. However, the results seem promising and it shares much overlap with the existing v2 state root anyway. I asked Codex to setup a harness that measured performance across different depths of cache warmth and compared state root implementations to each other and Reth's for correctness. Then I asked it to optimize v2 and implement a v3 state root. This was effective because the agent could simply iterate if it noticed it had the wrong root or if it was slower. Finally, I ran the harness for 1000+ blocks and the new v3 always produced the same root as v2 and reth, so I have some confidence in the implementation. You can find the testing scripts in this PR, specifically `correct_and_bench.rs`. Note that these require unwinding a reth node block by block (thank you Vitaly for this trick). The results are a significant speedup relative to v2. Over 1,000 blocks: | Scope | v2 p50 | v3 p50 | v2 p99 | v3 p99 | v2 mean | v3 mean | |---|---:|---:|---:|---:|---:|---:| | Overall (excluding warm=100, n=3213) | 3.01 | 0.88 | 4.71 | 1.87 | 3.19 | 0.97 | | Warm % | v2 p50 | v3 p50 | p50 speedup | v2 p99 | v3 p99 | p99 speedup | v2 mean | v3 mean | mean speedup | |---|---:|---:|---:|---:|---:|---:|---:|---:|---:| | 70 | 4.16 | 1.09 | 3.82x | 5.96 | 2.22 | 2.68x | 4.32 | 1.21 | 3.58x | | 80 | 3.05 | 0.94 | 3.26x | 4.75 | 1.93 | 2.46x | 3.23 | 1.03 | 3.14x | | 90 | 1.83 | 0.61 | 3.00x | 3.41 | 1.45 | 2.35x | 2.00 | 0.68 | 2.95x | Here's a moving average: <img width="2160" height="1620" alt="image" src="https://github.com/user-attachments/assets/2e8778d0-63c3-4d72-bb17-f0a442c7af6b" /> The one notable regression is that in ~2.5% of blocks there were v3 p99 cases that were greater than v2. On average these regressions added 1.89ms, and the worst observed was 23.9ms for a single block. ## 💡 Motivation and Context Calculating the state root is in the hot path for a new block so making it faster lowers latency for all our users. --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --------- Co-authored-by: Vitalii Drohan <vitaliy.drogan@gmail.com>
1 parent 4c12965 commit 588faf9

17 files changed

Lines changed: 5818 additions & 13 deletions

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6
9090
reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }
9191
reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }
9292
reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }
93+
reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }
9394
reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }
9495
reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }
9596
reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "27a8c0f5a6dfb27dea84c5751776ecabdd069646" }

crates/eth-sparse-mpt/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ nybbles = { version = "0.3.3", features = ["serde"] }
2222
tracing.workspace = true
2323

2424
# reth
25+
reth-chainspec = { workspace = true, optional = true }
26+
reth-db = { workspace = true, optional = true }
27+
reth-evm = { workspace = true, optional = true }
28+
reth-evm-ethereum = { workspace = true, optional = true }
2529
reth-errors.workspace = true
2630
reth-execution-errors.workspace = true
31+
reth-node-api = { workspace = true, optional = true }
32+
reth-node-ethereum = { workspace = true, optional = true }
2733
reth-trie.workspace = true
34+
reth-trie-parallel = { workspace = true, optional = true }
2835
reth-trie-db.workspace = true
2936
reth-provider.workspace = true
37+
reth-revm = { workspace = true, optional = true }
3038

3139
# revm
3240
revm.workspace = true
@@ -48,6 +56,16 @@ flate2 = { workspace = true, optional = true }
4856

4957
[features]
5058
benchmark-utils = ["dep:hash-db", "dep:triehash", "dep:flate2"]
59+
dev-tools = [
60+
"dep:reth-chainspec",
61+
"dep:reth-db",
62+
"dep:reth-evm",
63+
"dep:reth-evm-ethereum",
64+
"dep:reth-node-api",
65+
"dep:reth-node-ethereum",
66+
"dep:reth-trie-parallel",
67+
"dep:reth-revm",
68+
]
5169

5270
[dev-dependencies]
5371
criterion = { workspace = true, features = ["html_reports"] }
@@ -71,3 +89,21 @@ harness = false
7189
name = "trie_do_bench"
7290
harness = false
7391

92+
[[bench]]
93+
name = "trie_bench_v2_v_experimental"
94+
harness = false
95+
96+
[[bin]]
97+
name = "correctness-harness"
98+
path = "src/bin/correctness-harness.rs"
99+
required-features = ["dev-tools"]
100+
101+
[[bin]]
102+
name = "cache-warm-compare"
103+
path = "src/bin/cache-warm-compare.rs"
104+
required-features = ["dev-tools"]
105+
106+
[[bin]]
107+
name = "correct_and_bench"
108+
path = "src/bin/correct_and_bench.rs"
109+
required-features = ["dev-tools"]
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use alloy_primitives::{keccak256, Bytes, B256, U256};
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use eth_sparse_mpt::{
4+
v2::trie::{proof_store::ProofStore as ProofStoreV2, Trie as TrieV2},
5+
v_experimental::trie::{
6+
proof_store::ProofStore as ProofStoreVExperimental, Trie as TrieVExperimental,
7+
},
8+
};
9+
10+
fn prepare_key_value_data(n: usize) -> (Vec<Bytes>, Vec<Bytes>) {
11+
let mut keys = Vec::with_capacity(n);
12+
let mut values = Vec::with_capacity(n);
13+
for i in 0u64..(n as u64) {
14+
let b: B256 = U256::from(i).into();
15+
let data = keccak256(b).to_vec();
16+
let value = keccak256(&data).to_vec();
17+
keys.push(Bytes::copy_from_slice(data.as_slice()));
18+
values.push(Bytes::copy_from_slice(value.as_slice()));
19+
}
20+
(keys, values)
21+
}
22+
23+
fn insert_nodes_v2_v_experimental(c: &mut Criterion) {
24+
let (keys, values) = prepare_key_value_data(10000);
25+
26+
let empty_proof_store_v2 = ProofStoreV2::default();
27+
let mut v2_hash = B256::ZERO;
28+
let mut trie_v2 = TrieV2::new_empty();
29+
c.bench_function("insert_nodes_v2", |b| {
30+
b.iter(|| {
31+
trie_v2.clear_empty();
32+
for (key, value) in keys.iter().zip(values.iter()) {
33+
trie_v2.insert(key, value).unwrap();
34+
}
35+
v2_hash = trie_v2.root_hash(true, &empty_proof_store_v2).unwrap();
36+
})
37+
});
38+
39+
let empty_proof_store_v_experimental = ProofStoreVExperimental::default();
40+
let mut v_experimental_hash = B256::ZERO;
41+
let mut trie_v_experimental = TrieVExperimental::new_empty();
42+
c.bench_function("insert_nodes_v_experimental", |b| {
43+
b.iter(|| {
44+
trie_v_experimental.clear_empty();
45+
for (key, value) in keys.iter().zip(values.iter()) {
46+
trie_v_experimental.insert(key, value).unwrap();
47+
}
48+
v_experimental_hash = trie_v_experimental
49+
.root_hash(true, &empty_proof_store_v_experimental)
50+
.unwrap();
51+
})
52+
});
53+
54+
if !v2_hash.is_zero() && !v_experimental_hash.is_zero() {
55+
assert_eq!(v2_hash, v_experimental_hash);
56+
}
57+
}
58+
59+
criterion_group!(benches, insert_nodes_v2_v_experimental);
60+
criterion_main!(benches);

0 commit comments

Comments
 (0)