Skip to content

Commit

Permalink
Parse Genesis Value from Memory (#5238)
Browse files Browse the repository at this point in the history
  • Loading branch information
grantkee authored Nov 3, 2023
1 parent 265dd56 commit e6995e5
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 10 deletions.
120 changes: 115 additions & 5 deletions bin/reth/src/args/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ pub fn chain_spec_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Er
})
}

/// Clap value parser for [ChainSpec]s that takes either a built-in genesis format or the path
/// to a custom one.
/// Clap value parser for [ChainSpec]s.
///
/// The value parser matches either a known chain, the path
/// to a json file, or a json formatted string in-memory. The json can be either
/// a serialized [ChainSpec] or Genesis struct.
pub fn genesis_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error> {
Ok(match s {
"mainnet" => MAINNET.clone(),
Expand All @@ -44,8 +47,21 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error
"holesky" => HOLESKY.clone(),
"dev" => DEV.clone(),
_ => {
let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
let genesis: AllGenesisFormats = serde_json::from_str(&raw)?;
// both serialized Genesis and ChainSpec structs supported
let genesis: AllGenesisFormats =
// try to read json from path first
match fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned())) {
Ok(raw) => serde_json::from_str(&raw)?,
Err(io_err) => {
// valid json may start with "\n", but must contain "{"
if s.contains('{') {
serde_json::from_str(s)?
} else {
return Err(io_err.into()) // assume invalid path
}
}
};

Arc::new(genesis.into())
}
})
Expand Down Expand Up @@ -108,16 +124,110 @@ pub fn parse_socket_address(value: &str) -> eyre::Result<SocketAddr, SocketAddre
mod tests {
use super::*;
use proptest::prelude::Rng;
use reth_primitives::{
hex, Address, ChainConfig, ChainSpecBuilder, Genesis, GenesisAccount, U256,
};
use secp256k1::rand::thread_rng;
use std::collections::HashMap;

#[test]
fn parse_chain_spec() {
fn parse_known_chain_spec() {
for chain in ["mainnet", "sepolia", "goerli", "holesky"] {
chain_spec_value_parser(chain).unwrap();
genesis_value_parser(chain).unwrap();
}
}

#[test]
fn parse_chain_spec_from_memory() {
let custom_genesis_from_json = r#"
{
"nonce": "0x0",
"timestamp": "0x653FEE9E",
"gasLimit": "0x1388",
"difficulty": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b": {
"balance": "0x21"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"config": {
"chainId": 2600,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"shanghaiTime": 0
}
}
"#;

let chain_from_json = genesis_value_parser(custom_genesis_from_json).unwrap();

// using structs
let config = ChainConfig {
chain_id: 2600,
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),
berlin_block: Some(0),
london_block: Some(0),
shanghai_time: Some(0),
terminal_total_difficulty: Some(U256::ZERO),
terminal_total_difficulty_passed: true,
..Default::default()
};
let genesis = Genesis {
config,
nonce: 0,
timestamp: 1698688670,
gas_limit: 5000,
difficulty: U256::ZERO,
mix_hash: B256::ZERO,
coinbase: Address::ZERO,
..Default::default()
};

// seed accounts after genesis struct created
let address = hex!("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").into();
let account = GenesisAccount::default().with_balance(U256::from(33));
let genesis = genesis.extend_accounts(HashMap::from([(address, account)]));

let custom_genesis_from_struct = serde_json::to_string(&genesis).unwrap();
let chain_from_struct = genesis_value_parser(&custom_genesis_from_struct).unwrap();
assert_eq!(chain_from_json.genesis(), chain_from_struct.genesis());

// chain spec
let chain_spec = ChainSpecBuilder::default()
.chain(2600.into())
.genesis(genesis)
.cancun_activated()
.build();

let chain_spec_json = serde_json::to_string(&chain_spec).unwrap();
let custom_genesis_from_spec = genesis_value_parser(&chain_spec_json).unwrap();

assert_eq!(custom_genesis_from_spec.chain(), chain_from_struct.chain());
}

#[test]
fn parse_socket_addresses() {
for value in ["localhost:9000", ":9000", "9000"] {
Expand Down
68 changes: 64 additions & 4 deletions crates/primitives/src/chain/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,10 +708,10 @@ impl ForkTimestamps {
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum AllGenesisFormats {
/// The geth genesis format
Geth(Genesis),
/// The reth genesis format
Reth(ChainSpec),
/// The geth genesis format
Geth(Genesis),
}

impl From<Genesis> for AllGenesisFormats {
Expand Down Expand Up @@ -1189,10 +1189,13 @@ impl DepositContract {
#[cfg(test)]
mod tests {
use super::*;
use crate::{b256, hex, NamedChain, B256, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, U256};
use crate::{
b256, hex, ChainConfig, GenesisAccount, NamedChain, B256, DEV, GOERLI, HOLESKY, MAINNET,
SEPOLIA, U256,
};
use alloy_rlp::Encodable;
use bytes::BytesMut;
use std::str::FromStr;
use std::{collections::HashMap, str::FromStr};

fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
for (block, expected_id) in cases {
Expand Down Expand Up @@ -2293,4 +2296,61 @@ Post-merge hard forks (timestamp based):
.fork(Hardfork::Paris)
.active_at_ttd(HOLESKY.genesis.difficulty, HOLESKY.genesis.difficulty));
}

#[test]
fn test_all_genesis_formats_deserialization() {
// custom genesis with chain config
let config = ChainConfig {
chain_id: 2600,
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),
berlin_block: Some(0),
london_block: Some(0),
shanghai_time: Some(0),
terminal_total_difficulty: Some(U256::ZERO),
terminal_total_difficulty_passed: true,
..Default::default()
};
// genesis
let genesis = Genesis {
config,
nonce: 0,
timestamp: 1698688670,
gas_limit: 5000,
difficulty: U256::ZERO,
mix_hash: B256::ZERO,
coinbase: Address::ZERO,
..Default::default()
};

// seed accounts after genesis struct created
let address = hex!("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").into();
let account = GenesisAccount::default().with_balance(U256::from(33));
let genesis = genesis.extend_accounts(HashMap::from([(address, account)]));

// ensure genesis is deserialized correctly
let serialized_genesis = serde_json::to_string(&genesis).unwrap();
let deserialized_genesis: AllGenesisFormats =
serde_json::from_str(&serialized_genesis).unwrap();
assert!(matches!(deserialized_genesis, AllGenesisFormats::Geth(_)));

// build chain
let chain_spec = ChainSpecBuilder::default()
.chain(2600.into())
.genesis(genesis)
.cancun_activated()
.build();

// ensure chain spec is deserialized correctly
let serialized_chain_spec = serde_json::to_string(&chain_spec).unwrap();
let deserialized_chain_spec: AllGenesisFormats =
serde_json::from_str(&serialized_chain_spec).unwrap();
assert!(matches!(deserialized_chain_spec, AllGenesisFormats::Reth(_)))
}
}
2 changes: 1 addition & 1 deletion crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub use constants::{
#[cfg(feature = "c-kzg")]
pub use eip4844::{calculate_excess_blob_gas, kzg_to_versioned_hash};
pub use forkid::{ForkFilter, ForkHash, ForkId, ForkTransition, ValidationError};
pub use genesis::{Genesis, GenesisAccount};
pub use genesis::{ChainConfig, Genesis, GenesisAccount};
pub use hardfork::Hardfork;
pub use header::{Head, Header, HeadersDirection, SealedHeader};
pub use integer_list::IntegerList;
Expand Down

0 comments on commit e6995e5

Please sign in to comment.