Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# Trevm
# trevm

## Commands

- `cargo +nightly fmt` - format
- `cargo clippy --all-features --all-targets` - lint with features
- `cargo clippy --no-default-features --all-targets` - lint without
- `cargo t --all-features` - test with all features
- `cargo t --no-default-features` - test without features
- `cargo clippy -p trevm --all-features --all-targets` - lint with features
- `cargo clippy -p trevm --no-default-features --all-targets` - lint without
- `cargo t -p trevm` - test

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

## Style

Expand All @@ -31,3 +30,10 @@ Pre-commit: clippy (both feature sets) + fmt. Never use `cargo check/build`.
- Extensive feature flags: test with both `--all-features` and `--no-default-features`
- Key features: `call`, `concurrent-db`, `estimate_gas`, `tracing-inspectors`, `alloy-db`, `test-utils`
- Uses `#[cfg_attr(docsrs, doc(cfg(...)))]` for feature-gated documentation

## Versioning

Trevm uses semver. While pre-1.0, the MINOR version tracks revm's MAJOR
version (e.g. trevm `0.34.x` targets revm `34.x.x`). Breaking changes go
in PATCH versions to preserve this relationship, documented in GitHub
release notes. Always bump the patch version for breaking changes.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "trevm"
version = "0.34.1"
version = "0.34.2"
rust-version = "1.83.0"
edition = "2021"
authors = ["init4"]
Expand Down
261 changes: 247 additions & 14 deletions src/lifecycle/output.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use alloy::{
consensus::{ReceiptEnvelope, TxReceipt},
primitives::{Address, Bloom, Bytes, Log},
consensus::{proofs::ordered_trie_root_with_encoder, ReceiptEnvelope, TxReceipt},
eips::eip2718::Encodable2718,
primitives::{Address, Bloom, Bytes, Log, B256},
};
use std::sync::OnceLock;

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

/// The logs bloom of the block.
bloom: OnceLock<Bloom>,

/// The receipt root of the block.
receipt_root: OnceLock<B256>,
}

impl Default for BlockOutput {
Expand All @@ -34,21 +38,22 @@ impl<T: TxReceipt<Log = alloy::primitives::Log>> BlockOutput<T> {
receipts: Vec::with_capacity(capacity),
senders: Vec::with_capacity(capacity),
bloom: Default::default(),
receipt_root: Default::default(),
}
}

