@@ -2,18 +2,30 @@ use core::str::FromStr;
22use std:: collections:: BTreeMap ;
33
44use alloy_genesis:: { ChainConfig , Genesis , GenesisAccount } ;
5- use alloy_primitives:: { Address , B256 , U256 } ;
5+ use alloy_primitives:: { address , hex , Address , B256 , U256 } ;
66use alloy_signer_local:: coins_bip39:: English ;
77use alloy_signer_local:: { MnemonicBuilder , PrivateKeySigner } ;
88use chrono:: NaiveDate ;
99use color_eyre:: eyre:: { eyre, Result } ;
1010use hex:: decode;
1111use k256:: ecdsa:: VerifyingKey ;
12+ // Malachite types for Emerald genesis
13+ use malachitebft_eth_types:: secp256k1:: PublicKey as EmeraldPublicKey ;
14+ use malachitebft_eth_types:: {
15+ Genesis as EmeraldGenesis , Validator as EmeraldValidator , ValidatorSet as EmeraldValidatorSet ,
16+ } ;
1217use tracing:: debug;
1318
1419use crate :: validator_manager:: contract:: { ValidatorManager , GENESIS_VALIDATOR_MANAGER_ACCOUNT } ;
1520use crate :: validator_manager:: { generate_storage_data, Validator } ;
1621
22+ /// EIP-4788 Beacon Roots Contract address
23+ const BEACON_ROOTS_ADDRESS : Address = address ! ( "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) ;
24+
25+ /// EIP-4788 Beacon Roots Contract bytecode
26+ /// See: https://eips.ethereum.org/EIPS/eip-4788
27+ const BEACON_ROOTS_CODE : [ u8 ; 97 ] = hex ! ( "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" ) ;
28+
1729/// Test mnemonic for wallet generation
1830const TEST_MNEMONIC : & str = "test test test test test test test test test test test junk" ;
1931
@@ -31,29 +43,63 @@ pub(crate) fn make_signers() -> Vec<PrivateKeySigner> {
3143 ( 0 ..10 ) . map ( make_signer) . collect ( )
3244}
3345
34- pub ( crate ) fn generate_genesis ( public_keys_file : & str , genesis_output_file : & str ) -> Result < ( ) > {
35- // Create signers and get their addresses
36- let signers = make_signers ( ) ;
37- let signer_addresses: Vec < Address > = signers. iter ( ) . map ( |signer| signer. address ( ) ) . collect ( ) ;
38-
39- debug ! ( "Using signer addresses:" ) ;
40- for ( i, ( signer, addr) ) in signers. iter ( ) . zip ( signer_addresses. iter ( ) ) . enumerate ( ) {
41- debug ! (
42- "Signer {i}: {addr} ({})" ,
43- B256 :: from_slice( & signer. credential( ) . to_bytes( ) )
44- ) ;
45- }
46+ pub ( crate ) fn generate_genesis (
47+ public_keys_file : & str ,
48+ poa_address_owner : & Option < String > ,
49+ testnet : & bool ,
50+ testnet_balance : & u64 ,
51+ chain_id : & u64 ,
52+ evm_genesis_output_file : & str ,
53+ emerald_genesis_output_file : & str ,
54+ ) -> Result < ( ) > {
55+ generate_evm_genesis (
56+ public_keys_file,
57+ poa_address_owner,
58+ testnet,
59+ testnet_balance,
60+ chain_id,
61+ evm_genesis_output_file,
62+ ) ?;
63+
64+ generate_emerald_genesis ( public_keys_file, emerald_genesis_output_file) ?;
4665
47- // Create genesis configuration with pre-funded accounts
66+ Ok ( ( ) )
67+ }
68+
69+ pub ( crate ) fn generate_evm_genesis (
70+ public_keys_file : & str ,
71+ poa_address_owner : & Option < String > ,
72+ testnet : & bool ,
73+ testnet_balance : & u64 ,
74+ chain_id : & u64 ,
75+ genesis_output_file : & str ,
76+ ) -> Result < ( ) > {
4877 let mut alloc = BTreeMap :: new ( ) ;
49- for addr in & signer_addresses {
50- alloc. insert (
51- * addr,
52- GenesisAccount {
53- balance : U256 :: from_str ( "15000000000000000000000" ) . unwrap ( ) , // 15000 ETH
54- ..Default :: default ( )
55- } ,
56- ) ;
78+ let signers = make_signers ( ) ;
79+ // If test addresses are requested, create them and pre-fund them
80+ if * testnet {
81+ // Create signers and get their addresses
82+ let signer_addresses: Vec < Address > =
83+ signers. iter ( ) . map ( |signer| signer. address ( ) ) . collect ( ) ;
84+
85+ debug ! ( "Using signer addresses:" ) ;
86+ for ( i, ( signer, addr) ) in signers. iter ( ) . zip ( signer_addresses. iter ( ) ) . enumerate ( ) {
87+ debug ! (
88+ "Signer {i}: {addr} ({})" ,
89+ B256 :: from_slice( & signer. credential( ) . to_bytes( ) )
90+ ) ;
91+ }
92+
93+ let amount = U256 :: from ( * testnet_balance) * U256 :: from ( 10 ) . pow ( U256 :: from ( 18 ) ) ;
94+ for addr in & signer_addresses {
95+ alloc. insert (
96+ * addr,
97+ GenesisAccount {
98+ balance : amount,
99+ ..Default :: default ( )
100+ } ,
101+ ) ;
102+ }
57103 }
58104
59105 let mut initial_validators = Vec :: new ( ) ;
@@ -105,8 +151,17 @@ pub(crate) fn generate_genesis(public_keys_file: &str, genesis_output_file: &str
105151 initial_validators. push ( Validator :: from_public_key ( key, 100 ) ) ;
106152 }
107153
108- let storage = generate_storage_data ( initial_validators, signer_addresses[ 0 ] ) ?;
154+ // Parse PoA owner address or override with first test address
155+ let poa_address_owner = if let Some ( addr_str) = poa_address_owner {
156+ Address :: from_str ( addr_str)
157+ . map_err ( |e| eyre ! ( "invalid PoA owner address '{}': {}" , addr_str, e) ) ?
158+ } else if * testnet {
159+ signers[ 0 ] . address ( )
160+ } else {
161+ unreachable ! ( "unable to determine PoA owner address" ) ;
162+ } ;
109163
164+ let storage = generate_storage_data ( initial_validators, poa_address_owner) ?;
110165 alloc. insert (
111166 GENESIS_VALIDATOR_MANAGER_ACCOUNT ,
112167 GenesisAccount {
@@ -116,6 +171,18 @@ pub(crate) fn generate_genesis(public_keys_file: &str, genesis_output_file: &str
116171 } ,
117172 ) ;
118173
174+ // Deploy EIP-4788 Beacon Roots Contract
175+ // Required for Engine API V3 compliance when parent_beacon_block_root is set
176+ // reth deploys this contract at genesis but only for chain-id 1 so we add it here manually in
177+ // order to support arbitrary chain-ids
178+ alloc. insert (
179+ BEACON_ROOTS_ADDRESS ,
180+ GenesisAccount {
181+ code : Some ( BEACON_ROOTS_CODE . into ( ) ) ,
182+ ..Default :: default ( )
183+ } ,
184+ ) ;
185+
119186 // The Ethereum Prague-Electra (Pectra) upgrade was activated on the mainnet
120187 // on May 7, 2025, at epoch 364,032.
121188 let date = NaiveDate :: from_ymd_opt ( 2025 , 5 , 7 ) . expect ( "Failed to create date for May 7, 2025" ) ;
@@ -127,7 +194,7 @@ pub(crate) fn generate_genesis(public_keys_file: &str, genesis_output_file: &str
127194 // Create genesis configuration
128195 let genesis = Genesis {
129196 config : ChainConfig {
130- chain_id : 1 ,
197+ chain_id : * chain_id ,
131198 homestead_block : Some ( 0 ) ,
132199 eip150_block : Some ( 0 ) ,
133200 eip155_block : Some ( 0 ) ,
@@ -161,3 +228,75 @@ pub(crate) fn generate_genesis(public_keys_file: &str, genesis_output_file: &str
161228
162229 Ok ( ( ) )
163230}
231+
232+ /// Generate Malachite/Emerald genesis file from validator public keys
233+ pub ( crate ) fn generate_emerald_genesis (
234+ public_keys_file : & str ,
235+ emerald_genesis_output_file : & str ,
236+ ) -> Result < ( ) > {
237+ debug ! ( "Generating Emerald genesis file from {public_keys_file}" ) ;
238+
239+ let mut validators = Vec :: new ( ) ;
240+
241+ for ( idx, raw_line) in std:: fs:: read_to_string ( public_keys_file) ?
242+ . lines ( )
243+ . enumerate ( )
244+ {
245+ let line = raw_line. trim ( ) ;
246+ if line. is_empty ( ) {
247+ continue ;
248+ }
249+
250+ // Parse hex-encoded public key (64 bytes without 0x04 prefix)
251+ let hex_str = line. strip_prefix ( "0x" ) . unwrap_or ( line) ;
252+ let bytes = decode ( hex_str) . map_err ( |e| {
253+ eyre ! (
254+ "invalid hex-encoded validator key at line {} in {}: {}" ,
255+ idx + 1 ,
256+ public_keys_file,
257+ e
258+ )
259+ } ) ?;
260+
261+ if bytes. len ( ) != 64 {
262+ return Err ( eyre ! (
263+ "expected 64-byte uncompressed secp256k1 payload (sans 0x04 prefix) at line {} in {}, got {} bytes" ,
264+ idx + 1 ,
265+ public_keys_file,
266+ bytes. len( )
267+ ) ) ;
268+ }
269+
270+ // Convert to uncompressed SEC1 format (65 bytes with 0x04 prefix)
271+ let mut uncompressed = [ 0u8 ; 65 ] ;
272+ uncompressed[ 0 ] = 0x04 ;
273+ uncompressed[ 1 ..] . copy_from_slice ( & bytes) ;
274+
275+ // Validate and create public key
276+ let pub_key = EmeraldPublicKey :: from_sec1_bytes ( & uncompressed) . map_err ( |_| {
277+ eyre ! (
278+ "invalid secp256k1 public key material at line {} in {}" ,
279+ idx + 1 ,
280+ public_keys_file
281+ )
282+ } ) ?;
283+
284+ // Create validator with voting power of 1
285+ validators. push ( EmeraldValidator :: new ( pub_key, 1 ) ) ;
286+ }
287+
288+ if validators. is_empty ( ) {
289+ return Err ( eyre ! ( "no valid validators found in {}" , public_keys_file) ) ;
290+ }
291+
292+ // Create validator set and genesis
293+ let validator_set = EmeraldValidatorSet :: new ( validators) ;
294+ let genesis = EmeraldGenesis { validator_set } ;
295+
296+ // Write emerald genesis to file
297+ let genesis_json = serde_json:: to_string_pretty ( & genesis) ?;
298+ std:: fs:: write ( emerald_genesis_output_file, genesis_json) ?;
299+ debug ! ( "Emerald genesis configuration written to {emerald_genesis_output_file}" ) ;
300+
301+ Ok ( ( ) )
302+ }
0 commit comments