diff --git a/Cargo.lock b/Cargo.lock index b5469c4a..0068b28c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,7 +598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0" dependencies = [ "once_cell", - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 2.0.48", @@ -1783,6 +1783,7 @@ dependencies = [ "tempdir", "time", "tokio", + "toml 0.8.13", "tracing", "tracing-subscriber", "zkevm_opcode_defs 1.5.0", @@ -2005,7 +2006,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.48", - "toml 0.8.2", + "toml 0.8.13", "walkdir", ] @@ -3327,7 +3328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d94b7505034e2737e688e1153bf81e6f93ad296695c43958d6da2e4321f0a990" dependencies = [ "heck 0.4.1", - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -4030,7 +4031,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -4395,7 +4396,7 @@ version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -4761,11 +4762,10 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "toml_datetime", "toml_edit 0.20.2", ] @@ -5952,9 +5952,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -6940,21 +6940,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.22.13", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -6967,7 +6967,7 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.2.2", "toml_datetime", - "winnow", + "winnow 0.5.37", ] [[package]] @@ -6975,12 +6975,23 @@ name = "toml_edit" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.2.2", + "toml_datetime", + "winnow 0.5.37", +] + +[[package]] +name = "toml_edit" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap 2.2.2", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.9", ] [[package]] @@ -7744,6 +7755,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 1a88c3d4..57a3c00e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/matter-labs/zksync-era" license = "MIT OR Apache-2.0" keywords = ["blockchain", "zksync"] categories = ["cryptography"] -publish = false # We don't want to publish our binaries. +publish = false # We don't want to publish our binaries. [dependencies] zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs.git", branch = "v1.5.0" } @@ -19,7 +19,10 @@ zksync_contracts = { git = "https://github.com/matter-labs/zksync-era.git", rev zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } zksync_utils = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } zksync_state = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } -zksync_web3_decl = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea", features = [ "server", "client" ] } +zksync_web3_decl = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea", features = [ + "server", + "client", +] } sha3 = "0.10.6" @@ -39,7 +42,13 @@ clap = { version = "4.2.4", features = ["derive"] } reqwest = { version = "0.11", features = ["blocking"] } serde = { version = "1.0", features = ["derive"] } tracing = { version = "0.1.26", features = ["log"] } -tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter", "time", "json", "local-time"] } +tracing-subscriber = { version = "0.3", features = [ + "fmt", + "env-filter", + "time", + "json", + "local-time", +] } colored = "2.0" lazy_static = "1.4" eyre = "0.6" @@ -52,6 +61,7 @@ rustc-hash = "1.1.0" indexmap = "2.0.1" chrono = { version = "0.4.31", default-features = false } time = "0.3.30" +toml = "0.8.13" [dev-dependencies] httptest = "0.15.4" diff --git a/README.md b/README.md index 212d7c05..ff05362e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,24 @@ Please note that `era-test-node` is still in its **alpha** stage. Some features era_test_node run ``` +## 🔧 Configuring + +The test node can optionally be configured via a TOML configuration file placed +at `$HOME/.era_test_node/config.toml` or supplied as a path via the `--config` CLI-argument. To start configuring the test node: + +1. Create the configuration directory: +```bash +mkdir $HOME/.era_test_node +``` + +2. Copy the example configuration file: +```bash +cp example/config.toml $HOME/.era_test_node +``` + +For all options that can be configured, +please refer to [examples/config.toml](examples/config.toml) + ## 🧑‍💻 Running Locally 1. Compile Rust project and start the node: diff --git a/examples/config.toml b/examples/config.toml new file mode 100644 index 00000000..289bcac1 --- /dev/null +++ b/examples/config.toml @@ -0,0 +1,49 @@ +[node] +# Port to listen on. +port = 8011 + +# Show call debug information. Possible values: None, User, System, All. +show_calls = "None" +# Show call output. +show_outputs = false +# Show storage log information. Possible values: None, Read, Write, Paid, All. +show_storage_logs = "None" +# Show VM details information. Possible values: None, All, +show_vm_details = "None" +# Show gas details information. Possible values: None, All. +show_gas_details = "None" + +# If true, the tool will try to contact openchain to resolve the ABI & topic names. +# It will make debug log more readable, but will decrease the performance. +resolve_hashes = false + +# Specifies the option for the system contracts (use compiled built-in with or without signature verification, or load locally). +# Possible values: BuiltIn, BuiltInNoVerify, Local. +system_contracts_options = "BuiltIn" + +# Note: gas configuration functions as overrides. If provided, the node will use +# these instead of setting them to network-appropriate values. +# [gas] +# L1 gas price. +# l1_gas_price = 10 +# L2 gas price. +# l2_gas_price = 25_000_000 + +# [gas.estimation] +# L1 gas price scale factor for gas estimation. +# price_scale_factor = 1.5 +# The factor by which to scale the gasLimit. +# limit_scale_factor = 1.3 + +[log] +# Log filter level. Possible values: trace, debug, info, warn, error. +level = "info" +# Log file path. +file_path = "era_test_node.log" + +[cache] +# Cache type, can be one of `none`, `memory`, or `disk` +# The disk variant can further be configured via: +# - dir: Cache directory location. +# - reset: If true, will reset the local cache. +disk = { dir = ".cache", reset = false } diff --git a/src/cache.rs b/src/cache.rs index 5a31fc5a..825887d8 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -10,6 +10,8 @@ use zksync_basic_types::H256; use zksync_types::api::{Block, BridgeAddresses, Transaction, TransactionVariant}; use zksync_types::Transaction as RawTransaction; +use crate::config::cache::CacheConfig; + /// Caches full blocks by their hashes const CACHE_TYPE_BLOCKS_FULL: &str = "blocks_full"; /// Caches minimal blocks by their hashes @@ -24,22 +26,6 @@ const CACHE_TYPE_KEY_VALUE: &str = "key_value"; /// Caching key for bridge addresses const CACHE_KEY_BRIDGE_ADDRESSES: &str = "bridge_addresses"; -/// Cache configuration. Can be one of: -/// -/// None : Caching is disabled -/// Memory : Caching is provided in-memory and not persisted across runs -/// Disk : Caching is persisted on disk in the provided directory and can be reset -#[derive(Default, Debug, Clone)] -pub enum CacheConfig { - #[default] - None, - Memory, - Disk { - dir: String, - reset: bool, - }, -} - /// A general purpose cache. #[derive(Default, Debug, Clone)] pub(crate) struct Cache { diff --git a/src/config/cli.rs b/src/config/cli.rs new file mode 100644 index 00000000..c66d61ec --- /dev/null +++ b/src/config/cli.rs @@ -0,0 +1,134 @@ +use clap::{arg, command, Parser, Subcommand, ValueEnum}; +use serde::Deserialize; +use zksync_types::H256; + +use crate::observability::LogLevel; + +use super::node::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; + +/// Cache type config for the node. +#[derive(ValueEnum, Deserialize, Default, Debug, Copy, Clone)] +pub enum CacheType { + None, + Memory, + #[default] + Disk, +} + +/// System contract options. +#[derive(ValueEnum, Debug, Clone)] +pub enum DevSystemContracts { + BuiltIn, + BuiltInNoVerify, + Local, +} + +#[derive(Debug, Parser)] +#[command(author = "Matter Labs", version, about = "Test Node", long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Option, + #[arg(short, long)] + /// The file path to the config file. If not supplied, defaults will be used. + pub config: Option, + + #[arg(long)] + /// Port to listen on - default: 8011 + pub port: Option, + #[arg(long)] + /// Show call debug information + pub show_calls: Option, + #[arg(long)] + /// Show call output + pub show_outputs: Option, + #[arg(long)] + /// Show storage log information + pub show_storage_logs: Option, + #[arg(long)] + /// Show VM details information + pub show_vm_details: Option, + + #[arg(long)] + /// Show Gas details information + pub show_gas_details: Option, + + #[arg(long)] + /// If provided, uses a custom value as the L1 gas price. + pub l1_gas_price: Option, + + #[arg(long)] + /// If provided, uses a custom value as the L2 gas price. + pub l2_gas_price: Option, + + #[arg(long)] + /// If true, the tool will try to contact openchain to resolve the ABI & topic names. + /// It will make debug log more readable, but will decrease the performance. + pub resolve_hashes: Option, + + /// Specifies the option for the system contracts (use compiled built-in with or without signature verification, or load locally). + /// Default: built-in + #[arg(long)] + pub dev_system_contracts: Option, + + /// Log filter level - default: info + #[arg(long)] + pub log: Option, + + /// Log file path - default: era_test_node.log + #[arg(long)] + pub log_file_path: Option, + + /// Cache type, can be one of `none`, `memory`, or `disk` - default: "disk" + #[arg(long)] + pub cache: Option, + + /// If true, will reset the local `disk` cache. + #[arg(long)] + pub reset_cache: Option, + + /// Cache directory location for `disk` cache - default: ".cache" + #[arg(long)] + pub cache_dir: Option, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Starts a new empty local network. + #[command(name = "run")] + Run, + /// Starts a local network that is a fork of another network. + #[command(name = "fork")] + Fork(ForkArgs), + /// Starts a local network that is a fork of another network, and replays a given TX on it. + #[command(name = "replay_tx")] + ReplayTx(ReplayArgs), +} + +#[derive(Debug, Parser)] +pub struct ForkArgs { + /// Whether to fork from existing network. + /// If not set - will start a new network from genesis. + /// If set - will try to fork a remote network. Possible values: + /// - mainnet + /// - testnet + /// - http://XXX:YY + pub network: String, + #[arg(long)] + // Fork at a given L2 miniblock height. + // If not set - will use the current finalized block from the network. + pub fork_at: Option, +} + +#[derive(Debug, Parser)] +pub struct ReplayArgs { + /// Whether to fork from existing network. + /// If not set - will start a new network from genesis. + /// If set - will try to fork a remote network. Possible values: + /// - mainnet + /// - sepolia-testnet + /// - goerli-testnet + /// - http://XXX:YY + pub network: String, + /// Transaction hash to replay. + pub tx: H256, +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..5b26a446 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,362 @@ +use std::{env, fs::read_to_string, path::PathBuf}; + +use cache::CacheConfig; +use cli::{CacheType, Cli, DevSystemContracts}; +use gas::GasConfig; +use log::LogConfig; +use node::InMemoryNodeConfig; +use serde::Deserialize; + +use crate::system_contracts; + +pub mod cli; + +pub const CONFIG_DIR: &str = ".era_test_node"; +pub const CONFIG_FILE_NAME: &str = "config.toml"; + +/// Defines the configuration parameters for the [InMemoryNode]. +#[derive(Deserialize, Default, Debug, Clone)] +pub struct TestNodeConfig { + pub node: InMemoryNodeConfig, + // The values to be used when calculating gas. + pub gas: Option, + // Logging configuration. + pub log: LogConfig, + // Caching configuration. + pub cache: CacheConfig, +} + +impl TestNodeConfig { + /// Try to load a configuration file from either a provided path or the `$HOME` directory. + pub fn try_load(file_path: &Option) -> eyre::Result { + let path = if let Some(path) = file_path { + PathBuf::from(path) + } else { + // NOTE: `env::home_dir` is not compatible with Windows. + #[allow(deprecated)] + let mut path = env::home_dir().expect("failed to get home directory"); + + path.push(CONFIG_DIR); + path.push(CONFIG_FILE_NAME); + path + }; + + let toml = read_to_string(path)?; + let config = toml::from_str(&toml)?; + + Ok(config) + } + + /// Override the config with values provided by [`Cli`]. + pub fn override_with_opts(&mut self, opt: &Cli) { + // [`NodeConfig`]. + if let Some(port) = &opt.port { + self.node.port = *port; + } + + if let Some(show_calls) = &opt.show_calls { + self.node.show_calls = *show_calls; + } + if let Some(show_outputs) = &opt.show_outputs { + self.node.show_outputs = *show_outputs; + } + if let Some(show_storage_logs) = &opt.show_storage_logs { + self.node.show_storage_logs = *show_storage_logs; + } + if let Some(show_vm_details) = &opt.show_vm_details { + self.node.show_vm_details = *show_vm_details; + } + if let Some(show_gas_details) = &opt.show_gas_details { + self.node.show_gas_details = *show_gas_details; + } + if let Some(resolve_hashes) = &opt.resolve_hashes { + self.node.resolve_hashes = *resolve_hashes; + } + + if let Some(contract_options) = &opt.dev_system_contracts { + self.node.system_contracts_options = match contract_options { + DevSystemContracts::BuiltIn => system_contracts::Options::BuiltIn, + DevSystemContracts::BuiltInNoVerify => { + system_contracts::Options::BuiltInWithoutSecurity + } + DevSystemContracts::Local => system_contracts::Options::Local, + }; + } + + // [`GasConfig`] + if let Some(l1_gas_price) = &opt.l1_gas_price { + let mut gas = self.gas.unwrap_or_default(); + gas.l1_gas_price = Some(*l1_gas_price); + self.gas = Some(gas); + } + if let Some(l2_gas_price) = &opt.l2_gas_price { + let mut gas = self.gas.unwrap_or_default(); + gas.l2_gas_price = Some(*l2_gas_price); + self.gas = Some(gas); + } + + // [`LogConfig`]. + if let Some(log_level) = &opt.log { + self.log.level = *log_level; + } + if let Some(file_path) = &opt.log_file_path { + self.log.file_path = file_path.to_string(); + } + + // [`CacheConfig`]. + if let Some(cache_type) = &opt.cache { + self.cache = match cache_type { + CacheType::None => CacheConfig::None, + CacheType::Memory => CacheConfig::Memory, + CacheType::Disk => CacheConfig::Disk { + dir: opt.cache_dir.clone().expect("missing --cache-dir argument"), + reset: opt.reset_cache.unwrap_or_default(), + }, + }; + } + } +} + +pub mod node { + use clap::Parser; + use serde::Deserialize; + use std::{fmt::Display, str::FromStr}; + + use crate::system_contracts; + + #[derive(Deserialize, Debug, Copy, Clone)] + pub struct InMemoryNodeConfig { + pub port: u16, + pub show_calls: ShowCalls, + pub show_outputs: bool, + pub show_storage_logs: ShowStorageLogs, + pub show_vm_details: ShowVMDetails, + pub show_gas_details: ShowGasDetails, + pub resolve_hashes: bool, + pub system_contracts_options: system_contracts::Options, + } + + impl Default for InMemoryNodeConfig { + fn default() -> Self { + Self { + port: 8011, + show_calls: Default::default(), + show_outputs: Default::default(), + show_storage_logs: Default::default(), + show_vm_details: Default::default(), + show_gas_details: Default::default(), + resolve_hashes: Default::default(), + system_contracts_options: Default::default(), + } + } + } + + #[derive( + Deserialize, Debug, Default, clap::Parser, Copy, Clone, clap::ValueEnum, PartialEq, Eq, + )] + pub enum ShowCalls { + #[default] + None, + User, + System, + All, + } + + impl FromStr for ShowCalls { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_ref() { + "none" => Ok(ShowCalls::None), + "user" => Ok(ShowCalls::User), + "system" => Ok(ShowCalls::System), + "all" => Ok(ShowCalls::All), + _ => Err(format!( + "Unknown ShowCalls value {} - expected one of none|user|system|all.", + s + )), + } + } + } + + impl Display for ShowCalls { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{:?}", self) + } + } + + #[derive(Deserialize, Debug, Default, Parser, Copy, Clone, clap::ValueEnum, PartialEq, Eq)] + pub enum ShowStorageLogs { + #[default] + None, + Read, + Write, + Paid, + All, + } + + impl FromStr for ShowStorageLogs { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_ref() { + "none" => Ok(ShowStorageLogs::None), + "read" => Ok(ShowStorageLogs::Read), + "write" => Ok(ShowStorageLogs::Write), + "paid" => Ok(ShowStorageLogs::Paid), + "all" => Ok(ShowStorageLogs::All), + _ => Err(format!( + "Unknown ShowStorageLogs value {} - expected one of none|read|write|paid|all.", + s + )), + } + } + } + + impl Display for ShowStorageLogs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{:?}", self) + } + } + + #[derive(Deserialize, Debug, Default, Parser, Copy, Clone, clap::ValueEnum, PartialEq, Eq)] + pub enum ShowVMDetails { + #[default] + None, + All, + } + + impl FromStr for ShowVMDetails { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_ref() { + "none" => Ok(ShowVMDetails::None), + "all" => Ok(ShowVMDetails::All), + _ => Err(format!( + "Unknown ShowVMDetails value {} - expected one of none|all.", + s + )), + } + } + } + + impl Display for ShowVMDetails { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{:?}", self) + } + } + + #[derive(Deserialize, Debug, Default, Parser, Copy, Clone, clap::ValueEnum, PartialEq, Eq)] + pub enum ShowGasDetails { + #[default] + None, + All, + } + + impl FromStr for ShowGasDetails { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_ref() { + "none" => Ok(ShowGasDetails::None), + "all" => Ok(ShowGasDetails::All), + _ => Err(format!( + "Unknown ShowGasDetails value {} - expected one of none|all.", + s + )), + } + } + } + + impl Display for ShowGasDetails { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{:?}", self) + } + } +} + +pub mod gas { + use serde::Deserialize; + + /// L1 Gas Price. + pub const DEFAULT_L1_GAS_PRICE: u64 = 50_000_000_000; + // TODO: for now, that's fine, as computation overhead is set to zero, but we may consider using calculated fee input everywhere. + /// The default L2 Gas Price to be used if not supplied via the CLI argument. + pub const DEFAULT_L2_GAS_PRICE: u64 = 25_000_000; + /// L1 Gas Price Scale Factor for gas estimation. + pub const DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR: f64 = 1.5; + /// The factor by which to scale the gasLimit. + pub const DEFAULT_ESTIMATE_GAS_SCALE_FACTOR: f32 = 1.3; + + #[derive(Deserialize, Debug, Default, Copy, Clone)] + pub struct GasConfig { + /// L1 gas price. + pub l1_gas_price: Option, + /// L2 gas price. + pub l2_gas_price: Option, + /// Factors used in estimating gas. + pub estimation: Option, + } + + #[derive(Deserialize, Debug, Default, Copy, Clone)] + pub struct Estimation { + /// L1 gas price scale factor for gas estimation. + pub price_scale_factor: Option, + /// The factor by which to scale the gasLimit. + pub limit_scale_factor: Option, + } +} + +pub mod log { + use serde::Deserialize; + + use crate::observability::LogLevel; + + pub const DEFAULT_LOG_FILE_PATH: &str = "era_test_node.log"; + + #[derive(Deserialize, Debug, Clone)] + pub struct LogConfig { + pub level: LogLevel, + pub file_path: String, + } + + impl Default for LogConfig { + fn default() -> Self { + Self { + level: Default::default(), + file_path: String::from(DEFAULT_LOG_FILE_PATH), + } + } + } +} + +pub mod cache { + use serde::Deserialize; + + pub const DEFAULT_DISK_CACHE_DIR: &str = ".cache"; + + /// Cache configuration. Can be one of: + /// + /// None : Caching is disabled + /// Memory : Caching is provided in-memory and not persisted across runs + /// Disk : Caching is persisted on disk in the provided directory and can be reset + #[derive(Deserialize, Debug, Clone)] + pub enum CacheConfig { + #[serde(rename = "none")] + None, + #[serde(rename = "memory")] + Memory, + #[serde(rename = "disk")] + Disk { dir: String, reset: bool }, + } + + impl Default for CacheConfig { + fn default() -> Self { + Self::Disk { + dir: String::from(DEFAULT_DISK_CACHE_DIR), + reset: false, + } + } + } +} diff --git a/src/fork.rs b/src/fork.rs index e652268f..eeda5f4a 100644 --- a/src/fork.rs +++ b/src/fork.rs @@ -36,12 +36,12 @@ use zksync_web3_decl::{ }; use zksync_web3_decl::{namespaces::EthNamespaceClient, types::Index}; -use crate::{cache::CacheConfig, node::TEST_NODE_NETWORK_ID}; -use crate::{deps::InMemoryStorage, http_fork_source::HttpForkSource}; +use crate::{config::cache::CacheConfig, node::TEST_NODE_NETWORK_ID}; use crate::{ - node::{DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR}, + config::gas::{DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR}, system_contracts, }; +use crate::{deps::InMemoryStorage, http_fork_source::HttpForkSource}; pub fn block_on(future: F) -> F::Output where @@ -652,15 +652,12 @@ mod tests { use zksync_state::ReadStorage; use zksync_types::{api::TransactionVariant, StorageKey}; - use crate::{ - cache::CacheConfig, - deps::InMemoryStorage, - node::{ - DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, - DEFAULT_L2_GAS_PRICE, - }, - system_contracts, testing, + use crate::config::cache::CacheConfig; + use crate::config::gas::{ + DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, + DEFAULT_L2_GAS_PRICE, }; + use crate::{deps::InMemoryStorage, system_contracts, testing}; use super::{ForkDetails, ForkStorage}; diff --git a/src/formatter.rs b/src/formatter.rs index 49fa3550..56b8dd4f 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -1,5 +1,5 @@ //! Helper methods to display transaction data in more human readable way. -use crate::{node::ShowCalls, resolver}; +use crate::{config::node::ShowCalls, resolver}; use colored::Colorize; diff --git a/src/http_fork_source.rs b/src/http_fork_source.rs index 275b239d..84ba2c30 100644 --- a/src/http_fork_source.rs +++ b/src/http_fork_source.rs @@ -4,7 +4,8 @@ use std::{ }; use crate::{ - cache::{Cache, CacheConfig}, + cache::Cache, + config::cache::CacheConfig, fork::{block_on, ForkSource}, }; use eyre::Context; diff --git a/src/lib.rs b/src/lib.rs index d28de8ac..c997e2d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ //! Contributions to improve `era-test-node` are welcome. Please refer to the [contribution guidelines](https://github.com/matter-labs/era-test-node/blob/main/.github/CONTRIBUTING.md) for more details. pub mod bootloader_debug; +pub mod config; pub mod console_log; pub mod deps; pub mod filters; diff --git a/src/main.rs b/src/main.rs index 285b04c7..9bf692c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,16 @@ -use crate::cache::CacheConfig; -use crate::http_fork_source::HttpForkSource; -use crate::node::{InMemoryNodeConfig, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; use crate::observability::Observability; -use crate::utils::to_human_size; -use clap::{Parser, Subcommand, ValueEnum}; +use clap::Parser; use colored::Colorize; +use config::cli::{Cli, Command}; +use config::TestNodeConfig; use fork::{ForkDetails, ForkSource}; +use http_fork_source::HttpForkSource; use logging_middleware::LoggingMiddleware; -use node::{ShowCalls, DEFAULT_L2_GAS_PRICE}; -use observability::LogLevel; use tracing_subscriber::filter::LevelFilter; mod bootloader_debug; mod cache; +mod config; mod console_log; mod deps; mod filters; @@ -22,7 +20,7 @@ mod http_fork_source; mod logging_middleware; mod namespaces; mod node; -pub mod observability; +mod observability; mod resolver; mod system_contracts; mod testing; @@ -43,7 +41,7 @@ use futures::{ FutureExt, }; use jsonrpc_core::MetaIoHandler; -use zksync_basic_types::{H160, H256}; +use zksync_basic_types::H160; use crate::namespaces::{ ConfigurationApiNamespaceT, DebugNamespaceT, EthNamespaceT, EthTestNodeNamespaceT, @@ -193,134 +191,16 @@ async fn build_json_http< tokio::spawn(recv.map(drop)) } -/// Cache type config for the node. -#[derive(ValueEnum, Debug, Clone)] -enum CacheType { - None, - Memory, - Disk, -} - -/// System contract options. -#[derive(ValueEnum, Debug, Clone)] -enum DevSystemContracts { - BuiltIn, - BuiltInNoVerify, - Local, -} - -#[derive(Debug, Parser)] -#[command(author = "Matter Labs", version, about = "Test Node", long_about = None)] -struct Cli { - #[command(subcommand)] - command: Option, - #[arg(long, default_value = "8011")] - /// Port to listen on - default: 8011 - port: u16, - #[arg(long, default_value = "none")] - /// Show call debug information - show_calls: ShowCalls, - #[arg[long]] - /// Show call output - show_outputs: bool, - #[arg(long, default_value = "none")] - /// Show storage log information - show_storage_logs: ShowStorageLogs, - #[arg(long, default_value = "none")] - /// Show VM details information - show_vm_details: ShowVMDetails, - - #[arg(long, default_value = "none")] - /// Show Gas details information - show_gas_details: ShowGasDetails, - - #[arg(long)] - /// If provided, uses a custom value as the L1 gas price. - l1_gas_price: Option, - - #[arg(long)] - /// If provided, uses a custom value as the L2 gas price. If not provided the gas price will be - /// inferred from the protocol version. - l2_gas_price: Option, - - #[arg(long)] - /// If true, the tool will try to contact openchain to resolve the ABI & topic names. - /// It will make debug log more readable, but will decrease the performance. - resolve_hashes: bool, - - /// Specifies the option for the system contracts (use compiled built-in with or without signature verification, or load locally). - /// Default: built-in - #[arg(long, default_value = "built-in")] - dev_system_contracts: DevSystemContracts, - - /// Log filter level - default: info - #[arg(long, default_value = "info")] - log: LogLevel, - - /// Log file path - default: era_test_node.log - #[arg(long, default_value = "era_test_node.log")] - log_file_path: String, - - /// Cache type, can be one of `none`, `memory`, or `disk` - default: "disk" - #[arg(long, default_value = "disk")] - cache: CacheType, - - /// If true, will reset the local `disk` cache. - #[arg(long)] - reset_cache: bool, - - /// Cache directory location for `disk` cache - default: ".cache" - #[arg(long, default_value = ".cache")] - cache_dir: String, -} - -#[derive(Debug, Subcommand)] -enum Command { - /// Starts a new empty local network. - #[command(name = "run")] - Run, - /// Starts a local network that is a fork of another network. - #[command(name = "fork")] - Fork(ForkArgs), - /// Starts a local network that is a fork of another network, and replays a given TX on it. - #[command(name = "replay_tx")] - ReplayTx(ReplayArgs), -} - -#[derive(Debug, Parser)] -struct ForkArgs { - /// Whether to fork from existing network. - /// If not set - will start a new network from genesis. - /// If set - will try to fork a remote network. Possible values: - /// - mainnet - /// - testnet - /// - http://XXX:YY - network: String, - #[arg(long)] - // Fork at a given L2 miniblock height. - // If not set - will use the current finalized block from the network. - fork_at: Option, -} - -#[derive(Debug, Parser)] -struct ReplayArgs { - /// Whether to fork from existing network. - /// If not set - will start a new network from genesis. - /// If set - will try to fork a remote network. Possible values: - /// - mainnet - /// - sepolia-testnet - /// - goerli-testnet - /// - http://XXX:YY - network: String, - /// Transaction hash to replay. - tx: H256, -} - #[tokio::main] async fn main() -> anyhow::Result<()> { let opt = Cli::parse(); - let log_level_filter = LevelFilter::from(opt.log); - let log_file = File::create(opt.log_file_path)?; + + // Try to read the [`TestNodeConfig`] file if supplied as an argument. + let mut config = TestNodeConfig::try_load(&opt.config).unwrap_or_default(); + config.override_with_opts(&opt); + + let log_level_filter = LevelFilter::from(config.log.level); + let log_file = File::create(config.log.file_path)?; // Initialize the tracing subscriber let observability = Observability::init( @@ -329,29 +209,15 @@ async fn main() -> anyhow::Result<()> { log_file, )?; - if matches!(opt.dev_system_contracts, DevSystemContracts::Local) { - if let Some(path) = env::var_os("ZKSYNC_HOME") { - tracing::info!("+++++ Reading local contracts from {:?} +++++", path); - } - } - let cache_config = match opt.cache { - CacheType::None => CacheConfig::None, - CacheType::Memory => CacheConfig::Memory, - CacheType::Disk => CacheConfig::Disk { - dir: opt.cache_dir, - reset: opt.reset_cache, - }, - }; - // Use `Command::Run` as default. let command = opt.command.as_ref().unwrap_or(&Command::Run); - let fork_details = match &command { + let fork_details = match command { Command::Run => None, Command::Fork(fork) => { - Some(ForkDetails::from_network(&fork.network, fork.fork_at, cache_config).await) + Some(ForkDetails::from_network(&fork.network, fork.fork_at, config.cache).await) } Command::ReplayTx(replay_tx) => { - Some(ForkDetails::from_network_tx(&replay_tx.network, replay_tx.tx, cache_config).await) + Some(ForkDetails::from_network_tx(&replay_tx.network, replay_tx.tx, config.cache).await) } }; @@ -366,54 +232,18 @@ async fn main() -> anyhow::Result<()> { } else { vec![] }; - let system_contracts_options = match opt.dev_system_contracts { - DevSystemContracts::BuiltIn => system_contracts::Options::BuiltIn, - DevSystemContracts::BuiltInNoVerify => system_contracts::Options::BuiltInWithoutSecurity, - DevSystemContracts::Local => system_contracts::Options::Local, - }; - // If we're forking we set the price to be equal to that contained within - // `ForkDetails`. If not, we use the `DEFAULT_L2_GAS_PRICE` instead. - let mut l2_fair_gas_price = { - if let Some(f) = &fork_details { - f.l2_fair_gas_price - } else { - DEFAULT_L2_GAS_PRICE - } - }; - - // If L2 gas price has been supplied as an argument, override the value - // procured previously. - match opt.l2_gas_price { - Some(l2_gas_price) => { - tracing::info!( - "Starting node with L2 gas price set to {} (overridden from {})", - to_human_size(l2_gas_price.into()), - to_human_size(l2_fair_gas_price.into()) - ); - l2_fair_gas_price = l2_gas_price; + if matches!( + config.node.system_contracts_options, + system_contracts::Options::Local + ) { + if let Some(path) = env::var_os("ZKSYNC_HOME") { + tracing::info!("+++++ Reading local contracts from {:?} +++++", path); } - None => tracing::info!( - "Starting node with L2 gas price set to {}", - to_human_size(l2_fair_gas_price.into()) - ), } - let node: InMemoryNode = InMemoryNode::new( - fork_details, - Some(observability), - InMemoryNodeConfig { - l1_gas_price: opt.l1_gas_price, - l2_fair_gas_price, - show_calls: opt.show_calls, - show_outputs: opt.show_outputs, - show_storage_logs: opt.show_storage_logs, - show_vm_details: opt.show_vm_details, - show_gas_details: opt.show_gas_details, - resolve_hashes: opt.resolve_hashes, - system_contracts_options, - }, - ); + let node: InMemoryNode = + InMemoryNode::new(fork_details, Some(observability), config.node, config.gas); if !transactions_to_replay.is_empty() { let _ = node.apply_txs(transactions_to_replay); @@ -443,14 +273,14 @@ async fn main() -> anyhow::Result<()> { } let threads = build_json_http( - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), opt.port), + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), config.node.port), log_level_filter, node, ) .await; tracing::info!("========================================"); - tracing::info!(" Node is ready at 127.0.0.1:{}", opt.port); + tracing::info!(" Node is ready at 127.0.0.1:{}", config.node.port); tracing::info!("========================================"); future::select_all(vec![threads]).await.0.unwrap(); diff --git a/src/node/config.rs b/src/node/config_api.rs similarity index 87% rename from src/node/config.rs rename to src/node/config_api.rs index a0b63fd0..3051a998 100644 --- a/src/node/config.rs +++ b/src/node/config_api.rs @@ -1,6 +1,7 @@ use zksync_web3_decl::error::Web3Error; use crate::{ + config::node::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}, fork::ForkSource, namespaces::{ConfigurationApiNamespaceT, Result}, node::InMemoryNode, @@ -8,8 +9,6 @@ use crate::{ utils::into_jsrpc_error, }; -use super::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; - impl ConfigurationApiNamespaceT for InMemoryNode { @@ -22,7 +21,7 @@ impl Configurat "Failed to acquire read lock for inner node state.", ))) }) - .map(|reader| reader.show_calls.to_string()) + .map(|reader| reader.config.show_calls.to_string()) } fn config_get_show_outputs(&self) -> Result { @@ -34,7 +33,7 @@ impl Configurat "Failed to acquire read lock for inner node state.", ))) }) - .map(|reader| reader.show_outputs) + .map(|reader| reader.config.show_outputs) } fn config_get_current_timestamp(&self) -> Result { @@ -64,8 +63,8 @@ impl Configurat ))) }) .map(|mut writer| { - writer.show_calls = show_calls; - writer.show_calls.to_string() + writer.config.show_calls = show_calls; + writer.config.show_calls.to_string() }) } @@ -79,8 +78,8 @@ impl Configurat ))) }) .map(|mut writer| { - writer.show_outputs = value; - writer.show_outputs + writer.config.show_outputs = value; + writer.config.show_outputs }) } @@ -97,7 +96,7 @@ impl Configurat "Failed to acquire read lock for inner node state.", ))) }) - .map(|reader| reader.show_storage_logs.to_string()) + .map(|reader| reader.config.show_storage_logs.to_string()) } }; @@ -110,8 +109,8 @@ impl Configurat ))) }) .map(|mut writer| { - writer.show_storage_logs = show_storage_logs; - writer.show_storage_logs.to_string() + writer.config.show_storage_logs = show_storage_logs; + writer.config.show_storage_logs.to_string() }) } @@ -128,7 +127,7 @@ impl Configurat "Failed to acquire read lock for inner node state.", ))) }) - .map(|reader| reader.show_vm_details.to_string()) + .map(|reader| reader.config.show_vm_details.to_string()) } }; @@ -141,8 +140,8 @@ impl Configurat ))) }) .map(|mut writer| { - writer.show_vm_details = show_vm_details; - writer.show_vm_details.to_string() + writer.config.show_vm_details = show_vm_details; + writer.config.show_vm_details.to_string() }) } @@ -159,7 +158,7 @@ impl Configurat "Failed to acquire read lock for inner node state.", ))) }) - .map(|reader| reader.show_gas_details.to_string()) + .map(|reader| reader.config.show_gas_details.to_string()) } }; @@ -172,8 +171,8 @@ impl Configurat ))) }) .map(|mut writer| { - writer.show_gas_details = show_gas_details; - writer.show_gas_details.to_string() + writer.config.show_gas_details = show_gas_details; + writer.config.show_gas_details.to_string() }) } @@ -187,8 +186,8 @@ impl Configurat ))) }) .map(|mut writer| { - writer.resolve_hashes = value; - writer.resolve_hashes + writer.config.resolve_hashes = value; + writer.config.resolve_hashes }) } @@ -204,7 +203,7 @@ impl Configurat })? .observability { - match observability.set_log_level(level.clone()) { + match observability.set_log_level(level) { Ok(_) => tracing::info!("set log level to '{}'", level), Err(err) => { tracing::error!("failed setting log level {:?}", err); diff --git a/src/node/eth.rs b/src/node/eth.rs index b03b09a3..ce9f750f 100644 --- a/src/node/eth.rs +++ b/src/node/eth.rs @@ -1492,10 +1492,10 @@ impl EthTestNod #[cfg(test)] mod tests { use crate::{ - cache::CacheConfig, + config::{cache::CacheConfig, gas::DEFAULT_L2_GAS_PRICE}, fork::ForkDetails, http_fork_source::HttpForkSource, - node::{compute_hash, InMemoryNode, Snapshot, DEFAULT_L2_GAS_PRICE}, + node::{compute_hash, InMemoryNode, Snapshot}, testing::{ self, default_tx_debug_info, ForkBlockConfig, LogBuilder, MockServer, TransactionResponseBuilder, @@ -1686,6 +1686,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let inner = node.get_inner(); @@ -1730,6 +1731,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_block = node @@ -1798,6 +1800,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_block = node @@ -1846,6 +1849,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_block = node @@ -1882,6 +1886,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_block = node @@ -1909,6 +1914,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_block = node @@ -1967,6 +1973,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_transaction_count = node @@ -2024,6 +2031,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_transaction_count = node @@ -2066,6 +2074,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_transaction_count = node @@ -2099,6 +2108,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_transaction_count = node @@ -2374,6 +2384,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_value = node @@ -2469,6 +2480,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); node.get_inner() .write() @@ -3078,6 +3090,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); // store the block info with just the tx hash invariant @@ -3133,6 +3146,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_tx = node @@ -3228,6 +3242,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); // store the block info with just the tx hash invariant @@ -3290,6 +3305,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_tx = node diff --git a/src/node/fee_model.rs b/src/node/fee_model.rs index c29185ec..3aca807b 100644 --- a/src/node/fee_model.rs +++ b/src/node/fee_model.rs @@ -3,10 +3,11 @@ use zksync_node_fee_model::BatchFeeModelInputProvider; use zksync_types::fee_model::{FeeModelConfigV2, FeeParams, FeeParamsV2}; use zksync_types::L1_GAS_PER_PUBDATA_BYTE; -use super::{ - DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, +use crate::config::gas::{ + GasConfig, DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, DEFAULT_L1_GAS_PRICE, DEFAULT_L2_GAS_PRICE, }; +use crate::utils::to_human_size; #[derive(Debug, Clone, PartialEq)] pub struct TestNodeFeeInputProvider { @@ -58,6 +59,40 @@ impl TestNodeFeeInputProvider { } } + pub fn with_overrides(mut self, gas_config: Option) -> Self { + let Some(gas_config) = gas_config else { + return self; + }; + + if let Some(l1_gas_price) = gas_config.l1_gas_price { + tracing::info!( + "L1 gas price set to {} (overridden from {})", + to_human_size(l1_gas_price.into()), + to_human_size(self.l1_gas_price.into()) + ); + self.l1_gas_price = l1_gas_price; + } + if let Some(l2_gas_price) = gas_config.l2_gas_price { + tracing::info!( + "L2 gas price set to {} (overridden from {})", + to_human_size(l2_gas_price.into()), + to_human_size(self.l2_gas_price.into()) + ); + self.l2_gas_price = l2_gas_price; + } + + if let Some(estimation) = gas_config.estimation { + if let Some(factor) = estimation.price_scale_factor { + self.estimate_gas_price_scale_factor = factor; + } + if let Some(factor) = estimation.limit_scale_factor { + self.estimate_gas_scale_factor = factor; + } + } + + self + } + pub fn get_fee_model_config(&self) -> FeeModelConfigV2 { FeeModelConfigV2 { minimal_l2_gas_price: self.l2_gas_price, diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs index 3e149d7e..1fcfe114 100644 --- a/src/node/in_memory.rs +++ b/src/node/in_memory.rs @@ -1,7 +1,11 @@ //! In-memory node, that supports forking other networks. use crate::{ bootloader_debug::{BootloaderDebug, BootloaderDebugTracer}, - cache::CacheConfig, + config::{ + cache::CacheConfig, + gas::{self, GasConfig}, + node::{InMemoryNodeConfig, ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}, + }, console_log::ConsoleLogHandler, deps::{storage_view::StorageView, InMemoryStorage}, filters::EthFilters, @@ -12,14 +16,11 @@ use crate::{ system_contracts::{self, SystemContracts}, utils::{bytecode_to_factory_dep, create_debug_output, into_jsrpc_error, to_human_size}, }; -use clap::Parser; use colored::Colorize; -use core::fmt::Display; use indexmap::IndexMap; use once_cell::sync::OnceCell; use std::{ collections::{HashMap, HashSet}, - str::FromStr, sync::{Arc, RwLock}, }; @@ -74,17 +75,8 @@ pub const MAX_TX_SIZE: usize = 1_000_000; pub const NON_FORK_FIRST_BLOCK_TIMESTAMP: u64 = 1_000; /// Network ID we use for the test node. pub const TEST_NODE_NETWORK_ID: u32 = 260; -/// L1 Gas Price. -pub const DEFAULT_L1_GAS_PRICE: u64 = 50_000_000_000; -// TODO: for now, that's fine, as computation overhead is set to zero, but we may consider using calculated fee input everywhere. -/// The default L2 Gas Price to be used if not supplied via the CLI argument. -pub const DEFAULT_L2_GAS_PRICE: u64 = 25_000_000; -/// L1 Gas Price Scale Factor for gas estimation. -pub const DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR: f64 = 1.5; /// Acceptable gas overestimation limit. pub const ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION: u64 = 1_000; -/// The factor by which to scale the gasLimit. -pub const DEFAULT_ESTIMATE_GAS_SCALE_FACTOR: f32 = 1.3; /// The maximum number of previous blocks to store the state for. pub const MAX_PREVIOUS_STATES: u16 = 128; /// The zks protocol version. @@ -130,128 +122,6 @@ pub struct TxExecutionInfo { pub result: VmExecutionResultAndLogs, } -#[derive(Debug, Default, clap::Parser, Clone, clap::ValueEnum, PartialEq, Eq)] -pub enum ShowCalls { - #[default] - None, - User, - System, - All, -} - -impl FromStr for ShowCalls { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "none" => Ok(ShowCalls::None), - "user" => Ok(ShowCalls::User), - "system" => Ok(ShowCalls::System), - "all" => Ok(ShowCalls::All), - _ => Err(format!( - "Unknown ShowCalls value {} - expected one of none|user|system|all.", - s - )), - } - } -} - -impl Display for ShowCalls { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - -#[derive(Debug, Default, Parser, Clone, clap::ValueEnum, PartialEq, Eq)] -pub enum ShowStorageLogs { - #[default] - None, - Read, - Write, - Paid, - All, -} - -impl FromStr for ShowStorageLogs { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "none" => Ok(ShowStorageLogs::None), - "read" => Ok(ShowStorageLogs::Read), - "write" => Ok(ShowStorageLogs::Write), - "paid" => Ok(ShowStorageLogs::Paid), - "all" => Ok(ShowStorageLogs::All), - _ => Err(format!( - "Unknown ShowStorageLogs value {} - expected one of none|read|write|paid|all.", - s - )), - } - } -} - -impl Display for ShowStorageLogs { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - -#[derive(Debug, Default, Parser, Clone, clap::ValueEnum, PartialEq, Eq)] -pub enum ShowVMDetails { - #[default] - None, - All, -} - -impl FromStr for ShowVMDetails { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "none" => Ok(ShowVMDetails::None), - "all" => Ok(ShowVMDetails::All), - _ => Err(format!( - "Unknown ShowVMDetails value {} - expected one of none|all.", - s - )), - } - } -} - -impl Display for ShowVMDetails { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - -#[derive(Debug, Default, Parser, Clone, clap::ValueEnum, PartialEq, Eq)] -pub enum ShowGasDetails { - #[default] - None, - All, -} - -impl FromStr for ShowGasDetails { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "none" => Ok(ShowGasDetails::None), - "all" => Ok(ShowGasDetails::All), - _ => Err(format!( - "Unknown ShowGasDetails value {} - expected one of none|all.", - s - )), - } - } -} - -impl Display for ShowGasDetails { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - #[derive(Debug, Clone)] pub struct TransactionResult { pub info: TxExecutionInfo, @@ -302,18 +172,8 @@ pub struct InMemoryNodeInner { pub filters: EthFilters, // Underlying storage pub fork_storage: ForkStorage, - // Debug level information. - pub show_calls: ShowCalls, - // If true - will display the output of the calls. - pub show_outputs: bool, - // Displays storage logs. - pub show_storage_logs: ShowStorageLogs, - // Displays VM details. - pub show_vm_details: ShowVMDetails, - // Gas details information. - pub show_gas_details: ShowGasDetails, - // If true - will contact openchain to resolve the ABI to function names. - pub resolve_hashes: bool, + // Configuration. + pub config: InMemoryNodeConfig, pub console_log_handler: ConsoleLogHandler, pub system_contracts: SystemContracts, pub impersonated_accounts: HashSet
, @@ -339,43 +199,29 @@ impl InMemoryNodeInner { fork: Option, observability: Option, config: InMemoryNodeConfig, + gas_overrides: Option, ) -> Self { - let default_l1_gas_price = if let Some(f) = &fork { - f.l1_gas_price - } else { - DEFAULT_L1_GAS_PRICE - }; - let l1_gas_price = if let Some(custom_l1_gas_price) = config.l1_gas_price { - tracing::info!( - "L1 gas price set to {} (overridden from {})", - to_human_size(custom_l1_gas_price.into()), - to_human_size(default_l1_gas_price.into()) - ); - custom_l1_gas_price - } else { - default_l1_gas_price - }; - if let Some(f) = &fork { let mut block_hashes = HashMap::::new(); block_hashes.insert(f.l2_block.number.as_u64(), f.l2_block.hash); let mut blocks = HashMap::>::new(); blocks.insert(f.l2_block.hash, f.l2_block.clone()); - let mut fee_input_provider = if let Some(params) = f.fee_params { + let fee_input_provider = if let Some(params) = f.fee_params { TestNodeFeeInputProvider::from_fee_params_and_estimate_scale_factors( params, f.estimate_gas_price_scale_factor, f.estimate_gas_scale_factor, ) + .with_overrides(gas_overrides) } else { TestNodeFeeInputProvider::from_estimate_scale_factors( f.estimate_gas_price_scale_factor, f.estimate_gas_scale_factor, ) + .with_overrides(gas_overrides) }; - fee_input_provider.l1_gas_price = l1_gas_price; - fee_input_provider.l2_gas_price = config.l2_fair_gas_price; + InMemoryNodeInner { current_timestamp: f.block_timestamp, current_batch: f.l1_block.0, @@ -387,12 +233,7 @@ impl InMemoryNodeInner { block_hashes, filters: Default::default(), fork_storage: ForkStorage::new(fork, &config.system_contracts_options), - show_calls: config.show_calls, - show_outputs: config.show_outputs, - show_storage_logs: config.show_storage_logs, - show_vm_details: config.show_vm_details, - show_gas_details: config.show_gas_details, - resolve_hashes: config.resolve_hashes, + config, console_log_handler: ConsoleLogHandler::default(), system_contracts: SystemContracts::from_options(&config.system_contracts_options), impersonated_accounts: Default::default(), @@ -410,10 +251,8 @@ impl InMemoryNodeInner { create_empty_block(0, NON_FORK_FIRST_BLOCK_TIMESTAMP, 0, None), ); - let fee_input_provider = TestNodeFeeInputProvider { - l1_gas_price, - ..Default::default() - }; + let fee_input_provider = + TestNodeFeeInputProvider::default().with_overrides(gas_overrides); InMemoryNodeInner { current_timestamp: NON_FORK_FIRST_BLOCK_TIMESTAMP, current_batch: 0, @@ -425,12 +264,7 @@ impl InMemoryNodeInner { block_hashes, filters: Default::default(), fork_storage: ForkStorage::new(fork, &config.system_contracts_options), - show_calls: config.show_calls, - show_outputs: config.show_outputs, - show_storage_logs: config.show_storage_logs, - show_vm_details: config.show_vm_details, - show_gas_details: config.show_gas_details, - resolve_hashes: config.resolve_hashes, + config, console_log_handler: ConsoleLogHandler::default(), system_contracts: SystemContracts::from_options(&config.system_contracts_options), impersonated_accounts: Default::default(), @@ -988,37 +822,6 @@ pub struct Snapshot { pub(crate) factory_dep_cache: HashMap>>, } -/// Defines the configuration parameters for the [InMemoryNode]. -#[derive(Debug, Clone)] -pub struct InMemoryNodeConfig { - // The values to be used when calculating gas. - pub l1_gas_price: Option, - pub l2_fair_gas_price: u64, - pub show_calls: ShowCalls, - pub show_outputs: bool, - pub show_storage_logs: ShowStorageLogs, - pub show_vm_details: ShowVMDetails, - pub show_gas_details: ShowGasDetails, - pub resolve_hashes: bool, - pub system_contracts_options: system_contracts::Options, -} - -impl Default for InMemoryNodeConfig { - fn default() -> Self { - Self { - l1_gas_price: None, - l2_fair_gas_price: DEFAULT_L2_GAS_PRICE, - show_calls: Default::default(), - show_outputs: Default::default(), - show_storage_logs: Default::default(), - show_vm_details: Default::default(), - show_gas_details: Default::default(), - resolve_hashes: Default::default(), - system_contracts_options: Default::default(), - } - } -} - /// In-memory node, that can be used for local & unit testing. /// It also supports the option of forking testnet/mainnet. /// All contents are removed when object is destroyed. @@ -1029,6 +832,7 @@ pub struct InMemoryNode { /// List of snapshots of the [InMemoryNodeInner]. This is bounded at runtime by [MAX_SNAPSHOTS]. pub(crate) snapshots: Arc>>, /// Configuration option that survives reset. + #[allow(dead_code)] system_contracts_options: system_contracts::Options, } @@ -1045,7 +849,7 @@ fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs) impl Default for InMemoryNode { fn default() -> Self { - InMemoryNode::new(None, None, InMemoryNodeConfig::default()) + InMemoryNode::new(None, None, InMemoryNodeConfig::default(), None) } } @@ -1054,9 +858,10 @@ impl InMemoryNode { fork: Option, observability: Option, config: InMemoryNodeConfig, + gas_overrides: Option, ) -> Self { - let system_contracts_options = config.system_contracts_options.clone(); - let inner = InMemoryNodeInner::new(fork, observability, config); + let system_contracts_options = config.system_contracts_options; + let inner = InMemoryNodeInner::new(fork, observability, config, gas_overrides); InMemoryNode { inner: Arc::new(RwLock::new(inner)), snapshots: Default::default(), @@ -1084,26 +889,29 @@ impl InMemoryNode { inner.fork_storage.get_fork_url() } - fn get_config(&self, l2_gas_price_override: Option) -> Result { + fn get_config(&self) -> Result { let inner = self .inner .read() .map_err(|e| format!("Failed to acquire read lock: {}", e))?; - let l2_gas_price = if let Some(l2_gas_price) = l2_gas_price_override { - l2_gas_price - } else { - inner.fee_input_provider.l2_gas_price - }; - Ok(InMemoryNodeConfig { - l1_gas_price: Some(inner.fee_input_provider.l1_gas_price), - l2_fair_gas_price: l2_gas_price, - show_calls: inner.show_calls.clone(), - show_outputs: inner.show_outputs, - show_storage_logs: inner.show_storage_logs.clone(), - show_vm_details: inner.show_vm_details.clone(), - show_gas_details: inner.show_gas_details.clone(), - resolve_hashes: inner.resolve_hashes, - system_contracts_options: self.system_contracts_options.clone(), + + Ok(inner.config) + } + + fn get_gas_values(&self) -> Result { + let inner = self + .inner + .read() + .map_err(|e| format!("Failed to acquire read lock: {}", e))?; + + let fee_input_provider = &inner.fee_input_provider; + Ok(GasConfig { + l1_gas_price: Some(fee_input_provider.l1_gas_price), + l2_gas_price: Some(fee_input_provider.l2_gas_price), + estimation: Some(gas::Estimation { + price_scale_factor: Some(fee_input_provider.estimate_gas_price_scale_factor), + limit_scale_factor: Some(fee_input_provider.estimate_gas_scale_factor), + }), }) } @@ -1115,10 +923,9 @@ impl InMemoryNode { .observability .clone(); - let l2_gas_price = fork.as_ref().map(|f| f.l2_fair_gas_price); - let config = self.get_config(l2_gas_price)?; - - let inner = InMemoryNodeInner::new(fork, observability, config); + let config = self.get_config()?; + let gas_values = self.get_gas_values()?; + let inner = InMemoryNodeInner::new(fork, observability, config, Some(gas_values)); let mut writer = self .snapshots @@ -1232,9 +1039,9 @@ impl InMemoryNode { formatter::print_call( call, 0, - &inner.show_calls, - inner.show_outputs, - inner.resolve_hashes, + &inner.config.show_calls, + inner.config.show_outputs, + inner.config.resolve_hashes, ); } @@ -1544,7 +1351,7 @@ impl InMemoryNode { to_human_size(tx_result.refunds.gas_refunded.into()) ); - match inner.show_gas_details { + match inner.config.show_gas_details { ShowGasDetails::None => tracing::info!( "Use --show-gas-details flag or call config_setShowGasDetails to display more info" ), @@ -1561,11 +1368,11 @@ impl InMemoryNode { } } - if inner.show_storage_logs != ShowStorageLogs::None { - print_storage_logs_details(&inner.show_storage_logs, &tx_result); + if inner.config.show_storage_logs != ShowStorageLogs::None { + print_storage_logs_details(&inner.config.show_storage_logs, &tx_result); } - if inner.show_vm_details != ShowVMDetails::None { + if inner.config.show_vm_details != ShowVMDetails::None { formatter::print_vm_details(&tx_result); } @@ -1586,14 +1393,14 @@ impl InMemoryNode { format!("{:?} call traces. ", call_traces_count).bold() ); - if inner.show_calls != ShowCalls::None { + if inner.config.show_calls != ShowCalls::None { for call in call_traces { formatter::print_call( call, 0, - &inner.show_calls, - inner.show_outputs, - inner.resolve_hashes, + &inner.config.show_calls, + inner.config.show_outputs, + inner.config.resolve_hashes, ); } } @@ -1603,7 +1410,7 @@ impl InMemoryNode { format!("{} events", tx_result.logs.events.len()).bold() ); for event in &tx_result.logs.events { - formatter::print_event(event, inner.resolve_hashes); + formatter::print_event(event, inner.config.resolve_hashes); } // The computed block hash here will be different than that in production. @@ -1923,7 +1730,17 @@ mod tests { use super::*; use crate::{ - http_fork_source::HttpForkSource, node::InMemoryNode, system_contracts::Options, testing, + config::{ + gas::{ + DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, + DEFAULT_L2_GAS_PRICE, + }, + node::InMemoryNodeConfig, + }, + http_fork_source::HttpForkSource, + node::InMemoryNode, + system_contracts::Options, + testing, }; #[tokio::test] @@ -2033,6 +1850,7 @@ mod tests { }), None, Default::default(), + Default::default(), ); node.run_l2_tx_raw( @@ -2053,6 +1871,7 @@ mod tests { system_contracts_options: Options::BuiltInWithoutSecurity, ..Default::default() }, + Default::default(), ); let private_key = K256PrivateKey::from_bytes(H256::repeat_byte(0xef)).unwrap(); diff --git a/src/node/mod.rs b/src/node/mod.rs index b2dab7c5..c0e434f5 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,6 +1,6 @@ //! In-memory node, that supports forking other networks. -mod config; +mod config_api; mod debug; mod eth; mod evm; diff --git a/src/node/storage_logs.rs b/src/node/storage_logs.rs index 2662da66..4a256812 100644 --- a/src/node/storage_logs.rs +++ b/src/node/storage_logs.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::formatter::{self, PubdataBytesInfo}; -use super::ShowStorageLogs; +use crate::config::node::ShowStorageLogs; use multivm::vm_latest::VmExecutionResultAndLogs; use zksync_basic_types::AccountTreeId; use zksync_types::{ diff --git a/src/node/zks.rs b/src/node/zks.rs index 8894b938..1de91f52 100644 --- a/src/node/zks.rs +++ b/src/node/zks.rs @@ -568,7 +568,7 @@ impl ZksNamespa mod tests { use std::str::FromStr; - use crate::cache::CacheConfig; + use crate::config::cache::CacheConfig; use crate::fork::ForkDetails; use crate::node::TEST_NODE_NETWORK_ID; use crate::testing; @@ -732,6 +732,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let result = node @@ -817,6 +818,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let result = node @@ -892,6 +894,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual_bridge_addresses = node @@ -955,6 +958,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let actual = node @@ -1076,6 +1080,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await), None, Default::default(), + Default::default(), ); let txns = node @@ -1242,6 +1247,7 @@ mod tests { Some(ForkDetails::from_network(&mock_server.url(), Some(1), CacheConfig::None).await), None, Default::default(), + Default::default(), ); { let inner = node.get_inner(); diff --git a/src/observability.rs b/src/observability.rs index bcd32176..7fdb03ba 100644 --- a/src/observability.rs +++ b/src/observability.rs @@ -8,11 +8,12 @@ use tracing_subscriber::{ }; /// Log filter level for the node. -#[derive(Debug, Clone, ValueEnum, Serialize, Deserialize)] +#[derive(Default, Debug, Copy, Clone, ValueEnum, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum LogLevel { Trace, Debug, + #[default] Info, Warn, Error, diff --git a/src/system_contracts.rs b/src/system_contracts.rs index 34f1d7fe..45fe81e6 100644 --- a/src/system_contracts.rs +++ b/src/system_contracts.rs @@ -1,4 +1,5 @@ use multivm::interface::TxExecutionMode; +use serde::Deserialize; use zksync_contracts::{ read_sys_contract_bytecode, read_zbin_bytecode, BaseSystemContracts, ContractLanguage, SystemContractCode, @@ -8,7 +9,7 @@ use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words}; use crate::deps::system_contracts::{bytecode_from_slice, COMPILED_IN_SYSTEM_CONTRACTS}; -#[derive(Default, Debug, Clone)] +#[derive(Deserialize, Default, Debug, Copy, Clone)] pub enum Options { // Use the compiled-in contracts #[default]