Skip to content

Commit d2ad477

Browse files
authored
refactor: mv proofs mod to reth-primitives-traits and split tests (paradigmxyz#13871)
1 parent f527b5a commit d2ad477

File tree

9 files changed

+184
-27
lines changed

9 files changed

+184
-27
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ethereum/primitives/src/receipt.rs

+78-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ use alloy_consensus::{
33
Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
44
RlpEncodableReceipt, TxReceipt, TxType, Typed2718,
55
};
6-
use alloy_primitives::{Bloom, Log};
6+
use alloy_eips::eip2718::Encodable2718;
7+
use alloy_primitives::{Bloom, Log, B256};
78
use alloy_rlp::{BufMut, Decodable, Encodable, Header};
8-
use reth_primitives_traits::InMemorySize;
9+
use reth_primitives_traits::{proofs::ordered_trie_root_with_encoder, InMemorySize};
910
use serde::{Deserialize, Serialize};
1011

1112
/// Typed ethereum transaction receipt.
@@ -80,6 +81,13 @@ impl Receipt {
8081
logs_bloom,
8182
})
8283
}
84+
85+
/// Calculates the receipt root for a header for the reference type of [Receipt].
86+
///
87+
/// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized.
88+
pub fn calculate_receipt_root_no_memo(receipts: &[&Self]) -> B256 {
89+
ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
90+
}
8391
}
8492

