mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(primitives): impl genesis state root helper (#904)
This commit is contained in:
@ -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<u64>,
|
||||
/// 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<Bytes>,
|
||||
/// The account's storage at genesis.
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub storage: Option<HashMap<H256, H256>>,
|
||||
}
|
||||
|
||||
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::<KeccakHasher, _, _, _>(storage_values)
|
||||
})
|
||||
.encode(out);
|
||||
self.code.as_ref().map_or(KECCAK_EMPTY, keccak256).encode(out);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EthersGenesisAccount> 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()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Item = &'a Header> + 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<Address, GenesisAccount>) -> 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::<KeccakHasher, _, _, _>(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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user