Skip to content

Commit 91f27bc

Browse files
srypsrnbguyljoss17Rano | Ranadeepjmalicevic
authored
improve genesis command for external use cases (#84)
* improve genesis command for external use cases * conditional fix * generate emerald genesis file * fix lint * fix spammer chain_id * fix: eth address derivation (#85) * fix eth address in genesis * fix lint again * format toml * nits * exclude solidity libraries * mv foundry lockfile --------- Co-authored-by: Ranadeep Biswas <mail@rnbguy.at> * update init cmd to generate default emerald config (#56) * update init cmd to generate default emerald config * Remove config file dependency from init command * Fix comment * Update cli/src/cmd/init.rs Co-authored-by: Rano | Ranadeep <ranadeep@informal.systems> --------- Co-authored-by: Luca Joss <luca@informal.systems> Co-authored-by: Rano | Ranadeep <ranadeep@informal.systems> * fix(app): Properly handle genesis start height (#90) * Properly handle genesis start height * removed unused import * logging fixes * merge main * merge main * merge main * clippy fix * Revert "fix(app): Properly handle genesis start height (#90)" This reverts commit 4ae8516. * huh * fix clippy * fix forge * once more * change requests * fix block production test * Remove volumes based on pattern * Revert "Remove volumes based on pattern" This reverts commit 94f6e0c. --------- Co-authored-by: Ranadeep Biswas <mail@rnbguy.at> Co-authored-by: Luca Joss <luca@informal.systems> Co-authored-by: Rano | Ranadeep <ranadeep@informal.systems> Co-authored-by: Jasmina Malicevic <jasmina.dustinac@gmail.com>
1 parent 39f6849 commit 91f27bc

7 files changed

Lines changed: 287 additions & 60 deletions

File tree

.github/workflows/emerald.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,10 @@ jobs:
4848
- name: Run emerald
4949
run: |
5050
cargo build
51-
# `make all` equivalent but with --no-wait
5251
./scripts/generate_testnet_config.sh --nodes 3 --testnet-config-dir .testnet
5352
cargo run --bin emerald -- testnet --home nodes --testnet-config .testnet/testnet_config.toml
5453
ls nodes/*/config/priv_validator_key.json | xargs -I{} cargo run --bin emerald show-pubkey {} > nodes/validator_public_keys.txt
55-
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt
54+
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt --devnet
5655
docker compose up -d
5756
./scripts/add_peers.sh --nodes 3
5857
bash scripts/spawn.bash --nodes 3 --home nodes --no-delay --no-wait

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ all: clean build
22
./scripts/generate_testnet_config.sh --nodes 3 --testnet-config-dir .testnet
33
cargo run --bin emerald -- testnet --home nodes --testnet-config .testnet/testnet_config.toml --log-level info
44
ls nodes/*/config/priv_validator_key.json | xargs -I{} cargo run --bin emerald show-pubkey {} > nodes/validator_public_keys.txt
5-
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt
5+
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt --devnet
66
docker compose up -d reth0 reth1 reth2 prometheus grafana otterscan
77
./scripts/add_peers.sh --nodes 3
88
@echo 👉 Grafana dashboard is available at http://localhost:3000
@@ -12,7 +12,7 @@ four: clean build
1212
./scripts/generate_testnet_config.sh --nodes 4 --testnet-config-dir .testnet
1313
cargo run --bin emerald -- testnet --home nodes --testnet-config .testnet/testnet_config.toml
1414
ls nodes/*/config/priv_validator_key.json | xargs -I{} cargo run --bin emerald show-pubkey {} > nodes/validator_public_keys.txt
15-
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt
15+
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt --devnet
1616
docker compose up -d reth0 reth1 reth2 reth3 prometheus grafana otterscan
1717
./scripts/add_peers.sh --nodes 4
1818
@echo 👉 Grafana dashboard is available at http://localhost:3000
@@ -22,7 +22,7 @@ sync: clean build
2222
./scripts/generate_testnet_config.sh --nodes 4 --testnet-config-dir .testnet
2323
cargo run --bin emerald -- testnet --home nodes --testnet-config .testnet/testnet_config.toml
2424
ls nodes/*/config/priv_validator_key.json | xargs -I{} cargo run --bin emerald show-pubkey {} > nodes/validator_public_keys.txt
25-
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt
25+
cargo run --bin emerald-utils genesis --public-keys-file ./nodes/validator_public_keys.txt --devnet
2626
docker compose up -d
2727
./scripts/add_peers.sh --nodes 4
2828
@echo 👉 Grafana dashboard is available at http://localhost:3000

utils/src/genesis.rs

Lines changed: 163 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,30 @@ use core::str::FromStr;
22
use std::collections::BTreeMap;
33

44
use alloy_genesis::{ChainConfig, Genesis, GenesisAccount};
5-
use alloy_primitives::{Address, B256, U256};
5+
use alloy_primitives::{address, hex, Address, B256, U256};
66
use alloy_signer_local::coins_bip39::English;
77
use alloy_signer_local::{MnemonicBuilder, PrivateKeySigner};
88
use chrono::NaiveDate;
99
use color_eyre::eyre::{eyre, Result};
1010
use hex::decode;
1111
use 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+
};
1217
use tracing::debug;
1318

1419
use crate::validator_manager::contract::{ValidatorManager, GENESIS_VALIDATOR_MANAGER_ACCOUNT};
1520
use 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
1830
const 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

Comments
 (0)