-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for Ethereum genesis.json initialization #2034
base: main
Are you sure you want to change the base?
Changes from 2 commits
de94fbd
604f0b3
d50c631
65a1fb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"config": { | ||
"chainId": 2008, | ||
"homesteadBlock": 0, | ||
"eip150Block": 0, | ||
"eip155Block": 0, | ||
"eip158Block": 0, | ||
"byzantiumBlock": 0, | ||
"constantinopleBlock": 0, | ||
"petersburgBlock": 0, | ||
"istanbulBlock": 0, | ||
"berlinBlock": 0, | ||
"londonBlock": 0, | ||
"shanghaiTime": 0, | ||
"cancunTime": 0 | ||
}, | ||
"nonce": "0x0000000000000042", | ||
"timestamp": "0x0", | ||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"gasLimit": "0xffffffff", | ||
"difficulty": "0x1", | ||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"coinbase": "0x0000000000000000000000000000000000000000", | ||
"alloc": { | ||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { | ||
"balance": "0xffffffffffffffffffffffffffffffff", | ||
"nonce": "0x1" | ||
}, | ||
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { | ||
"balance": "0xffffffffffffffffffffffffffffffff" | ||
}, | ||
"0x1000000000000000000000000000000000000000": { | ||
"balance": "0x0", | ||
"code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de59181e14ace3a8bd1f2b775b9f7a3764736f6c63430008090033", | ||
"storage": { | ||
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000000007b" | ||
} | ||
} | ||
}, | ||
"number": "0x0", | ||
"gasUsed": "0x0", | ||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
"baseFeePerGas": "0x0" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
use std::collections::BTreeMap; | ||
use std::fs::File; | ||
use std::io::BufReader; | ||
use std::path::Path; | ||
use std::path::PathBuf; | ||
|
||
use alloy_primitives::hex; | ||
use alloy_primitives::U256 as AlloyU256; | ||
use anyhow::anyhow; | ||
use anyhow::Result; | ||
use ethereum_types::U256 as EthereumU256; | ||
use serde::Deserialize; | ||
use serde::Serialize; | ||
|
||
use crate::eth::primitives::Account; | ||
use crate::eth::primitives::Address; | ||
use crate::eth::primitives::Nonce; | ||
use crate::eth::primitives::Wei; | ||
|
||
/// Represents the configuration of an Ethereum genesis.json file | ||
#[allow(non_snake_case)] | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct GenesisConfig { | ||
pub config: ChainConfig, | ||
pub nonce: String, | ||
pub timestamp: String, | ||
pub extraData: String, | ||
pub gasLimit: String, | ||
pub difficulty: String, | ||
pub mixHash: String, | ||
pub coinbase: String, | ||
pub alloc: BTreeMap<String, GenesisAccount>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub number: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub gasUsed: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub parentHash: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub baseFeePerGas: Option<String>, | ||
} | ||
|
||
/// Chain configuration in genesis.json | ||
#[allow(non_snake_case)] | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct ChainConfig { | ||
pub chainId: u64, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub homesteadBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub eip150Block: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub eip155Block: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub eip158Block: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub byzantiumBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub constantinopleBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub petersburgBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub istanbulBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub berlinBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub londonBlock: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub shanghaiTime: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub cancunTime: Option<u64>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub pragueTime: Option<u64>, | ||
} | ||
|
||
/// Account in the genesis.json file | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct GenesisAccount { | ||
pub balance: String, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub code: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub nonce: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub storage: Option<BTreeMap<String, String>>, | ||
} | ||
|
||
impl GenesisConfig { | ||
/// Loads a genesis.json file from the specified path | ||
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> { | ||
let file = File::open(path)?; | ||
let reader = BufReader::new(file); | ||
let config: GenesisConfig = serde_json::from_reader(reader)?; | ||
Ok(config) | ||
} | ||
|
||
/// Searches for a genesis.json file in the current directory or a specified path | ||
pub fn find_genesis_file(custom_path: Option<&str>) -> Option<PathBuf> { | ||
if let Some(path) = custom_path { | ||
let path_buf = PathBuf::from(path); | ||
if path_buf.exists() { | ||
return Some(path_buf); | ||
} | ||
} | ||
|
||
// Check for environment-specific genesis file | ||
if let Ok(env) = std::env::var("ENV") { | ||
let env_path = format!("config/genesis.{}.json", env.to_lowercase()); | ||
let env_path_buf = PathBuf::from(&env_path); | ||
if env_path_buf.exists() { | ||
return Some(env_path_buf); | ||
} | ||
} | ||
|
||
// Check for default local genesis file | ||
let local_path = PathBuf::from("config/genesis.local.json"); | ||
if local_path.exists() { | ||
return Some(local_path); | ||
} | ||
|
||
// Fallback to current directory | ||
let default_path = PathBuf::from("genesis.json"); | ||
if default_path.exists() { | ||
return Some(default_path); | ||
} | ||
|
||
None | ||
} | ||
|
||
/// Converts accounts from genesis.json to Stratus internal format | ||
pub fn to_stratus_accounts(&self) -> Result<Vec<Account>> { | ||
let mut accounts = Vec::new(); | ||
|
||
for (addr_str, genesis_account) in &self.alloc { | ||
// Remove 0x prefix if present | ||
let addr_str = addr_str.trim_start_matches("0x"); | ||
|
||
let address = Address::from_str_hex(addr_str)?; | ||
|
||
// Create the account | ||
let mut account = Account::new_empty(address); | ||
|
||
// Convert balance | ||
let balance_str = genesis_account.balance.trim_start_matches("0x"); | ||
|
||
let balance = if genesis_account.balance.starts_with("0x") { | ||
// For hex strings | ||
Wei::from_str_hex(balance_str)? | ||
} else { | ||
// For decimal strings | ||
let value = balance_str.parse::<u64>().unwrap_or(0); | ||
Wei::from(EthereumU256::from(value)) | ||
}; | ||
|
||
account.balance = balance; | ||
|
||
// Add nonce if it exists | ||
if let Some(nonce) = &genesis_account.nonce { | ||
let nonce_str = nonce.trim_start_matches("0x"); | ||
let nonce_value = if nonce.starts_with("0x") { | ||
u64::from_str_radix(nonce_str, 16).unwrap_or(0) | ||
} else { | ||
nonce_str.parse::<u64>().unwrap_or(0) | ||
}; | ||
account.nonce = Nonce::from(nonce_value); | ||
} | ||
|
||
// Add code if it exists | ||
if let Some(code) = &genesis_account.code { | ||
let code_str = code.trim_start_matches("0x"); | ||
if let Ok(code_bytes) = hex::decode(code_str) { | ||
if !code_bytes.is_empty() { | ||
account.bytecode = Some(revm::primitives::Bytecode::new_raw(code_bytes.into())); | ||
} | ||
} | ||
} | ||
|
||
accounts.push(account); | ||
} | ||
|
||
Ok(accounts) | ||
} | ||
} | ||
|
||
// Implementation of hex string conversion to internal types | ||
trait FromStrHex: Sized { | ||
fn from_str_hex(hex: &str) -> Result<Self>; | ||
} | ||
|
||
impl FromStrHex for Address { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implement traits for primitive types in its declaration file ( |
||
fn from_str_hex(hex: &str) -> Result<Self> { | ||
// Ensure the hex string has an even number of digits | ||
let hex = if hex.len() % 2 == 1 { | ||
format!("0{}", hex) // Add a leading zero if needed | ||
} else { | ||
hex.to_string() | ||
}; | ||
|
||
if hex.len() != 40 { | ||
return Err(anyhow!("Invalid address length: {}", hex.len())); | ||
} | ||
|
||
let bytes = match hex::decode(&hex) { | ||
Ok(b) => b, | ||
Err(e) => return Err(anyhow!("Failed to decode address: {}", e)), | ||
}; | ||
|
||
if bytes.len() != 20 { | ||
return Err(anyhow!("Unexpected bytes length: {}", bytes.len())); | ||
} | ||
|
||
let mut array = [0u8; 20]; | ||
array.copy_from_slice(&bytes); | ||
Ok(Address::new(array)) | ||
} | ||
} | ||
|
||
impl FromStrHex for Wei { | ||
fn from_str_hex(hex: &str) -> Result<Self> { | ||
// Ensure the hex string has an even number of digits | ||
let hex = if hex.len() % 2 == 1 { | ||
format!("0{}", hex) // Add a leading zero if needed | ||
} else { | ||
hex.to_string() | ||
}; | ||
|
||
// Decode the hex string | ||
let bytes = match hex::decode(&hex) { | ||
Ok(b) => b, | ||
Err(e) => return Err(anyhow!("Failed to decode hex string: {}", e)), | ||
}; | ||
|
||
let mut array = [0u8; 32]; | ||
let start = array.len() - bytes.len(); | ||
array[start..].copy_from_slice(&bytes); | ||
let ethereum_u256 = EthereumU256::from_big_endian(&array); | ||
Ok(Wei::from(ethereum_u256)) | ||
} | ||
} | ||
Comment on lines
+300
to
+321
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be added in this other file alongside its other conversions. |
||
|
||
impl FromStrHex for AlloyU256 { | ||
fn from_str_hex(hex: &str) -> Result<Self> { | ||
let bytes = hex::decode(hex)?; | ||
let mut array = [0u8; 32]; | ||
let start = array.len() - bytes.len(); | ||
array[start..].copy_from_slice(&bytes); | ||
Ok(AlloyU256::from_be_bytes(array)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_load_genesis_and_convert_accounts() { | ||
use std::fs::File; | ||
use std::io::Write; | ||
|
||
// Criar um arquivo temporário sem usar tempfile | ||
let file_path = "test_genesis.json"; | ||
|
||
// Create a minimal genesis.json file | ||
let json = r#" | ||
{ | ||
"config": { | ||
"chainId": 2008 | ||
}, | ||
"nonce": "0x0", | ||
"timestamp": "0x0", | ||
"extraData": "0x0", | ||
"gasLimit": "0x0", | ||
"difficulty": "0x0", | ||
"mixHash": "0x0", | ||
"coinbase": "0x0000000000000000000000000000000000000000", | ||
"alloc": { | ||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { | ||
"balance": "0x1000", | ||
"nonce": "0x1" | ||
} | ||
} | ||
}"#; | ||
|
||
// Write the JSON to the file | ||
let mut file = File::create(file_path).unwrap(); | ||
file.write_all(json.as_bytes()).unwrap(); | ||
|
||
// Load the genesis config from the file | ||
let genesis = GenesisConfig::load_from_file(file_path).unwrap(); | ||
|
||
// Test address conversion | ||
let addr_str = "f39fd6e51aad88f6f4ce6ab8827279cfffb92266"; | ||
let addr = Address::from_str_hex(addr_str).unwrap(); | ||
|
||
// Convert accounts | ||
let accounts = genesis.to_stratus_accounts().unwrap(); | ||
|
||
// Verify the account | ||
assert_eq!(accounts.len(), 1); | ||
let account = &accounts[0]; | ||
assert_eq!(account.address, addr); | ||
assert_eq!(account.nonce, Nonce::from(1u64)); | ||
assert_eq!(account.balance, Wei::from(EthereumU256::from(4096))); // 0x1000 in decimal | ||
|
||
// Clean up | ||
std::fs::remove_file(file_path).unwrap(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should accept an
ENV VAR
for this genesis.json path. Something likeGENESIS_JSON_PATH
. And not useENV
related namesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define this type of configuration in
src/config.rs
(using clap)