fn seal(&self) {
fn seal_bloom(&self) {
self.bloom.get_or_init(|| {
let mut bloom = Bloom::default();
for log in self.logs() {
bloom.accrue_log(log);
}
bloom
self.receipts.iter().fold(Bloom::default(), |mut bloom, r| {
bloom |= r.bloom();
bloom
})
});
}

fn unseal(&mut self) {
self.bloom.take();
self.receipt_root.take();
}

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

/// Get the logs bloom of the block.
pub fn logs_bloom(&self) -> Bloom {
self.seal();
self.bloom.get().cloned().unwrap()
self.seal_bloom();
*self.bloom.get().unwrap()
}

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

/// Deconstruct the block output into its parts.
pub fn into_parts(self) -> (Vec<T>, Vec<Address>, Bloom) {
let bloom = self.logs_bloom();
(self.receipts, self.senders, bloom)
/// Deconstruct the block output into its parts, returning any memoized
/// bloom and receipt root values.
pub fn into_parts(self) -> (Vec<T>, Vec<Address>, Option<Bloom>, Option<B256>) {
(self.receipts, self.senders, self.bloom.into_inner(), self.receipt_root.into_inner())
}
}

impl<T: TxReceipt + Encodable2718> BlockOutput<T> {
/// Seal the block output, computing and memoizing the logs bloom and
/// receipt root in a single pass over the receipts. The block bloom is
/// derived as a side effect of receipt root computation by accumulating
/// per-receipt blooms during trie encoding.
///
/// Subsequent calls to [`logs_bloom`] and [`receipt_root`] will return
/// the memoized values without recomputation.
///
/// [`logs_bloom`]: Self::logs_bloom
/// [`receipt_root`]: Self::receipt_root
pub fn seal(&self) {
if self.bloom.get().is_some() && self.receipt_root.get().is_some() {
return;
}

let mut block_bloom = Bloom::default();
let root = ordered_trie_root_with_encoder(&self.receipts, |r, buf| {
block_bloom |= r.bloom();
r.encode_2718(buf);
});

self.bloom.get_or_init(|| block_bloom);
self.receipt_root.get_or_init(|| root);
}

/// Get the receipt root of the block.
pub fn receipt_root(&self) -> B256 {
self.seal();
*self.receipt_root.get().unwrap()
}

/// Seal and deconstruct the block output into its parts.
pub fn into_sealed_parts(self) -> (Vec<T>, Vec<Address>, Bloom, B256) {
self.seal();
(
self.receipts,
self.senders,
self.bloom.into_inner().expect("seal sets bloom"),
self.receipt_root.into_inner().expect("seal sets receipt_root"),
)
}
}

Expand All @@ -131,6 +180,16 @@ impl<T: TxReceipt + Eq> Eq for BlockOutput<T> {}
#[cfg(test)]
mod tests {
use super::*;
use alloy::{
consensus::{
constants::EMPTY_ROOT_HASH, Receipt, ReceiptEnvelope, ReceiptWithBloom, TxType,
},
primitives::{b256, Address, Bloom},
};

fn envelope(tx_type: TxType, receipt: ReceiptWithBloom<Receipt>) -> ReceiptEnvelope {
ReceiptEnvelope::from_typed(tx_type, receipt)
}

#[test]
fn block_output_eq_with_one_populated_bloom() {
Expand All @@ -141,4 +200,178 @@ mod tests {
assert!(output_b.bloom.get().is_none());
assert_eq!(output_a, output_b);
}

#[test]
fn empty_receipt_root() {
let output = BlockOutput::default();
assert_eq!(output.receipt_root(), EMPTY_ROOT_HASH);
assert_eq!(output.logs_bloom(), Bloom::ZERO);
}

#[test]
fn seal_computes_bloom_and_root() {
let output = BlockOutput::default();
assert!(output.bloom.get().is_none());
assert!(output.receipt_root.get().is_none());

output.seal();

assert!(output.bloom.get().is_some());
assert!(output.receipt_root.get().is_some());
}

#[test]
fn single_eip2930_receipt_root() {
// Test vector from:
// - https://github.com/alloy-rs/alloy/blob/main/crates/consensus/src/receipt/mod.rs (check_receipt_root_optimism)
// - https://github.com/paradigmxyz/reth/blob/main/crates/ethereum/primitives/src/receipt.rs (check_receipt_root_optimism)
let logs = vec![Log::new_unchecked(Address::ZERO, vec![], Default::default())];
let bloom = Bloom::from(alloy::primitives::hex!(
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
));

let receipt = ReceiptWithBloom {
receipt: Receipt {
status: alloy::consensus::Eip658Value::success(),
cumulative_gas_used: 102068,
logs,
},
logs_bloom: bloom,
};

let mut output = BlockOutput::default();
output.push_result(envelope(TxType::Eip2930, receipt), Address::ZERO);

assert_eq!(
output.receipt_root(),
b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0"),
);
assert_eq!(output.logs_bloom(), bloom);
}

#[test]
fn mixed_receipt_types() {
// Adapted from https://github.com/paradigmxyz/reth/blob/main/crates/engine/tree/src/tree/payload_processor/receipt_root_task.rs
// (test_receipt_root_matches_standard_calculation)
let default_receipt = || ReceiptWithBloom {
receipt: Receipt {
status: alloy::consensus::Eip658Value::success(),
cumulative_gas_used: 0,
logs: vec![],
},
logs_bloom: Bloom::ZERO,
};

let legacy = {
let mut r = default_receipt();
r.receipt.cumulative_gas_used = 21000;
envelope(TxType::Legacy, r)
};
let eip1559 = {
let mut r = default_receipt();
r.receipt.cumulative_gas_used = 42000;
r.receipt.logs =
vec![Log::new_unchecked(Address::ZERO, vec![B256::ZERO], Bytes::new())];
r.logs_bloom = alloy::consensus::TxReceipt::bloom(&r.receipt);
envelope(TxType::Eip1559, r)
};
let eip2930 = {
let mut r = default_receipt();
r.receipt.cumulative_gas_used = 63000;
r.receipt.status = alloy::consensus::Eip658Value::Eip658(false);
envelope(TxType::Eip2930, r)
};

let mut output = BlockOutput::default();
output.push_result(legacy, Address::ZERO);
output.push_result(eip1559, Address::ZERO);
output.push_result(eip2930, Address::ZERO);

assert_eq!(
output.receipt_root(),
b256!("0xa4746a21d06f407a22200fed3491774c1b455736af092b7dcd7565de6b11da0c"),
);
assert_eq!(
output.logs_bloom(),
Bloom::from(alloy::primitives::hex!(
"00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000"
)),
);
}

#[test]
fn into_sealed_parts_returns_all() {
let receipt = ReceiptWithBloom {
receipt: Receipt {
status: alloy::consensus::Eip658Value::success(),
cumulative_gas_used: 100,
logs: vec![],
},
logs_bloom: Bloom::ZERO,
};

let mut output = BlockOutput::default();
output.push_result(envelope(TxType::Legacy, receipt), Address::ZERO);

let (receipts, senders, bloom, root) = output.into_sealed_parts();
assert_eq!(receipts.len(), 1);
assert_eq!(senders.len(), 1);
assert_eq!(
root,
b256!("0x134447aac1b9bfa029f21da1cad6d6a71f841f16dc45134b24f14effe1efe791"),
);
assert_eq!(bloom, Bloom::ZERO);
}

#[test]
fn into_parts_without_seal() {
let mut output = BlockOutput::default();
output.push_result(
envelope(
TxType::Legacy,
ReceiptWithBloom {
receipt: Receipt {
status: alloy::consensus::Eip658Value::success(),
cumulative_gas_used: 100,
logs: vec![],
},
logs_bloom: Bloom::ZERO,
},
),
Address::ZERO,
);

let (receipts, senders, bloom, root) = output.into_parts();
assert_eq!(receipts.len(), 1);
assert_eq!(senders.len(), 1);
assert!(bloom.is_none());
assert!(root.is_none());
}

#[test]
fn unseal_clears_memoized_values() {
let mut output = BlockOutput::default();
output.seal();
assert!(output.bloom.get().is_some());
assert!(output.receipt_root.get().is_some());

output.push_result(
envelope(
TxType::Legacy,
ReceiptWithBloom {
receipt: Receipt {
status: alloy::consensus::Eip658Value::success(),
cumulative_gas_used: 100,
logs: vec![],
},
logs_bloom: Bloom::ZERO,
},
),
Address::ZERO,
);

// push_result calls unseal
assert!(output.bloom.get().is_none());
assert!(output.receipt_root.get().is_none());
}
}