8593
impl Eip2718EncodableReceipt for Receipt {
@@ -188,9 +196,21 @@ impl reth_primitives_traits::Receipt for Receipt {}
188196
#[cfg(test)]
189197
mod tests {
190198
use super::*;
199+
use crate::TransactionSigned;
191200
use alloy_eips::eip2718::Encodable2718;
192-
use alloy_primitives::{address, b256, bytes, hex_literal::hex, Bytes};
201+
use alloy_primitives::{
202+
address, b256, bloom, bytes, hex_literal::hex, Address, Bytes, Log, LogData,
203+
};
204+
use alloy_rlp::Decodable;
193205
use reth_codecs::Compact;
206+
use reth_primitives_traits::proofs::{
207+
calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root,
208+
};
209+
210+
/// Ethereum full block.
211+
///
212+
/// Withdrawals can be optionally included at the end of the RLP encoded message.
213+
pub(crate) type Block<T = TransactionSigned> = alloy_consensus::Block<T>;
194214

195215
#[test]
196216
fn test_decode_receipt() {
@@ -319,4 +339,59 @@ mod tests {
319339
"Encoded length for legacy receipt should match the actual encoded data length"
320340
);
321341
}
342+
343+
#[test]
344+
fn check_transaction_root() {
345+
let data = &hex!("f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0");
346+
let block_rlp = &mut data.as_slice();
347+
let block: Block = Block::decode(block_rlp).unwrap();
348+
349+
let tx_root = calculate_transaction_root(&block.body.transactions);
350+
assert_eq!(block.transactions_root, tx_root, "Must be the same");
351+
}
352+
353+
#[test]
354+
fn check_withdrawals_root() {
355+
// Single withdrawal, amount 0
356+
// https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/amountIs0.json
357+
let data = &hex!("f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80");
358+
let block: Block = Block::decode(&mut data.as_slice()).unwrap();
359+
assert!(block.body.withdrawals.is_some());
360+
let withdrawals = block.body.withdrawals.as_ref().unwrap();
361+
assert_eq!(withdrawals.len(), 1);
362+
let withdrawals_root = calculate_withdrawals_root(withdrawals);
363+
assert_eq!(block.withdrawals_root, Some(withdrawals_root));
364+
365+
// 4 withdrawals, identical indices
366+
// https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndex.json
367+
let data = &hex!("f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710");
368+
let block: Block = Block::decode(&mut data.as_slice()).unwrap();
369+
assert!(block.body.withdrawals.is_some());
370+
let withdrawals = block.body.withdrawals.as_ref().unwrap();
371+
assert_eq!(withdrawals.len(), 4);
372+
let withdrawals_root = calculate_withdrawals_root(withdrawals);
373+
assert_eq!(block.withdrawals_root, Some(withdrawals_root));
374+
}
375+
#[test]
376+
fn check_receipt_root_optimism() {
377+
use alloy_consensus::ReceiptWithBloom;
378+
379+
let logs = vec![Log {
380+
address: Address::ZERO,
381+
data: LogData::new_unchecked(vec![], Default::default()),
382+
}];
383+
let bloom = bloom!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
384+
let receipt = ReceiptWithBloom {
385+
receipt: Receipt {
386+
tx_type: TxType::Eip2930,
387+
success: true,
388+
cumulative_gas_used: 102068,
389+
logs,
390+
},
391+
logs_bloom: bloom,
392+
};
393+
let receipt = vec![receipt];
394+
let root = calculate_receipt_root(&receipt);
395+
assert_eq!(root, b256!("fe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0"));
396+
}
322397
}

crates/evm/execution-types/src/execution_outcome.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ impl ExecutionOutcome {
362362
/// of receipt. This is a expensive operation.
363363
pub fn ethereum_receipts_root(&self, _block_number: BlockNumber) -> Option<B256> {
364364
self.receipts.root_slow(self.block_number_to_index(_block_number)?, |receipts| {
365-
reth_primitives::proofs::calculate_receipt_root_no_memo(receipts)
365+
reth_primitives::Receipt::calculate_receipt_root_no_memo(receipts)
366366
})
367367
}
368368
}

crates/primitives-traits/Cargo.toml

+7-3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ rayon = { workspace = true, optional = true }
5454

5555
[dev-dependencies]
5656
reth-codecs.workspace = true
57+
reth-chainspec = { workspace = true, features = ["arbitrary"] }
5758

5859
alloy-primitives = { workspace = true, features = ["arbitrary", "serde"] }
5960
alloy-consensus = { workspace = true, features = ["arbitrary", "serde"] }
@@ -93,12 +94,14 @@ std = [
9394
"thiserror/std",
9495
"alloy-trie/std",
9596
"op-alloy-consensus?/std",
96-
"serde_json/std"
97+
"serde_json/std",
98+
"reth-chainspec/std"
9799
]
98100
secp256k1 = ["dep:secp256k1"]
99101
test-utils = [
100102
"arbitrary",
101-
"reth-codecs?/test-utils"
103+
"reth-codecs?/test-utils",
104+
"reth-chainspec/test-utils"
102105
]
103106
arbitrary = [
104107
"std",
@@ -113,7 +116,8 @@ arbitrary = [
113116
"secp256k1?/global-context",
114117
"secp256k1?/rand",
115118
"op-alloy-consensus?/arbitrary",
116-
"alloy-trie/arbitrary"
119+
"alloy-trie/arbitrary",
120+
"reth-chainspec/arbitrary"
117121
]
118122
serde-bincode-compat = [
119123
"serde",

crates/primitives-traits/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ pub use error::{GotExpected, GotExpectedBoxed};
9393
mod log;
9494
pub use alloy_primitives::{logs_bloom, Log, LogData};
9595

96+
pub mod proofs;
97+
9698
mod storage;
9799
pub use storage::StorageEntry;
98100

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Helper function for calculating Merkle proofs and hashes.
2+
pub use alloy_trie::root::ordered_trie_root_with_encoder;
3+
4+
pub use alloy_consensus::proofs::calculate_receipt_root;
5+
6+
/// Calculate a transaction root.
7+
///
8+
/// `(rlp(index), encoded(tx))` pairs.
9+
#[doc(inline)]
10+
pub use alloy_consensus::proofs::calculate_transaction_root;
11+
12+
/// Calculates the root hash of the withdrawals.
13+
#[doc(inline)]
14+
pub use alloy_consensus::proofs::calculate_withdrawals_root;
15+
16+
/// Calculates the root hash for ommer/uncle headers.
17+
#[doc(inline)]
18+
pub use alloy_consensus::proofs::calculate_ommers_root;
19+
20+
#[cfg(test)]
21+
mod tests {
22+
use alloy_consensus::EMPTY_ROOT_HASH;
23+
use alloy_genesis::GenesisAccount;
24+
use alloy_primitives::{b256, hex_literal::hex, Address, B256, U256};
25+
use alloy_trie::root::{state_root_ref_unhashed, state_root_unhashed};
26+
use reth_chainspec::{HOLESKY, MAINNET, SEPOLIA};
27+
use std::collections::HashMap;
28+
29+
#[test]
30+
fn check_empty_state_root() {
31+
let genesis_alloc = HashMap::<Address, GenesisAccount>::new();
32+
let root = state_root_unhashed(genesis_alloc);
33+
assert_eq!(root, EMPTY_ROOT_HASH);
34+
}
35+
36+
#[test]
37+
fn test_simple_account_state_root() {
38+
// each fixture specifies an address and expected root hash - the address is initialized
39+
// with a maximum balance, and is the only account in the state.
40+
// these test cases are generated by using geth with a custom genesis.json (with a single
41+
// account that has max balance)
42+
let fixtures: Vec<(Address, B256)> = vec![
43+
(
44+
hex!("9fe4abd71ad081f091bd06dd1c16f7e92927561e").into(),
45+
hex!("4b35be4231841d212ce2fa43aedbddeadd6eb7d420195664f9f0d55629db8c32").into(),
46+
),
47+
(
48+
hex!("c2ba9d87f8be0ade00c60d3656c1188e008fbfa2").into(),
49+
hex!("e1389256c47d63df8856d7729dec9dc2dae074a7f0cbc49acad1cf7b29f7fe94").into(),
50+
),
51+
];
52+
53+
for (test_addr, expected_root) in fixtures {
54+
let mut genesis_alloc = HashMap::new();
55+
genesis_alloc
56+
.insert(test_addr, GenesisAccount { balance: U256::MAX, ..Default::default() });
57+
58+
let root = state_root_unhashed(genesis_alloc);
59+
60+
assert_eq!(root, expected_root);
61+
}
62+
}
63+
64+
#[test]
65+
fn test_chain_state_roots() {
66+
let expected_mainnet_state_root =
67+
b256!("d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544");
68+
let calculated_mainnet_state_root = state_root_ref_unhashed(&MAINNET.genesis.alloc);
69+
assert_eq!(
70+
expected_mainnet_state_root, calculated_mainnet_state_root,
71+
"mainnet state root mismatch"
72+
);
73+
74+
let expected_sepolia_state_root =
75+
b256!("5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494");
76+
let calculated_sepolia_state_root = state_root_ref_unhashed(&SEPOLIA.genesis.alloc);
77+
assert_eq!(
78+
expected_sepolia_state_root, calculated_sepolia_state_root,
79+
"sepolia state root mismatch"
80+
);
81+
82+
let expected_holesky_state_root =
83+
b256!("69d8c9d72f6fa4ad42d4702b433707212f90db395eb54dc20bc85de253788783");
84+
let calculated_holesky_state_root = state_root_ref_unhashed(&HOLESKY.genesis.alloc);
85+
assert_eq!(
86+
expected_holesky_state_root, calculated_holesky_state_root,
87+
"holesky state root mismatch"
88+
);
89+
}
90+
}

crates/primitives/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ reth-static-file-types.workspace = true
2121
# ethereum
2222
alloy-consensus.workspace = true
2323
alloy-primitives = { workspace = true, features = ["rand", "rlp"] }
24-
alloy-eips = { workspace = true, features = ["serde"] }
2524
alloy-trie = { workspace = true, features = ["serde"] }
2625

2726
# for eip-4844

crates/primitives/src/proofs.rs

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
//! Helper function for calculating Merkle proofs and hashes.
2-
3-
use crate::Receipt;
4-
use alloy_consensus::TxReceipt;
5-
use alloy_eips::eip2718::Encodable2718;
6-
use alloy_primitives::B256;
72
pub use alloy_trie::root::ordered_trie_root_with_encoder;
83

94
pub use alloy_consensus::proofs::calculate_receipt_root;
@@ -22,22 +17,16 @@ pub use alloy_consensus::proofs::calculate_withdrawals_root;
2217
#[doc(inline)]
2318
pub use alloy_consensus::proofs::calculate_ommers_root;
2419

25-
/// Calculates the receipt root for a header for the reference type of [Receipt].
26-
///
27-
/// NOTE: Prefer [`calculate_receipt_root`] if you have log blooms memoized.
28-
pub fn calculate_receipt_root_no_memo(receipts: &[&Receipt]) -> B256 {
29-
ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
30-
}
31-
3220
#[cfg(test)]
3321
mod tests {
3422
use super::*;
3523
use crate::{Block, TxType};
3624
use alloy_consensus::EMPTY_ROOT_HASH;
3725
use alloy_genesis::GenesisAccount;
38-
use alloy_primitives::{b256, bloom, hex_literal::hex, Address, Log, LogData, U256};
26+
use alloy_primitives::{b256, bloom, hex_literal::hex, Address, Log, LogData, B256, U256};
3927
use alloy_rlp::Decodable;
4028
use reth_chainspec::{HOLESKY, MAINNET, SEPOLIA};
29+
use reth_ethereum_primitives::Receipt;
4130
use reth_trie_common::root::{state_root_ref_unhashed, state_root_unhashed};
4231
use std::collections::HashMap;
4332

crates/rpc/rpc/src/eth/helpers/pending_block.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE};
55
use alloy_primitives::U256;
66
use reth_chainspec::{EthChainSpec, EthereumHardforks};
77
use reth_evm::ConfigureEvm;
8-
use reth_primitives::{
9-
logs_bloom,
10-
proofs::{calculate_receipt_root_no_memo, calculate_transaction_root},
11-
BlockBody, Receipt,
12-
};
8+
use reth_primitives::{logs_bloom, proofs::calculate_transaction_root, BlockBody, Receipt};
139
use reth_provider::{
1410
BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderReceipt, ProviderTx,
1511
StateProviderFactory,
@@ -64,7 +60,8 @@ where
6460
let chain_spec = self.provider().chain_spec();
6561

6662
let transactions_root = calculate_transaction_root(&transactions);
67-
let receipts_root = calculate_receipt_root_no_memo(&receipts.iter().collect::<Vec<_>>());
63+
let receipts_root =
64+
Receipt::calculate_receipt_root_no_memo(&receipts.iter().collect::<Vec<_>>());
6865

6966
let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| &r.logs));
7067

0 commit comments

Comments
 (0)