Sitelet https://github.com/init4tech/trevm/commit/9982b06ef9571be2ae8bf445eb044800f31f621b
Skip to content

Commit 9982b06

Browse files
authored
feat: memoized receipt root on BlockOutput, bump to 0.34.2 (#155)
1 parent b000860 commit 9982b06

3 files changed

Lines changed: 260 additions & 21 deletions

File tree

‎CLAUDE.md‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
# Trevm
1+
# trevm
22

33
## Commands
44

55
- `cargo +nightly fmt` - format
6-
- `cargo clippy --all-features --all-targets` - lint with features
7-
- `cargo clippy --no-default-features --all-targets` - lint without
8-
- `cargo t --all-features` - test with all features
9-
- `cargo t --no-default-features` - test without features
6+
- `cargo clippy -p trevm --all-features --all-targets` - lint with features
7+
- `cargo clippy -p trevm --no-default-features --all-targets` - lint without
8+
- `cargo t -p trevm` - test
109

11-
Pre-commit: clippy (both feature sets) + fmt. Never use `cargo check/build`.
10+
Pre-push: clippy (both feature sets) + fmt. Never use `cargo check/build`.
1211

1312
## Style
1413

@@ -31,3 +30,10 @@ Pre-commit: clippy (both feature sets) + fmt. Never use `cargo check/build`.
3130
- Extensive feature flags: test with both `--all-features` and `--no-default-features`
3231
- Key features: `call`, `concurrent-db`, `estimate_gas`, `tracing-inspectors`, `alloy-db`, `test-utils`
3332
- Uses `#[cfg_attr(docsrs, doc(cfg(...)))]` for feature-gated documentation
33+
34+
## Versioning
35+
36+
Trevm uses semver. While pre-1.0, the MINOR version tracks revm's MAJOR
37+
version (e.g. trevm `0.34.x` targets revm `34.x.x`). Breaking changes go
38+
in PATCH versions to preserve this relationship, documented in GitHub
39+
release notes. Always bump the patch version for breaking changes.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "trevm"
3-
version = "0.34.1"
3+
version = "0.34.2"
44
rust-version = "1.83.0"
55
edition = "2021"
66
authors = ["init4"]

‎src/lifecycle/output.rs‎

Lines changed: 247 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use alloy::{
2-
consensus::{ReceiptEnvelope, TxReceipt},
3-
primitives::{Address, Bloom, Bytes, Log},
2+
consensus::{proofs::ordered_trie_root_with_encoder, ReceiptEnvelope, TxReceipt},
3+
eips::eip2718::Encodable2718,
4+
primitives::{Address, Bloom, Bytes, Log, B256},
45
};
56
use std::sync::OnceLock;
67

@@ -18,6 +19,9 @@ pub struct BlockOutput<T: TxReceipt = ReceiptEnvelope> {
1819

1920
/// The logs bloom of the block.
2021
bloom: OnceLock<Bloom>,
22+
23+
/// The receipt root of the block.
24+
receipt_root: OnceLock<B256>,
2125
}
2226

2327
impl Default for BlockOutput {
@@ -34,21 +38,22 @@ impl<T: TxReceipt<Log = alloy::primitives::Log>> BlockOutput<T> {
3438
receipts: Vec::with_capacity(capacity),
3539
senders: Vec::with_capacity(capacity),
3640
bloom: Default::default(),
41+
receipt_root: Default::default(),
3742
}
3843
}
3944

40-
fn seal(&self) {
45+
fn seal_bloom(&self) {
4146
self.bloom.get_or_init(|| {
42-
let mut bloom = Bloom::default();
43-
for log in self.logs() {
44-
bloom.accrue_log(log);
45-
}
46-
bloom
47+
self.receipts.iter().fold(Bloom::default(), |mut bloom, r| {
48+
bloom |= r.bloom();
49+
bloom
50+
})
4751
});
4852
}
4953

5054
fn unseal(&mut self) {
5155
self.bloom.take();
56+
self.receipt_root.take();
5257
}
5358

5459
/// Reserve memory for `capacity` transaction outcomes.
@@ -70,8 +75,8 @@ impl<T: TxReceipt<Log = alloy::primitives::Log>> BlockOutput<T> {
7075

7176
/// Get the logs bloom of the block.
7277
pub fn logs_bloom(&self) -> Bloom {
73-
self.seal();
74-
self.bloom.get().cloned().unwrap()
78+
self.seal_bloom();
79+
*self.bloom.get().unwrap()
7580
}
7681

7782
/// Get a reference the senders of the transactions in the block.
@@ -113,10 +118,54 @@ impl<T: TxReceipt<Log = alloy::primitives::Log>> BlockOutput<T> {
113118
)
114119
}
115120

116-
/// Deconstruct the block output into its parts.
117-
pub fn into_parts(self) -> (Vec<T>, Vec<Address>, Bloom) {
118-
let bloom = self.logs_bloom();
119-
(self.receipts, self.senders, bloom)
121+
/// Deconstruct the block output into its parts, returning any memoized
122+
/// bloom and receipt root values.
123+
pub fn into_parts(self) -> (Vec<T>, Vec<Address>, Option<Bloom>, Option<B256>) {
124+
(self.receipts, self.senders, self.bloom.into_inner(), self.receipt_root.into_inner())
125+
}
126+
}
127+
128+
impl<T: TxReceipt + Encodable2718> BlockOutput<T> {
129+
/// Seal the block output, computing and memoizing the logs bloom and
130+
/// receipt root in a single pass over the receipts. The block bloom is
131+
/// derived as a side effect of receipt root computation by accumulating
132+
/// per-receipt blooms during trie encoding.
133+
///
134+
/// Subsequent calls to [`logs_bloom`] and [`receipt_root`] will return
135+
/// the memoized values without recomputation.
136+
///
137+
/// [`logs_bloom`]: Self::logs_bloom
138+
/// [`receipt_root`]: Self::receipt_root
139+
pub fn seal(&self) {
140+
if self.bloom.get().is_some() && self.receipt_root.get().is_some() {
141+
return;
142+
}
143+
144+
let mut block_bloom = Bloom::default();
145+
let root = ordered_trie_root_with_encoder(&self.receipts, |r, buf| {
146+
block_bloom |= r.bloom();
147+
r.encode_2718(buf);
148+
});
149+
150+
self.bloom.get_or_init(|| block_bloom);
151+
self.receipt_root.get_or_init(|| root);
152+
}
153+
154+
/// Get the receipt root of the block.
155+
pub fn receipt_root(&self) -> B256 {
156+
self.seal();
157+
*self.receipt_root.get().unwrap()
158+
}
159+
160+
/// Seal and deconstruct the block output into its parts.
161+
pub fn into_sealed_parts(self) -> (Vec<T>, Vec<Address>, Bloom, B256) {
162+
self.seal();
163+
(
164+
self.receipts,
165+
self.senders,
166+
self.bloom.into_inner().expect("seal sets bloom"),
167+
self.receipt_root.into_inner().expect("seal sets receipt_root"),
168+
)
120169
}
121170
}
122171

@@ -131,6 +180,16 @@ impl<T: TxReceipt + Eq> Eq for BlockOutput<T> {}
131180
#[cfg(test)]
132181
mod tests {
133182
use super::*;
183+
use alloy::{
184+
consensus::{
185+
constants::EMPTY_ROOT_HASH, Receipt, ReceiptEnvelope, ReceiptWithBloom, TxType,
186+
},
187+
primitives::{b256, Address, Bloom},
188+
};
189+
190+
fn envelope(tx_type: TxType, receipt: ReceiptWithBloom<Receipt>) -> ReceiptEnvelope {
191+
ReceiptEnvelope::from_typed(tx_type, receipt)
192+
}
134193

135194
#[test]
136195
fn block_output_eq_with_one_populated_bloom() {
@@ -141,4 +200,178 @@ mod tests {
141200
assert!(output_b.bloom.get().is_none());
142201
assert_eq!(output_a, output_b);
143202
}
203+
204+
#[test]
205+
fn empty_receipt_root() {
206+
let output = BlockOutput::default();
207+
assert_eq!(output.receipt_root(), EMPTY_ROOT_HASH);
208+
assert_eq!(output.logs_bloom(), Bloom::ZERO);
209+
}
210+
211+
#[test]
212+
fn seal_computes_bloom_and_root() {
213+
let output = BlockOutput::default();
214+
assert!(output.bloom.get().is_none());
215+
assert!(output.receipt_root.get().is_none());
216+
217+
output.seal();
218+
219+
assert!(output.bloom.get().is_some());
220+
assert!(output.receipt_root.get().is_some());
221+
}
222+
223+
#[test]
224+
fn single_eip2930_receipt_root() {
225+
// Test vector from:
226+
// - https://github.com/alloy-rs/alloy/blob/main/crates/consensus/src/receipt/mod.rs (check_receipt_root_optimism)
227+
// - https://github.com/paradigmxyz/reth/blob/main/crates/ethereum/primitives/src/receipt.rs (check_receipt_root_optimism)
228+
let logs = vec![Log::new_unchecked(Address::ZERO, vec![], Default::default())];
229+
let bloom = Bloom::from(alloy::primitives::hex!(
230+
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
231+
));
232+
233+
let receipt = ReceiptWithBloom {
234+
receipt: Receipt {
235+
status: alloy::consensus::Eip658Value::success(),
236+
cumulative_gas_used: 102068,
237+
logs,
238+
},
239+
logs_bloom: bloom,
240+
};
241+
242+
let mut output = BlockOutput::default();
243+
output.push_result(envelope(TxType::Eip2930, receipt), Address::ZERO);
244+
245+
assert_eq!(
246+
output.receipt_root(),
247+
b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0"),
248+
);
249+
assert_eq!(output.logs_bloom(), bloom);
250+
}
251+
252+
#[test]
253+
fn mixed_receipt_types() {
254+
// Adapted from https://github.com/paradigmxyz/reth/blob/main/crates/engine/tree/src/tree/payload_processor/receipt_root_task.rs
255+
// (test_receipt_root_matches_standard_calculation)
256+
let default_receipt = || ReceiptWithBloom {
257+
receipt: Receipt {
258+
status: alloy::consensus::Eip658Value::success(),
259+
cumulative_gas_used: 0,
260+
logs: vec![],
261+
},
262+
logs_bloom: Bloom::ZERO,
263+
};
264+
265+
let legacy = {
266+
let mut r = default_receipt();
267+
r.receipt.cumulative_gas_used = 21000;
268+
envelope(TxType::Legacy, r)
269+
};
270+
let eip1559 = {
271+
let mut r = default_receipt();
272+
r.receipt.cumulative_gas_used = 42000;
273+
r.receipt.logs =
274+
vec![Log::new_unchecked(Address::ZERO, vec![B256::ZERO], Bytes::new())];
275+
r.logs_bloom = alloy::consensus::TxReceipt::bloom(&r.receipt);
276+
envelope(TxType::Eip1559, r)
277+
};
278+
let eip2930 = {
279+
let mut r = default_receipt();
280+
r.receipt.cumulative_gas_used = 63000;
281+
r.receipt.status = alloy::consensus::Eip658Value::Eip658(false);
282+
envelope(TxType::Eip2930, r)
283+
};
284+
285+
let mut output = BlockOutput::default();
286+
output.push_result(legacy, Address::ZERO);
287+
output.push_result(eip1559, Address::ZERO);
288+
output.push_result(eip2930, Address::ZERO);
289+
290+
assert_eq!(
291+
output.receipt_root(),
292+
b256!("0xa4746a21d06f407a22200fed3491774c1b455736af092b7dcd7565de6b11da0c"),
293+
);
294+
assert_eq!(
295+
output.logs_bloom(),
296+
Bloom::from(alloy::primitives::hex!(
297+
"00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000"
298+
)),
299+
);
300+
}
301+
302+
#[test]
303+
fn into_sealed_parts_returns_all() {
304+
let receipt = ReceiptWithBloom {
305+
receipt: Receipt {
306+
status: alloy::consensus::Eip658Value::success(),
307+
cumulative_gas_used: 100,
308+
logs: vec![],
309+
},
310+
logs_bloom: Bloom::ZERO,
311+
};
312+
313+
let mut output = BlockOutput::default();
314+
output.push_result(envelope(TxType::Legacy, receipt), Address::ZERO);
315+
316+
let (receipts, senders, bloom, root) = output.into_sealed_parts();
317+
assert_eq!(receipts.len(), 1);
318+
assert_eq!(senders.len(), 1);
319+
assert_eq!(
320+
root,
321+
b256!("0x134447aac1b9bfa029f21da1cad6d6a71f841f16dc45134b24f14effe1efe791"),
322+
);
323+
assert_eq!(bloom, Bloom::ZERO);
324+
}
325+
326+
#[test]
327+
fn into_parts_without_seal() {
328+
let mut output = BlockOutput::default();
329+
output.push_result(
330+
envelope(
331+
TxType::Legacy,
332+
ReceiptWithBloom {
333+
receipt: Receipt {
334+
status: alloy::consensus::Eip658Value::success(),
335+
cumulative_gas_used: 100,
336+
logs: vec![],
337+
},
338+
logs_bloom: Bloom::ZERO,
339+
},
340+
),
341+
Address::ZERO,
342+
);
343+
344+
let (receipts, senders, bloom, root) = output.into_parts();
345+
assert_eq!(receipts.len(), 1);
346+
assert_eq!(senders.len(), 1);
347+
assert!(bloom.is_none());
348+
assert!(root.is_none());
349+
}
350+
351+
#[test]
352+
fn unseal_clears_memoized_values() {
353+
let mut output = BlockOutput::default();
354+
output.seal();
355+
assert!(output.bloom.get().is_some());
356+
assert!(output.receipt_root.get().is_some());
357+
358+
output.push_result(
359+
envelope(
360+
TxType::Legacy,
361+
ReceiptWithBloom {
362+
receipt: Receipt {
363+
status: alloy::consensus::Eip658Value::success(),
364+
cumulative_gas_used: 100,
365+
logs: vec![],
366+
},
367+
logs_bloom: Bloom::ZERO,
368+
},
369+
),
370+
Address::ZERO,
371+
);
372+
373+
// push_result calls unseal
374+
assert!(output.bloom.get().is_none());
375+
assert!(output.receipt_root.get().is_none());
376+
}
144377
}

0 commit comments

Comments
 (0)