From 5e17d02456c7d49c29c1bc6f3a5da5f3b09bb89c Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Wed, 20 Nov 2024 12:34:00 -0600 Subject: [PATCH] feat: adds config-out cli option (#394) --- src/config/cli.rs | 15 ++++++--- src/config/mod.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 5 +-- 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/config/cli.rs b/src/config/cli.rs index 0c0dcebd..7329fe41 100644 --- a/src/config/cli.rs +++ b/src/config/cli.rs @@ -15,7 +15,7 @@ use crate::system_contracts::Options as SystemContractsOptions; use super::DEFAULT_DISK_CACHE_DIR; use alloy_signer_local::coins_bip39::{English, Mnemonic}; -#[derive(Debug, Parser)] +#[derive(Debug, Parser, Clone)] #[command( author = "Matter Labs", version, @@ -31,6 +31,10 @@ pub struct Cli { /// Run in offline mode (disables all network requests). pub offline: bool, + /// Writes output of `era-test-node` as json to user-specified file. + #[arg(long, value_name = "OUT_FILE", help_heading = "General Options")] + pub config_out: Option, + #[arg(long, default_value = "8011", help_heading = "Network Options")] /// Port to listen on (default: 8011). pub port: Option, @@ -173,7 +177,7 @@ pub struct Cli { pub derivation_path: Option, } -#[derive(Debug, Subcommand)] +#[derive(Debug, Subcommand, Clone)] pub enum Command { /// Starts a new empty local network. #[command(name = "run")] @@ -186,7 +190,7 @@ pub enum Command { ReplayTx(ReplayArgs), } -#[derive(Debug, Parser)] +#[derive(Debug, Parser, Clone)] pub struct ForkArgs { /// Whether to fork from existing network. /// If not set - will start a new network from genesis. @@ -210,7 +214,7 @@ pub struct ForkArgs { pub fork_block_number: Option, } -#[derive(Debug, Parser)] +#[derive(Debug, Parser, Clone)] pub struct ReplayArgs { /// Whether to fork from existing network. /// If not set - will start a new network from genesis. @@ -240,7 +244,7 @@ impl Cli { } } /// Converts the CLI arguments to a `TestNodeConfig`. - pub fn to_test_node_config(&self) -> eyre::Result { + pub fn into_test_node_config(self) -> eyre::Result { let genesis_balance = U256::from(100u128 * 10u128.pow(18)); let vm_log_detail = if let Some(output) = self.show_outputs { @@ -288,6 +292,7 @@ impl Cli { } })) .with_chain_id(self.chain_id) + .set_config_out(self.config_out) .with_evm_emulator(if self.emulate_evm { Some(true) } else { None }); if self.emulate_evm && self.dev_system_contracts != Some(SystemContractsOptions::Local) { diff --git a/src/config/mod.rs b/src/config/mod.rs index 1c853847..c6ddb896 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -15,6 +15,9 @@ use colored::{Colorize, CustomColor}; use observability::LogLevel; use rand::thread_rng; use serde::Deserialize; +use serde_json::{json, to_writer, Value}; +use std::collections::HashMap; +use std::fs::File; use zksync_types::fee_model::FeeModelConfigV2; use zksync_types::U256; @@ -46,6 +49,8 @@ pub struct ForkPrintInfo { /// Defines the configuration parameters for the [InMemoryNode]. #[derive(Debug, Clone)] pub struct TestNodeConfig { + /// Filename to write era-test-node output as json + pub config_out: Option, /// Port the node will listen on pub port: u16, /// Controls visibility of call logs @@ -102,6 +107,7 @@ impl Default for TestNodeConfig { let genesis_accounts = AccountGenerator::new(10).phrase(DEFAULT_MNEMONIC).gen(); Self { // Node configuration defaults + config_out: None, port: NODE_PORT, show_calls: Default::default(), show_outputs: false, @@ -143,6 +149,15 @@ impl Default for TestNodeConfig { impl TestNodeConfig { pub fn print(&self, fork_details: Option<&ForkPrintInfo>) { + if self.config_out.is_some() { + let config_out = self.config_out.as_deref().unwrap(); + to_writer( + &File::create(config_out) + .expect("Unable to create era-test-node config description file"), + &self.as_json(fork_details), + ) + .expect("Failed writing json"); + } let color = CustomColor::new(13, 71, 198); println!("{}", BANNER.custom_color(color)); @@ -283,6 +298,63 @@ impl TestNodeConfig { println!("\n"); } + fn as_json(&self, fork: Option<&ForkPrintInfo>) -> Value { + let mut wallet_description = HashMap::new(); + let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len()); + let mut private_keys = Vec::with_capacity(self.genesis_accounts.len()); + + for wallet in &self.genesis_accounts { + available_accounts.push(format!("{:?}", wallet.address())); + private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes()))); + } + + if let Some(ref gen) = self.account_generator { + let phrase = gen.get_phrase().to_string(); + let derivation_path = gen.get_derivation_path().to_string(); + + wallet_description.insert("derivation_path".to_string(), derivation_path); + wallet_description.insert("mnemonic".to_string(), phrase); + }; + + if let Some(fork) = fork { + json!({ + "available_accounts": available_accounts, + "private_keys": private_keys, + "endpoint": fork.network_rpc, + "l1_block": fork.l1_block, + "l2_block": fork.l2_block, + "block_hash": fork.fork_block_hash, + "chain_id": self.get_chain_id(), + "wallet": wallet_description, + "l1_gas_price": format!("{}", self.get_l1_gas_price()), + "l2_gas_price": format!("{}", self.get_l2_gas_price()), + "l1_pubdata_price": format!("{}", self.get_l1_pubdata_price()), + "price_scale_factor": format!("{}", self.get_price_scale()), + "limit_scale_factor": format!("{}", self.get_gas_limit_scale()), + "fee_model_config_v2": fork.fee_model_config_v2, + }) + } else { + json!({ + "available_accounts": available_accounts, + "private_keys": private_keys, + "wallet": wallet_description, + "chain_id": self.get_chain_id(), + "l1_gas_price": format!("{}", self.get_l1_gas_price()), + "l2_gas_price": format!("{}", self.get_l2_gas_price()), + "l1_pubdata_price": format!("{}", self.get_l1_pubdata_price()), + "price_scale_factor": format!("{}", self.get_price_scale()), + "limit_scale_factor": format!("{}", self.get_gas_limit_scale()), + }) + } + } + + /// Sets the file path to write the Era-test-node's config info to. + #[must_use] + pub fn set_config_out(mut self, config_out: Option) -> Self { + self.config_out = config_out; + self + } + /// Set the port for the test node #[must_use] pub fn with_port(mut self, port: Option) -> Self { @@ -579,6 +651,10 @@ impl AccountGenerator { self } + fn get_phrase(&self) -> &str { + &self.phrase + } + #[must_use] pub fn chain_id(mut self, chain_id: impl Into) -> Self { self.chain_id = chain_id.into(); @@ -595,6 +671,10 @@ impl AccountGenerator { self } + fn get_derivation_path(&self) -> &str { + self.derivation_path.as_deref().unwrap_or(DERIVATION_PATH) + } + pub fn gen(&self) -> Vec { let builder = MnemonicBuilder::::default().phrase(self.phrase.as_str()); diff --git a/src/main.rs b/src/main.rs index c3694857..43f27e72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,8 +107,9 @@ async fn main() -> anyhow::Result<()> { Cli::deprecated_config_option(); let opt = Cli::parse(); + let command = opt.command.clone(); - let mut config = opt.to_test_node_config().map_err(|e| anyhow!(e))?; + let mut config = opt.into_test_node_config().map_err(|e| anyhow!(e))?; let log_level_filter = LevelFilter::from(config.log_level); let log_file = File::create(&config.log_file_path)?; @@ -118,7 +119,7 @@ async fn main() -> anyhow::Result<()> { Observability::init(vec!["era_test_node".into()], log_level_filter, log_file)?; // Use `Command::Run` as default. - let command = opt.command.as_ref().unwrap_or(&Command::Run); + let command = command.as_ref().unwrap_or(&Command::Run); let fork_details = match command { Command::Run => { if config.offline {