From ff27ecc75ef17e5833de2498e2b6424b59512670 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 26 Sep 2023 15:53:47 +0300 Subject: [PATCH] chore(primitives): root calculation using `HashBuilder` (#4780) --- Cargo.lock | 1 + crates/primitives/Cargo.toml | 5 +- crates/primitives/src/genesis.rs | 25 +++-- crates/primitives/src/proofs.rs | 178 +++++++++---------------------- crates/trie/src/trie.rs | 2 +- 5 files changed, 72 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fa5edbce..553febacf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5927,6 +5927,7 @@ dependencies = [ "hex", "hex-literal", "impl-serde", + "itertools 0.11.0", "modular-bitfield", "once_cell", "paste", diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index a3e1551e8..3202eed97 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -63,9 +63,8 @@ paste = "1.0" rayon = "1.7" tempfile = "3.3" sha2 = "0.10.7" +itertools = "0.11" -# proof related -triehash = "0.8" # See to replace hashers to simplify libraries plain_hasher = "0.2" hash-db = "~0.15" @@ -87,6 +86,7 @@ proptest.workspace = true proptest-derive.workspace = true assert_matches.workspace = true toml = "0.7.4" +triehash = "0.8" # necessary so we don't hit a "undeclared 'std'": # https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198 @@ -105,4 +105,5 @@ harness = false [[bench]] name = "trie_root" +required-features = ["arbitrary"] harness = false diff --git a/crates/primitives/src/genesis.rs b/crates/primitives/src/genesis.rs index 97aa9baf0..d9a793053 100644 --- a/crates/primitives/src/genesis.rs +++ b/crates/primitives/src/genesis.rs @@ -1,15 +1,16 @@ use crate::{ keccak256, - proofs::{KeccakHasher, EMPTY_ROOT}, + proofs::EMPTY_ROOT, serde_helper::{deserialize_json_u256, deserialize_json_u256_opt, deserialize_storage_map}, + trie::{HashBuilder, Nibbles}, utils::serde_helpers::{deserialize_stringified_u64, deserialize_stringified_u64_opt}, Account, Address, Bytes, H256, KECCAK_EMPTY, U256, }; +use itertools::Itertools; use reth_rlp::{encode_fixed_size, length_of_length, Encodable, Header as RlpHeader}; use revm_primitives::B160; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use triehash::sec_trie_root; /// The genesis block specification. #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] @@ -214,12 +215,20 @@ impl Encodable for GenesisAccount { if storage.is_empty() { return EMPTY_ROOT } - let storage_values = - storage.iter().filter(|(_k, &v)| v != H256::zero()).map(|(&k, v)| { - let value = U256::from_be_bytes(**v); - (k, encode_fixed_size(&value)) - }); - sec_trie_root::(storage_values) + + let storage_with_sorted_hashed_keys = storage + .iter() + .filter(|(_k, &v)| v != H256::zero()) + .map(|(slot, value)| (keccak256(slot), value)) + .sorted_by_key(|(key, _)| *key); + + let mut hb = HashBuilder::default(); + for (hashed_slot, value) in storage_with_sorted_hashed_keys { + let encoded_value = encode_fixed_size(&U256::from_be_bytes(**value)); + hb.add_leaf(Nibbles::unpack(hashed_slot), &encoded_value); + } + + hb.root() }) .encode(out); self.code.as_ref().map_or(KECCAK_EMPTY, keccak256).encode(out); diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index 54e234ef4..7b2aa111e 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -1,16 +1,16 @@ use crate::{ keccak256, trie::{HashBuilder, Nibbles}, - Address, Bytes, GenesisAccount, Header, Log, ReceiptWithBloom, ReceiptWithBloomRef, - TransactionSigned, Withdrawal, H256, + Address, GenesisAccount, Header, Log, ReceiptWithBloom, ReceiptWithBloomRef, TransactionSigned, + Withdrawal, H256, }; use bytes::{BufMut, BytesMut}; use hash_db::Hasher; use hex_literal::hex; +use itertools::Itertools; use plain_hasher::PlainHasher; use reth_rlp::Encodable; use std::collections::HashMap; -use triehash::sec_trie_root; /// Keccak-256 hash of the RLP of an empty list, KEC("\xc0"). pub const EMPTY_LIST_HASH: H256 = @@ -128,29 +128,33 @@ pub fn calculate_ommers_root(ommers: &[Header]) -> H256 { /// Calculates the root hash for the state, this corresponds to [geth's /// `deriveHash`](https://github.com/ethereum/go-ethereum/blob/6c149fd4ad063f7c24d726a73bc0546badd1bc73/core/genesis.go#L119). pub fn genesis_state_root(genesis_alloc: &HashMap) -> H256 { - let encoded_accounts = genesis_alloc.iter().map(|(address, account)| { - let mut acc_rlp = BytesMut::new(); - account.encode(&mut acc_rlp); - (address, Bytes::from(acc_rlp.freeze())) - }); + let accounts_with_sorted_hashed_keys = genesis_alloc + .iter() + .map(|(address, account)| (keccak256(address), account)) + .sorted_by_key(|(key, _)| *key); - H256(sec_trie_root::(encoded_accounts).0) + let mut hb = HashBuilder::default(); + let mut account_rlp_buf = Vec::new(); + for (hashed_key, account) in accounts_with_sorted_hashed_keys { + account_rlp_buf.clear(); + account.encode(&mut account_rlp_buf); + hb.add_leaf(Nibbles::unpack(hashed_key), &account_rlp_buf); + } + + hb.root() } #[cfg(test)] mod tests { - - use std::{collections::HashMap, str::FromStr}; - + use super::{calculate_withdrawals_root, EMPTY_ROOT}; use crate::{ hex_literal::hex, proofs::{calculate_receipt_root, calculate_transaction_root, genesis_state_root}, - Address, Block, Bloom, GenesisAccount, Log, Receipt, ReceiptWithBloom, TxType, H160, H256, - U256, + Address, Block, Bloom, GenesisAccount, Log, Receipt, ReceiptWithBloom, TxType, GOERLI, + H160, H256, HOLESKY, MAINNET, SEPOLIA, U256, }; use reth_rlp::Decodable; - - use super::{calculate_withdrawals_root, EMPTY_ROOT}; + use std::collections::HashMap; #[test] fn check_transaction_root() { @@ -243,119 +247,37 @@ mod tests { } #[test] - fn test_sepolia_state_root() { - let expected_root = - hex!("5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494").into(); - let alloc = HashMap::from([ - ( - hex!("a2A6d93439144FFE4D27c9E088dCD8b783946263").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("Bc11295936Aa79d594139de1B2e12629414F3BDB").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("7cF5b79bfe291A67AB02b393E456cCc4c266F753").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("aaec86394441f915bce3e6ab399977e9906f3b69").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("F47CaE1CF79ca6758Bfc787dbD21E6bdBe7112B8").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("d7eDDB78ED295B3C9629240E8924fb8D8874ddD8").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("8b7F0977Bb4f0fBE7076FA22bC24acA043583F5e").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("e2e2659028143784d557bcec6ff3a0721048880a").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("d9a5179f091d85051d3c982785efd1455cec8699").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("beef32ca5b9a198d27B4e02F4c70439fE60356Cf").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("0000006916a87b82333f4245046623b23794c65c").into(), - GenesisAccount { - balance: U256::from_str("10000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("b21c33de1fab3fa15499c62b59fe0cc3250020d1").into(), - GenesisAccount { - balance: U256::from_str("100000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("10F5d45854e038071485AC9e402308cF80D2d2fE").into(), - GenesisAccount { - balance: U256::from_str("100000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("d7d76c58b3a519e9fA6Cc4D22dC017259BC49F1E").into(), - GenesisAccount { - balance: U256::from_str("100000000000000000000000000").unwrap(), - ..Default::default() - }, - ), - ( - hex!("799D329e5f583419167cD722962485926E338F4a").into(), - GenesisAccount { - balance: U256::from_str("1000000000000000000").unwrap(), - ..Default::default() - }, - ), - ]); + fn test_chain_state_roots() { + let expected_mainnet_state_root = + H256::from(hex!("d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544")); + let calculated_mainnet_state_root = genesis_state_root(&MAINNET.genesis.alloc); + assert_eq!( + expected_mainnet_state_root, calculated_mainnet_state_root, + "mainnet state root mismatch" + ); - let root = genesis_state_root(&alloc); + let expected_goerli_state_root = + H256::from(hex!("5d6cded585e73c4e322c30c2f782a336316f17dd85a4863b9d838d2d4b8b3008")); + let calculated_goerli_state_root = genesis_state_root(&GOERLI.genesis.alloc); + assert_eq!( + expected_goerli_state_root, calculated_goerli_state_root, + "goerli state root mismatch" + ); - assert_eq!(root, expected_root); + let expected_sepolia_state_root = + H256::from(hex!("5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494")); + let calculated_sepolia_state_root = genesis_state_root(&SEPOLIA.genesis.alloc); + assert_eq!( + expected_sepolia_state_root, calculated_sepolia_state_root, + "sepolia state root mismatch" + ); + + let expected_holesky_state_root = + H256::from(hex!("69d8c9d72f6fa4ad42d4702b433707212f90db395eb54dc20bc85de253788783")); + let calculated_holesky_state_root = genesis_state_root(&HOLESKY.genesis.alloc); + assert_eq!( + expected_holesky_state_root, calculated_holesky_state_root, + "holesky state root mismatch" + ); } } diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index 116166d93..10c3b8228 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -295,7 +295,7 @@ where // We assume we can always calculate a storage root without // OOMing. This opens us up to a potential DOS vector if // a contract had too many storage entries and they were - // all buffered w/o us returning and committing our intermeditate + // all buffered w/o us returning and committing our intermediate // progress. // TODO: We can consider introducing the TrieProgress::Progress/Complete // abstraction inside StorageRoot, but let's give it a try as-is for now.