From d2ad477b0e6601b0c8da3636c696ad866f56a9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Narzis?= <78718413+lean-apple@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:18:29 +0100 Subject: [PATCH] refactor: mv proofs mod to reth-primitives-traits and split tests (#13871) --- Cargo.lock | 1 + crates/ethereum/primitives/src/receipt.rs | 81 ++++++++++++++++- .../execution-types/src/execution_outcome.rs | 2 +- crates/primitives-traits/Cargo.toml | 10 ++- crates/primitives-traits/src/lib.rs | 2 + crates/primitives-traits/src/proofs.rs | 90 +++++++++++++++++++ crates/primitives/Cargo.toml | 1 - crates/primitives/src/proofs.rs | 15 +--- .../rpc/rpc/src/eth/helpers/pending_block.rs | 9 +- 9 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 crates/primitives-traits/src/proofs.rs diff --git a/Cargo.lock b/Cargo.lock index 7e7c5ff87..b7494c07e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8622,6 +8622,7 @@ dependencies = [ "proptest-arbitrary-interop", "rand 0.8.5", "rayon", + "reth-chainspec", "reth-codecs", "revm-primitives", "secp256k1", diff --git a/crates/ethereum/primitives/src/receipt.rs b/crates/ethereum/primitives/src/receipt.rs index 491a544ef..23e80064e 100644 --- a/crates/ethereum/primitives/src/receipt.rs +++ b/crates/ethereum/primitives/src/receipt.rs @@ -3,9 +3,10 @@ use alloy_consensus::{ Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, TxType, Typed2718, }; -use alloy_primitives::{Bloom, Log}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Bloom, Log, B256}; use alloy_rlp::{BufMut, Decodable, Encodable, Header}; -use reth_primitives_traits::InMemorySize; +use reth_primitives_traits::{proofs::ordered_trie_root_with_encoder, InMemorySize}; use serde::{Deserialize, Serialize}; /// Typed ethereum transaction receipt. @@ -80,6 +81,13 @@ impl Receipt { logs_bloom, }) } + + /// Calculates the receipt root for a header for the reference type of [Receipt]. + /// + /// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized. + pub fn calculate_receipt_root_no_memo(receipts: &[&Self]) -> B256 { + ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf)) + } } impl Eip2718EncodableReceipt for Receipt { @@ -188,9 +196,21 @@ impl reth_primitives_traits::Receipt for Receipt {} #[cfg(test)] mod tests { use super::*; + use crate::TransactionSigned; use alloy_eips::eip2718::Encodable2718; - use alloy_primitives::{address, b256, bytes, hex_literal::hex, Bytes}; + use alloy_primitives::{ + address, b256, bloom, bytes, hex_literal::hex, Address, Bytes, Log, LogData, + }; + use alloy_rlp::Decodable; use reth_codecs::Compact; + use reth_primitives_traits::proofs::{ + calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root, + }; + + /// Ethereum full block. + /// + /// Withdrawals can be optionally included at the end of the RLP encoded message. + pub(crate) type Block = alloy_consensus::Block; #[test] fn test_decode_receipt() { @@ -319,4 +339,59 @@ mod tests { "Encoded length for legacy receipt should match the actual encoded data length" ); } + + #[test] + fn check_transaction_root() { + let data = &hex!("f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0"); + let block_rlp = &mut data.as_slice(); + let block: Block = Block::decode(block_rlp).unwrap(); + + let tx_root = calculate_transaction_root(&block.body.transactions); + assert_eq!(block.transactions_root, tx_root, "Must be the same"); + } + + #[test] + fn check_withdrawals_root() { + // Single withdrawal, amount 0 + // https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/amountIs0.json + let data = &hex!("f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80"); + let block: Block = Block::decode(&mut data.as_slice()).unwrap(); + assert!(block.body.withdrawals.is_some()); + let withdrawals = block.body.withdrawals.as_ref().unwrap(); + assert_eq!(withdrawals.len(), 1); + let withdrawals_root = calculate_withdrawals_root(withdrawals); + assert_eq!(block.withdrawals_root, Some(withdrawals_root)); + + // 4 withdrawals, identical indices + // https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndex.json + let data = &hex!("f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710"); + let block: Block = Block::decode(&mut data.as_slice()).unwrap(); + assert!(block.body.withdrawals.is_some()); + let withdrawals = block.body.withdrawals.as_ref().unwrap(); + assert_eq!(withdrawals.len(), 4); + let withdrawals_root = calculate_withdrawals_root(withdrawals); + assert_eq!(block.withdrawals_root, Some(withdrawals_root)); + } + #[test] + fn check_receipt_root_optimism() { + use alloy_consensus::ReceiptWithBloom; + + let logs = vec![Log { + address: Address::ZERO, + data: LogData::new_unchecked(vec![], Default::default()), + }]; + let bloom = bloom!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); + let receipt = ReceiptWithBloom { + receipt: Receipt { + tx_type: TxType::Eip2930, + success: true, + cumulative_gas_used: 102068, + logs, + }, + logs_bloom: bloom, + }; + let receipt = vec![receipt]; + let root = calculate_receipt_root(&receipt); + assert_eq!(root, b256!("fe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")); + } } diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index 1a0f8a5c3..9d735271c 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -362,7 +362,7 @@ impl ExecutionOutcome { /// of receipt. This is a expensive operation. pub fn ethereum_receipts_root(&self, _block_number: BlockNumber) -> Option { self.receipts.root_slow(self.block_number_to_index(_block_number)?, |receipts| { - reth_primitives::proofs::calculate_receipt_root_no_memo(receipts) + reth_primitives::Receipt::calculate_receipt_root_no_memo(receipts) }) } } diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index d017ea650..7f0c98fc8 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -54,6 +54,7 @@ rayon = { workspace = true, optional = true } [dev-dependencies] reth-codecs.workspace = true +reth-chainspec = { workspace = true, features = ["arbitrary"] } alloy-primitives = { workspace = true, features = ["arbitrary", "serde"] } alloy-consensus = { workspace = true, features = ["arbitrary", "serde"] } @@ -93,12 +94,14 @@ std = [ "thiserror/std", "alloy-trie/std", "op-alloy-consensus?/std", - "serde_json/std" + "serde_json/std", + "reth-chainspec/std" ] secp256k1 = ["dep:secp256k1"] test-utils = [ "arbitrary", - "reth-codecs?/test-utils" + "reth-codecs?/test-utils", + "reth-chainspec/test-utils" ] arbitrary = [ "std", @@ -113,7 +116,8 @@ arbitrary = [ "secp256k1?/global-context", "secp256k1?/rand", "op-alloy-consensus?/arbitrary", - "alloy-trie/arbitrary" + "alloy-trie/arbitrary", + "reth-chainspec/arbitrary" ] serde-bincode-compat = [ "serde", diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index f09875ed3..558f0ad22 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -93,6 +93,8 @@ pub use error::{GotExpected, GotExpectedBoxed}; mod log; pub use alloy_primitives::{logs_bloom, Log, LogData}; +pub mod proofs; + mod storage; pub use storage::StorageEntry; diff --git a/crates/primitives-traits/src/proofs.rs b/crates/primitives-traits/src/proofs.rs new file mode 100644 index 000000000..8d5d4bce4 --- /dev/null +++ b/crates/primitives-traits/src/proofs.rs @@ -0,0 +1,90 @@ +//! Helper function for calculating Merkle proofs and hashes. +pub use alloy_trie::root::ordered_trie_root_with_encoder; + +pub use alloy_consensus::proofs::calculate_receipt_root; + +/// Calculate a transaction root. +/// +/// `(rlp(index), encoded(tx))` pairs. +#[doc(inline)] +pub use alloy_consensus::proofs::calculate_transaction_root; + +/// Calculates the root hash of the withdrawals. +#[doc(inline)] +pub use alloy_consensus::proofs::calculate_withdrawals_root; + +/// Calculates the root hash for ommer/uncle headers. +#[doc(inline)] +pub use alloy_consensus::proofs::calculate_ommers_root; + +#[cfg(test)] +mod tests { + use alloy_consensus::EMPTY_ROOT_HASH; + use alloy_genesis::GenesisAccount; + use alloy_primitives::{b256, hex_literal::hex, Address, B256, U256}; + use alloy_trie::root::{state_root_ref_unhashed, state_root_unhashed}; + use reth_chainspec::{HOLESKY, MAINNET, SEPOLIA}; + use std::collections::HashMap; + + #[test] + fn check_empty_state_root() { + let genesis_alloc = HashMap::::new(); + let root = state_root_unhashed(genesis_alloc); + assert_eq!(root, EMPTY_ROOT_HASH); + } + + #[test] + fn test_simple_account_state_root() { + // each fixture specifies an address and expected root hash - the address is initialized + // with a maximum balance, and is the only account in the state. + // these test cases are generated by using geth with a custom genesis.json (with a single + // account that has max balance) + let fixtures: Vec<(Address, B256)> = vec![ + ( + hex!("9fe4abd71ad081f091bd06dd1c16f7e92927561e").into(), + hex!("4b35be4231841d212ce2fa43aedbddeadd6eb7d420195664f9f0d55629db8c32").into(), + ), + ( + hex!("c2ba9d87f8be0ade00c60d3656c1188e008fbfa2").into(), + hex!("e1389256c47d63df8856d7729dec9dc2dae074a7f0cbc49acad1cf7b29f7fe94").into(), + ), + ]; + + for (test_addr, expected_root) in fixtures { + let mut genesis_alloc = HashMap::new(); + genesis_alloc + .insert(test_addr, GenesisAccount { balance: U256::MAX, ..Default::default() }); + + let root = state_root_unhashed(genesis_alloc); + + assert_eq!(root, expected_root); + } + } + + #[test] + fn test_chain_state_roots() { + let expected_mainnet_state_root = + b256!("d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"); + let calculated_mainnet_state_root = state_root_ref_unhashed(&MAINNET.genesis.alloc); + assert_eq!( + expected_mainnet_state_root, calculated_mainnet_state_root, + "mainnet state root mismatch" + ); + + let expected_sepolia_state_root = + b256!("5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494"); + let calculated_sepolia_state_root = state_root_ref_unhashed(&SEPOLIA.genesis.alloc); + assert_eq!( + expected_sepolia_state_root, calculated_sepolia_state_root, + "sepolia state root mismatch" + ); + + let expected_holesky_state_root = + b256!("69d8c9d72f6fa4ad42d4702b433707212f90db395eb54dc20bc85de253788783"); + let calculated_holesky_state_root = state_root_ref_unhashed(&HOLESKY.genesis.alloc); + assert_eq!( + expected_holesky_state_root, calculated_holesky_state_root, + "holesky state root mismatch" + ); + } +} diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 7490535da..da2fa01b2 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -21,7 +21,6 @@ reth-static-file-types.workspace = true # ethereum alloy-consensus.workspace = true alloy-primitives = { workspace = true, features = ["rand", "rlp"] } -alloy-eips = { workspace = true, features = ["serde"] } alloy-trie = { workspace = true, features = ["serde"] } # for eip-4844 diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index 1d4a95e73..493259fac 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -1,9 +1,4 @@ //! Helper function for calculating Merkle proofs and hashes. - -use crate::Receipt; -use alloy_consensus::TxReceipt; -use alloy_eips::eip2718::Encodable2718; -use alloy_primitives::B256; pub use alloy_trie::root::ordered_trie_root_with_encoder; pub use alloy_consensus::proofs::calculate_receipt_root; @@ -22,22 +17,16 @@ pub use alloy_consensus::proofs::calculate_withdrawals_root; #[doc(inline)] pub use alloy_consensus::proofs::calculate_ommers_root; -/// Calculates the receipt root for a header for the reference type of [Receipt]. -/// -/// NOTE: Prefer [`calculate_receipt_root`] if you have log blooms memoized. -pub fn calculate_receipt_root_no_memo(receipts: &[&Receipt]) -> B256 { - ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf)) -} - #[cfg(test)] mod tests { use super::*; use crate::{Block, TxType}; use alloy_consensus::EMPTY_ROOT_HASH; use alloy_genesis::GenesisAccount; - use alloy_primitives::{b256, bloom, hex_literal::hex, Address, Log, LogData, U256}; + use alloy_primitives::{b256, bloom, hex_literal::hex, Address, Log, LogData, B256, U256}; use alloy_rlp::Decodable; use reth_chainspec::{HOLESKY, MAINNET, SEPOLIA}; + use reth_ethereum_primitives::Receipt; use reth_trie_common::root::{state_root_ref_unhashed, state_root_unhashed}; use std::collections::HashMap; diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index b40fff97f..d771241db 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -5,11 +5,7 @@ use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::U256; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_evm::ConfigureEvm; -use reth_primitives::{ - logs_bloom, - proofs::{calculate_receipt_root_no_memo, calculate_transaction_root}, - BlockBody, Receipt, -}; +use reth_primitives::{logs_bloom, proofs::calculate_transaction_root, BlockBody, Receipt}; use reth_provider::{ BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderReceipt, ProviderTx, StateProviderFactory, @@ -64,7 +60,8 @@ where let chain_spec = self.provider().chain_spec(); let transactions_root = calculate_transaction_root(&transactions); - let receipts_root = calculate_receipt_root_no_memo(&receipts.iter().collect::>()); + let receipts_root = + Receipt::calculate_receipt_root_no_memo(&receipts.iter().collect::>()); let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| &r.logs));