fix(core): properly parse genesis alloc storage (#2205)

* fix(core): properly parse genesis alloc storage

 * Previously we were not properly parsing the storage field of
   GenesisAlloc. The first issue was in using serde(flatten), which is
   just incorrect because storage is not encoded flattened. The second
   issue was in the parsing of H256 keys and values. Some of the storage
   values in hive genesis examples were encoded in less than 64 hex
   characters, such as the string `0x12`. This would not succeed normal
   H256 parsing.
 * Introduce from_unformatted_hex_map to properly deserialize the
   storage map.
 * Modify existing genesis parsing tests to check parsed storage and
   code values against expected values.
 * Introduce new genesis parsing test from the hive smoke tests,
   checking full struct equality.

* remove unused from_unformatted hex

* make clippy happy
This commit is contained in:
Dan Cline 2023-02-27 17:22:01 -05:00 committed by GitHub
parent 319b86a643
commit 537d0a9deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 258 additions and 9 deletions

View File

@ -2,13 +2,13 @@ use std::collections::HashMap;
use crate::{ use crate::{
types::{Address, Bytes, H256, U256, U64}, types::{Address, Bytes, H256, U256, U64},
utils::{from_int_or_hex, from_int_or_hex_opt, from_u64_or_hex_opt}, utils::{from_int_or_hex, from_int_or_hex_opt, from_u64_or_hex_opt, from_unformatted_hex_map},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// This represents the chain configuration, specifying the genesis block, header fields, and hard /// This represents the chain configuration, specifying the genesis block, header fields, and hard
/// fork switch blocks. /// fork switch blocks.
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Genesis { pub struct Genesis {
/// The fork configuration for this network. /// The fork configuration for this network.
@ -133,7 +133,11 @@ pub struct GenesisAccount {
pub balance: U256, pub balance: U256,
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub code: Option<Bytes>, pub code: Option<Bytes>,
#[serde(flatten, skip_serializing_if = "Option::is_none", default)] #[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "from_unformatted_hex_map",
default
)]
pub storage: Option<HashMap<H256, H256>>, pub storage: Option<HashMap<H256, H256>>,
} }
@ -142,7 +146,7 @@ pub struct GenesisAccount {
/// See [geth's `ChainConfig` /// See [geth's `ChainConfig`
/// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349) /// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349)
/// for the source of each field. /// for the source of each field.
#[derive(Clone, Debug, Deserialize, Serialize, Default)] #[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
pub struct ChainConfig { pub struct ChainConfig {
/// The network's chain ID. /// The network's chain ID.
@ -248,11 +252,11 @@ const fn one() -> u64 {
} }
/// Empty consensus configuration for proof-of-work networks. /// Empty consensus configuration for proof-of-work networks.
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct EthashConfig {} pub struct EthashConfig {}
/// Consensus configuration for Clique. /// Consensus configuration for Clique.
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct CliqueConfig { pub struct CliqueConfig {
/// Number of seconds between blocks to enforce. /// Number of seconds between blocks to enforce.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@ -265,7 +269,12 @@ pub struct CliqueConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Genesis, H256}; use super::{ChainConfig, Genesis, GenesisAccount, H256};
use crate::{
types::{Address, Bytes, H160, U256},
utils::EthashConfig,
};
use std::{collections::HashMap, str::FromStr};
#[test] #[test]
fn parse_hive_genesis() { fn parse_hive_genesis() {
@ -611,6 +620,204 @@ mod tests {
} }
"#; "#;
let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
let alloc_entry = genesis
.alloc
.get(&H160::from_str("0000000000000000000000000000000000000314").unwrap())
.expect("missing account for parsed genesis");
let storage = alloc_entry.storage.as_ref().expect("missing storage for parsed genesis");
let expected_storage = HashMap::from_iter(vec![
(
H256::from_str(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap(),
H256::from_str(
"0x0000000000000000000000000000000000000000000000000000000000001234",
)
.unwrap(),
),
(
H256::from_str(
"0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9",
)
.unwrap(),
H256::from_str(
"0x0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap(),
),
]);
assert_eq!(storage, &expected_storage);
let expected_code = Bytes::from_str("0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029").unwrap();
let code = alloc_entry.code.as_ref().expect("missing code for parsed genesis");
assert_eq!(code, &expected_code);
}
#[test]
fn test_hive_smoke_alloc_deserialize() {
let hive_genesis = r#"
{
"nonce": "0x0000000000000042",
"difficulty": "0x2123456",
"mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
"coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"timestamp": "0x123456",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0xfafbfcfd",
"gasLimit": "0x2fefd8",
"alloc": {
"dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
},
"e6716f9544a56c530d868e4bfbacb172315bdead": {
"balance": "0x11",
"code": "0x12"
},
"b9c015918bdaba24b4ff057a92a3873d6eb201be": {
"balance": "0x21",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
}
},
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
"balance": "0x31",
"nonce": "0x32"
},
"0000000000000000000000000000000000000001": {
"balance": "0x41"
},
"0000000000000000000000000000000000000002": {
"balance": "0x51"
},
"0000000000000000000000000000000000000003": {
"balance": "0x61"
},
"0000000000000000000000000000000000000004": {
"balance": "0x71"
}
},
"config": {
"ethash": {},
"chainId": 10,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0
}
}
"#;
let expected_genesis = Genesis {
nonce: 0x0000000000000042.into(),
difficulty: 0x2123456.into(),
mix_hash: H256::from_str("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234").unwrap(),
coinbase: Address::from_str("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(),
timestamp: 0x123456.into(),
parent_hash: Some(H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap()),
extra_data: Bytes::from_str("0xfafbfcfd").unwrap(),
gas_limit: 0x2fefd8.into(),
alloc: HashMap::from_iter(vec![
(
Address::from_str("0xdbdbdb2cbd23b783741e8d7fcf51e459b497e4a6").unwrap(),
GenesisAccount {
balance: U256::from_str("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0xe6716f9544a56c530d868e4bfbacb172315bdead").unwrap(),
GenesisAccount {
balance: U256::from_str("0x11").unwrap(),
nonce: None,
code: Some(Bytes::from_str("0x12").unwrap()),
storage: None,
},
),
(
Address::from_str("0xb9c015918bdaba24b4ff057a92a3873d6eb201be").unwrap(),
GenesisAccount {
balance: U256::from_str("0x21").unwrap(),
nonce: None,
code: None,
storage: Some(HashMap::from_iter(vec![
(
H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001").unwrap(),
H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000022").unwrap(),
),
])),
},
),
(
Address::from_str("0x1a26338f0d905e295fccb71fa9ea849ffa12aaf4").unwrap(),
GenesisAccount {
balance: U256::from_str("0x31").unwrap(),
nonce: Some(0x32u64),
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000001").unwrap(),
GenesisAccount {
balance: U256::from_str("0x41").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000002").unwrap(),
GenesisAccount {
balance: U256::from_str("0x51").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000003").unwrap(),
GenesisAccount {
balance: U256::from_str("0x61").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000004").unwrap(),
GenesisAccount {
balance: U256::from_str("0x71").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
]),
config: ChainConfig {
ethash: Some(EthashConfig{}),
chain_id: 10,
homestead_block: Some(0),
eip150_block: Some(0),
eip155_block: Some(0),
eip158_block: Some(0),
byzantium_block: Some(0),
constantinople_block: Some(0),
petersburg_block: Some(0),
istanbul_block: Some(0),
..Default::default()
},
..Default::default()
};
let deserialized_genesis: Genesis = serde_json::from_str(hive_genesis).unwrap();
assert_eq!(deserialized_genesis, expected_genesis, "deserialized genesis {deserialized_genesis:#?} does not match expected {expected_genesis:#?}");
} }
} }

View File

@ -36,11 +36,12 @@ pub use rlp;
/// Re-export hex /// Re-export hex
pub use hex; pub use hex;
use crate::types::{Address, ParseI256Error, I256, U256, U64}; use crate::types::{Address, Bytes, ParseI256Error, H256, I256, U256, U64};
use elliptic_curve::sec1::ToEncodedPoint; use elliptic_curve::sec1::ToEncodedPoint;
use ethabi::ethereum_types::FromDecStrErr; use ethabi::ethereum_types::FromDecStrErr;
use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey}; use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey};
use std::{ use std::{
collections::HashMap,
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt, fmt,
str::FromStr, str::FromStr,
@ -468,6 +469,47 @@ pub fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec<Vec<U256>>
(max_fee_per_gas, max_priority_fee_per_gas) (max_fee_per_gas, max_priority_fee_per_gas)
} }
/// Converts a Bytes value into a H256, accepting inputs that are less than 32 bytes long. These
/// inputs will be left padded with zeros.
pub fn from_bytes_to_h256<'de, D>(bytes: Bytes) -> Result<H256, D::Error>
where
D: Deserializer<'de>,
{
if bytes.0.len() > 32 {
return Err(serde::de::Error::custom("input too long to be a H256"))
}
// left pad with zeros to 32 bytes
let mut padded = [0u8; 32];
padded[32 - bytes.0.len()..].copy_from_slice(&bytes.0);
// then convert to H256 without a panic
Ok(H256::from_slice(&padded))
}
/// Deserializes the input into an Option<HashMap<H256, H256>>, using from_unformatted_hex to
/// deserialize the keys and values.
pub fn from_unformatted_hex_map<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<H256, H256>>, D::Error>
where
D: Deserializer<'de>,
{
let map = Option::<HashMap<Bytes, Bytes>>::deserialize(deserializer)?;
match map {
Some(mut map) => {
let mut res_map = HashMap::new();
for (k, v) in map.drain() {
let k_deserialized = from_bytes_to_h256::<'de, D>(k)?;
let v_deserialized = from_bytes_to_h256::<'de, D>(v)?;
res_map.insert(k_deserialized, v_deserialized);
}
Ok(Some(res_map))
}
None => Ok(None),
}
}
/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with /// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with
/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). /// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number).
pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result<U256, D::Error> pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result<U256, D::Error>