From e1aae3e120196f14b12203f9e8aa75ab02eac9ec Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 17 Jan 2023 19:15:42 -0500 Subject: [PATCH] feat(primitives): impl genesis state root helper (#904) --- crates/primitives/src/genesis.rs | 75 ++++++++++++- crates/primitives/src/proofs.rs | 185 ++++++++++++++++++++++++++++++- 2 files changed, 252 insertions(+), 8 deletions(-) diff --git a/crates/primitives/src/genesis.rs b/crates/primitives/src/genesis.rs index b6309e28c..3453a9d2d 100644 --- a/crates/primitives/src/genesis.rs +++ b/crates/primitives/src/genesis.rs @@ -1,10 +1,16 @@ use std::collections::HashMap; -use serde::{Deserialize, Serialize}; - use crate::{ - utils::serde_helpers::deserialize_stringified_u64, Address, Bytes, Header, H256, U256, + keccak256, + proofs::{KeccakHasher, EMPTY_ROOT}, + utils::serde_helpers::deserialize_stringified_u64, + Address, Bytes, Header, H256, KECCAK_EMPTY, U256, }; +use bytes::BytesMut; +use ethers_core::utils::GenesisAccount as EthersGenesisAccount; +use reth_rlp::{length_of_length, Encodable, Header as RlpHeader}; +use serde::{Deserialize, Serialize}; +use triehash::sec_trie_root; /// The genesis block specification. #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -57,4 +63,67 @@ pub struct GenesisAccount { pub nonce: Option, /// The balance of the account at genesis. pub balance: U256, + /// The account's bytecode at genesis. + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + /// The account's storage at genesis. + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub storage: Option>, +} + +impl GenesisAccount { + /// Determines the RLP payload length, without the RLP header. + fn payload_len(&self) -> usize { + let mut len = 0; + len += self.nonce.unwrap_or_default().length(); + len += self.balance.length(); + // rather than rlp-encoding the storage, we just return the length of a single hash + // hashes are a fixed size, so it is safe to use the empty root for this + len += EMPTY_ROOT.length(); + len += self.code.as_ref().map_or(KECCAK_EMPTY, keccak256).length(); + len + } +} + +impl Encodable for GenesisAccount { + fn length(&self) -> usize { + let len = self.payload_len(); + // RLP header length + payload length + len + length_of_length(len) + } + + fn encode(&self, out: &mut dyn bytes::BufMut) { + let header = RlpHeader { list: true, payload_length: self.payload_len() }; + header.encode(out); + + self.nonce.unwrap_or_default().encode(out); + self.balance.encode(out); + self.storage + .as_ref() + .map_or(EMPTY_ROOT, |storage| { + let storage_values = + storage.iter().filter(|(_k, &v)| v != KECCAK_EMPTY).map(|(&k, v)| { + let mut value_rlp = BytesMut::new(); + v.encode(&mut value_rlp); + (k, value_rlp.freeze()) + }); + + sec_trie_root::(storage_values) + }) + .encode(out); + self.code.as_ref().map_or(KECCAK_EMPTY, keccak256).encode(out); + } +} + +impl From for GenesisAccount { + fn from(genesis_account: EthersGenesisAccount) -> Self { + Self { + balance: genesis_account.balance.into(), + nonce: genesis_account.nonce, + code: genesis_account.code.as_ref().map(|code| code.0.clone().into()), + storage: genesis_account.storage.as_ref().map(|storage| { + storage.clone().into_iter().map(|(k, v)| (k.0.into(), v.0.into())).collect() + }), + } + } } diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index e83994724..969c54d08 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -1,8 +1,14 @@ -use crate::{keccak256, Header, Log, Receipt, TransactionSigned, H256}; +use std::collections::HashMap; + +use crate::{ + keccak256, Address, Bytes, GenesisAccount, Header, Log, Receipt, TransactionSigned, H256, +}; +use bytes::BytesMut; use hash_db::Hasher; use hex_literal::hex; use plain_hasher::PlainHasher; -use triehash::ordered_trie_root; +use reth_rlp::Encodable; +use triehash::{ordered_trie_root, sec_trie_root}; /// Keccak-256 hash of the RLP of an empty list, KEC("\xc0"). pub const EMPTY_LIST_HASH: H256 = @@ -14,7 +20,7 @@ pub const EMPTY_ROOT: H256 = /// A [Hasher] that calculates a keccak256 hash of the given data. #[derive(Default, Debug, Clone, PartialEq, Eq)] -struct KeccakHasher; +pub(crate) struct KeccakHasher; impl Hasher for KeccakHasher { type Out = H256; @@ -66,16 +72,32 @@ pub fn calculate_ommers_root<'a>(ommers: impl Iterator + Clon keccak256(ommers_rlp) } +/// 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())) + }); + + H256(sec_trie_root::(encoded_accounts).0) +} + #[cfg(test)] mod tests { + use std::{collections::HashMap, str::FromStr}; + use crate::{ hex_literal::hex, - proofs::{calculate_receipt_root, calculate_transaction_root}, - Block, Bloom, Log, Receipt, TxType, H160, H256, + proofs::{calculate_receipt_root, calculate_transaction_root, genesis_state_root}, + Address, Block, Bloom, GenesisAccount, Log, Receipt, TxType, H160, H256, U256, }; use reth_rlp::Decodable; + use super::EMPTY_ROOT; + #[test] fn check_transaction_root() { let data = &hex!("f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0"); @@ -104,4 +126,157 @@ mod tests { H256(hex!("fe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")) ); } + + #[test] + fn check_empty_state_root() { + let genesis_alloc = HashMap::new(); + let root = genesis_state_root(genesis_alloc); + assert_eq!(root, EMPTY_ROOT); + } + + #[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, H256)> = 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 { nonce: None, balance: U256::MAX, code: None, storage: None }, + ); + let root = genesis_state_root(genesis_alloc); + + assert_eq!(root, expected_root); + } + } + + #[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() + }, + ), + ]); + + let root = genesis_state_root(alloc); + + assert_eq!(root, expected_root); + } }