diff --git a/Cargo.lock b/Cargo.lock index c6c53afa5b..9570b8bce1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8104,6 +8104,7 @@ dependencies = [ "anyhow", "assert_matches", "byte-unit", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?rev=5c2616c273faca7700d2ba565503fcefb5b9d720)", "clap", "clap_complete", "comfy-table", @@ -8114,10 +8115,12 @@ dependencies = [ "katana-node", "katana-primitives", "katana-slot-controller", + "serde", "serde_json", "shellexpand", "starknet 0.12.0", "tokio", + "toml 0.8.19", "tracing", "tracing-log 0.1.4", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 03eb8f64cf..b0aba1360c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ lto = "fat" [workspace.dependencies] cainome = { git = "https://github.com/cartridge-gg/cainome", rev = "5c2616c273faca7700d2ba565503fcefb5b9d720", features = [ "abigen-rs" ] } +cainome-cairo-serde = { git = "https://github.com/cartridge-gg/cainome", rev = "5c2616c273faca7700d2ba565503fcefb5b9d720" } dojo-utils = { path = "crates/dojo/utils" } # metrics diff --git a/bin/katana/Cargo.toml b/bin/katana/Cargo.toml index 8421bfe09c..dcb52e154d 100644 --- a/bin/katana/Cargo.toml +++ b/bin/katana/Cargo.toml @@ -16,14 +16,17 @@ katana-slot-controller = { workspace = true, optional = true } alloy-primitives.workspace = true anyhow.workspace = true byte-unit = "5.1.4" +cainome-cairo-serde.workspace = true clap.workspace = true clap_complete.workspace = true comfy-table = "7.1.1" console.workspace = true dojo-utils.workspace = true +serde.workspace = true serde_json.workspace = true shellexpand = "3.1.0" tokio.workspace = true +toml.workspace = true tracing.workspace = true tracing-log.workspace = true tracing-subscriber.workspace = true diff --git a/bin/katana/src/cli/mod.rs b/bin/katana/src/cli/mod.rs index 90041a21a0..04aa84992e 100644 --- a/bin/katana/src/cli/mod.rs +++ b/bin/katana/src/cli/mod.rs @@ -1,5 +1,6 @@ mod db; mod node; +mod options; use anyhow::Result; use clap::{Args, CommandFactory, Parser, Subcommand}; @@ -25,7 +26,7 @@ impl Cli { }; } - self.node.execute() + self.node.with_config_file()?.execute() } } diff --git a/bin/katana/src/cli/node.rs b/bin/katana/src/cli/node.rs index 60387c1882..c187f4fe59 100644 --- a/bin/katana/src/cli/node.rs +++ b/bin/katana/src/cli/node.rs @@ -11,28 +11,21 @@ //! for more info. use std::collections::HashSet; -use std::net::IpAddr; use std::path::PathBuf; use alloy_primitives::U256; use anyhow::{Context, Result}; -use clap::{Args, Parser}; +use clap::Parser; use console::Style; use katana_core::constants::DEFAULT_SEQUENCER_ADDRESS; use katana_core::service::messaging::MessagingConfig; use katana_node::config::db::DbConfig; use katana_node::config::dev::{DevConfig, FixedL1GasPriceConfig}; -use katana_node::config::execution::{ - ExecutionConfig, DEFAULT_INVOCATION_MAX_STEPS, DEFAULT_VALIDATION_MAX_STEPS, -}; +use katana_node::config::execution::ExecutionConfig; use katana_node::config::fork::ForkingConfig; -use katana_node::config::metrics::{MetricsConfig, DEFAULT_METRICS_ADDR, DEFAULT_METRICS_PORT}; -use katana_node::config::rpc::{ - ApiKind, RpcConfig, DEFAULT_RPC_ADDR, DEFAULT_RPC_MAX_CONNECTIONS, DEFAULT_RPC_PORT, -}; +use katana_node::config::metrics::MetricsConfig; +use katana_node::config::rpc::{ApiKind, RpcConfig}; use katana_node::config::{Config, SequencingConfig}; -use katana_primitives::block::BlockHashOrNumber; -use katana_primitives::chain::ChainId; use katana_primitives::chain_spec::{self, ChainSpec}; use katana_primitives::class::ClassHash; use katana_primitives::contract::ContractAddress; @@ -41,15 +34,15 @@ use katana_primitives::genesis::constant::{ DEFAULT_LEGACY_ERC20_CLASS_HASH, DEFAULT_LEGACY_UDC_CLASS_HASH, DEFAULT_PREFUNDED_ACCOUNT_BALANCE, DEFAULT_UDC_ADDRESS, }; -use katana_primitives::genesis::Genesis; +use serde::{Deserialize, Serialize}; use tracing::{info, Subscriber}; use tracing_log::LogTracer; use tracing_subscriber::{fmt, EnvFilter}; -use url::Url; -use crate::utils::{parse_block_hash_or_number, parse_genesis, parse_seed, LogFormat}; +use super::options::*; +use crate::utils::{parse_seed, LogFormat}; -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Serialize, Deserialize, Default)] pub struct NodeArgs { /// Don't print anything on startup. #[arg(long)] @@ -106,172 +99,30 @@ pub struct NodeArgs { #[cfg(feature = "slot")] #[command(flatten)] pub slot: SlotOptions, -} -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Metrics options")] -pub struct MetricsOptions { - /// Enable metrics. - /// - /// For now, metrics will still be collected even if this flag is not set. This only - /// controls whether the metrics server is started or not. + /// Configuration file #[arg(long)] - pub metrics: bool, - - /// The metrics will be served at the given address. - #[arg(requires = "metrics")] - #[arg(long = "metrics.addr", value_name = "ADDRESS")] - #[arg(default_value_t = DEFAULT_METRICS_ADDR)] - pub metrics_addr: IpAddr, - - /// The metrics will be served at the given port. - #[arg(requires = "metrics")] - #[arg(long = "metrics.port", value_name = "PORT")] - #[arg(default_value_t = DEFAULT_METRICS_PORT)] - pub metrics_port: u16, -} - -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Server options")] -pub struct ServerOptions { - /// HTTP-RPC server listening interface. - #[arg(long = "http.addr", value_name = "ADDRESS")] - #[arg(default_value_t = DEFAULT_RPC_ADDR)] - pub http_addr: IpAddr, - - /// HTTP-RPC server listening port. - #[arg(long = "http.port", value_name = "PORT")] - #[arg(default_value_t = DEFAULT_RPC_PORT)] - pub http_port: u16, - - /// Comma separated list of domains from which to accept cross origin requests. - #[arg(long = "http.corsdomain")] - #[arg(value_delimiter = ',')] - pub http_cors_domain: Option>, - - /// Maximum number of concurrent connections allowed. - #[arg(long = "rpc.max-connections", value_name = "COUNT")] - #[arg(default_value_t = DEFAULT_RPC_MAX_CONNECTIONS)] - pub max_connections: u32, -} - -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Starknet options")] -pub struct StarknetOptions { - #[command(flatten)] - pub environment: EnvironmentOptions, - - #[arg(long)] - #[arg(value_parser = parse_genesis)] - #[arg(conflicts_with_all(["seed", "total_accounts"]))] - pub genesis: Option, -} - -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Environment options")] -pub struct EnvironmentOptions { - /// The chain ID. - /// - /// The chain ID. If a raw hex string (`0x` prefix) is provided, then it'd - /// used as the actual chain ID. Otherwise, it's represented as the raw - /// ASCII values. It must be a valid Cairo short string. - #[arg(long)] - #[arg(value_parser = ChainId::parse)] - pub chain_id: Option, - - /// The maximum number of steps available for the account validation logic. - #[arg(long)] - pub validate_max_steps: Option, - - /// The maximum number of steps available for the account execution logic. - #[arg(long)] - pub invoke_max_steps: Option, -} - -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Development options")] -pub struct DevOptions { - /// Enable development mode. - #[arg(long)] - pub dev: bool, - - /// Specify the seed for randomness of accounts to be predeployed. - #[arg(requires = "dev")] - #[arg(long = "dev.seed", default_value = "0")] - pub seed: String, - - /// Number of pre-funded accounts to generate. - #[arg(requires = "dev")] - #[arg(long = "dev.accounts", value_name = "NUM")] - #[arg(default_value_t = 10)] - pub total_accounts: u16, - - /// Disable charging fee when executing transactions. - #[arg(requires = "dev")] - #[arg(long = "dev.no-fee")] - pub no_fee: bool, - - /// Disable account validation when executing transactions. - /// - /// Skipping the transaction sender's account validation function. - #[arg(requires = "dev")] - #[arg(long = "dev.no-account-validation")] - pub no_account_validation: bool, -} - -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Forking options")] -pub struct ForkingOptions { - /// The RPC URL of the network to fork from. - /// - /// This will operate Katana in forked mode. Continuing from the tip of the forked network, or - /// at a specific block if `fork.block` is provided. - #[arg(long = "fork.provider", value_name = "URL", conflicts_with = "genesis")] - pub fork_provider: Option, - - /// Fork the network at a specific block id, can either be a hash (0x-prefixed) or a block - /// number. - #[arg(long = "fork.block", value_name = "BLOCK", requires = "fork_provider")] - #[arg(value_parser = parse_block_hash_or_number)] - pub fork_block: Option, -} - -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Logging options")] -pub struct LoggingOptions { - /// Log format to use - #[arg(long = "log.format", value_name = "FORMAT")] - #[arg(default_value_t = LogFormat::Full)] - pub log_format: LogFormat, + config: Option, } -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Gas Price Oracle Options")] -pub struct GasPriceOracleOptions { - /// The L1 ETH gas price. (denominated in wei) - #[arg(long = "gpo.l1-eth-gas-price", value_name = "WEI")] - pub l1_eth_gas_price: Option, - - /// The L1 STRK gas price. (denominated in fri) - #[arg(long = "gpo.l1-strk-gas-price", value_name = "FRI")] - pub l1_strk_gas_price: Option, - - /// The L1 ETH data gas price. (denominated in wei) - #[arg(long = "gpo.l1-eth-data-gas-price", value_name = "WEI")] - pub l1_eth_data_gas_price: Option, - - /// The L1 STRK data gas price. (denominated in fri) - #[arg(long = "gpo.l1-strk-data-gas-price", value_name = "FRI")] - pub l1_strk_data_gas_price: Option, -} +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct NodeArgsConfig { + pub silent: Option, + pub no_mining: Option, + pub block_time: Option, + pub db_dir: Option, + pub messaging: Option, + pub logging: Option, + pub metrics: Option, + pub server: Option, + pub starknet: Option, + pub gpo: Option, + pub forking: Option, + #[serde(rename = "dev")] + pub development: Option, -#[cfg(feature = "slot")] -#[derive(Debug, Args, Clone)] -#[command(next_help_heading = "Slot options")] -pub struct SlotOptions { - #[arg(hide = true)] - #[arg(long = "slot.controller")] - pub controller: bool, + #[cfg(feature = "slot")] + pub slot: Option, } pub(crate) const LOG_TARGET: &str = "katana::cli"; @@ -370,7 +221,7 @@ impl NodeArgs { port: self.server.http_port, addr: self.server.http_addr, max_connections: self.server.max_connections, - cors_domain: self.server.http_cors_domain.clone(), + cors_origins: self.server.http_cors_origins.clone(), } } @@ -406,24 +257,24 @@ impl NodeArgs { fn dev_config(&self) -> DevConfig { let mut fixed_gas_prices = None; - if let Some(price) = self.gpo.l1_eth_gas_price { + if self.gpo.l1_eth_gas_price > 0 { let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); - prices.gas_price.eth = price; + prices.gas_price.eth = self.gpo.l1_eth_gas_price; } - if let Some(price) = self.gpo.l1_strk_gas_price { + if self.gpo.l1_strk_gas_price > 0 { let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); - prices.gas_price.strk = price; + prices.gas_price.strk = self.gpo.l1_strk_gas_price; } - if let Some(price) = self.gpo.l1_eth_data_gas_price { + if self.gpo.l1_eth_data_gas_price > 0 { let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); - prices.data_gas_price.eth = price; + prices.data_gas_price.eth = self.gpo.l1_eth_data_gas_price; } - if let Some(price) = self.gpo.l1_strk_data_gas_price { + if self.gpo.l1_strk_data_gas_price > 0 { let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); - prices.data_gas_price.strk = price; + prices.data_gas_price.strk = self.gpo.l1_strk_data_gas_price; } DevConfig { @@ -435,16 +286,8 @@ impl NodeArgs { fn execution_config(&self) -> ExecutionConfig { ExecutionConfig { - invocation_max_steps: self - .starknet - .environment - .invoke_max_steps - .unwrap_or(DEFAULT_INVOCATION_MAX_STEPS), - validation_max_steps: self - .starknet - .environment - .validate_max_steps - .unwrap_or(DEFAULT_VALIDATION_MAX_STEPS), + invocation_max_steps: self.starknet.environment.invoke_max_steps, + validation_max_steps: self.starknet.environment.validate_max_steps, ..Default::default() } } @@ -469,6 +312,87 @@ impl NodeArgs { None } } + + /// Parse the node config from the command line arguments and the config file, + /// and merge them together prioritizing the command line arguments. + pub fn with_config_file(mut self) -> Result { + let config: NodeArgsConfig = if let Some(path) = &self.config { + toml::from_str(&std::fs::read_to_string(path)?)? + } else { + return Ok(self); + }; + + // the CLI (self) takes precedence over the config file. + // Currently, the merge is made at the top level of the commands. + // We may add recursive merging in the future. + + if !self.silent { + self.silent = config.silent.unwrap_or_default(); + } + + if !self.no_mining { + self.no_mining = config.no_mining.unwrap_or_default(); + } + + if self.block_time.is_none() { + self.block_time = config.block_time; + } + + if self.db_dir.is_none() { + self.db_dir = config.db_dir; + } + + if self.logging == LoggingOptions::default() { + if let Some(logging) = config.logging { + self.logging = logging; + } + } + + if self.metrics == MetricsOptions::default() { + if let Some(metrics) = config.metrics { + self.metrics = metrics; + } + } + + if self.server == ServerOptions::default() { + if let Some(server) = config.server { + self.server = server; + } + } + + if self.starknet == StarknetOptions::default() { + if let Some(starknet) = config.starknet { + self.starknet = starknet; + } + } + + if self.gpo == GasPriceOracleOptions::default() { + if let Some(gpo) = config.gpo { + self.gpo = gpo; + } + } + + if self.forking == ForkingOptions::default() { + if let Some(forking) = config.forking { + self.forking = forking; + } + } + + if self.development == DevOptions::default() { + if let Some(development) = config.development { + self.development = development; + } + } + + #[cfg(feature = "slot")] + if self.slot == SlotOptions::default() { + if let Some(slot) = config.slot { + self.slot = slot; + } + } + + Ok(self) + } } fn print_intro(args: &NodeArgs, chain: &ChainSpec) { @@ -590,6 +514,10 @@ mod test { DEFAULT_ETH_L1_DATA_GAS_PRICE, DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_STRK_L1_DATA_GAS_PRICE, DEFAULT_STRK_L1_GAS_PRICE, }; + use katana_node::config::execution::{ + DEFAULT_INVOCATION_MAX_STEPS, DEFAULT_VALIDATION_MAX_STEPS, + }; + use katana_primitives::chain::ChainId; use katana_primitives::{address, felt}; use super::*; @@ -750,4 +678,59 @@ mod test { assert_eq!(prices.data_gas_price.strk, 222); }) } + + #[test] + fn config_from_file_and_cli() { + // CLI args must take precedence over the config file. + let content = r#" +[gpo] +l1_eth_gas_price = "0xfe" +l1_strk_gas_price = "200" +l1_eth_data_gas_price = "111" +l1_strk_data_gas_price = "222" + +[dev] +total_accounts = 20 + +[starknet.env] +validate_max_steps = 500 + "#; + let path = std::env::temp_dir().join("katana-config.json"); + std::fs::write(&path, content).unwrap(); + + let path_str = path.to_string_lossy().to_string(); + + let args = vec![ + "katana", + "--config", + path_str.as_str(), + "--genesis", + "./tests/test-data/genesis.json", + "--validate-max-steps", + "1234", + "--dev", + "--dev.no-fee", + ]; + + let config = + NodeArgs::parse_from(args.clone()).with_config_file().unwrap().config().unwrap(); + + assert_eq!(config.execution.validation_max_steps, 1234); + assert_eq!(config.execution.invocation_max_steps, 10_000_000); + assert_eq!(config.chain.id, ChainId::parse("KATANA").unwrap()); + assert!(!config.dev.fee); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, 254); + assert_eq!(prices.gas_price.strk, 200); + assert_eq!(prices.data_gas_price.eth, 111); + assert_eq!(prices.data_gas_price.strk, 222); + }); + assert_eq!(config.chain.genesis.number, 0); + assert_eq!(config.chain.genesis.parent_hash, felt!("0x999")); + assert_eq!(config.chain.genesis.timestamp, 5123512314); + assert_eq!(config.chain.genesis.state_root, felt!("0x99")); + assert_eq!(config.chain.genesis.sequencer_address, address!("0x100")); + assert_eq!(config.chain.genesis.gas_prices.eth, 9999); + assert_eq!(config.chain.genesis.gas_prices.strk, 8888); + } } diff --git a/bin/katana/src/cli/options.rs b/bin/katana/src/cli/options.rs new file mode 100644 index 0000000000..ba5bffd04e --- /dev/null +++ b/bin/katana/src/cli/options.rs @@ -0,0 +1,274 @@ +//! Options related to the CLI and the configuration file parsing. +//! +//! The clap args are first parsed, then the configuration file is parsed. +//! If no configuration file is provided, the default values are used form the clap args. +//! If a configuration file is provided, the values are merged with the clap args, however, the clap +//! args keep the precedence. +//! +//! Currently, the merge is made at the top level of the commands. + +use std::net::IpAddr; + +use clap::Args; +use katana_node::config::execution::{DEFAULT_INVOCATION_MAX_STEPS, DEFAULT_VALIDATION_MAX_STEPS}; +use katana_node::config::metrics::{DEFAULT_METRICS_ADDR, DEFAULT_METRICS_PORT}; +use katana_node::config::rpc::{DEFAULT_RPC_ADDR, DEFAULT_RPC_MAX_CONNECTIONS, DEFAULT_RPC_PORT}; +use katana_primitives::block::BlockHashOrNumber; +use katana_primitives::chain::ChainId; +use katana_primitives::genesis::Genesis; +use serde::{Deserialize, Serialize}; +use url::Url; + +use crate::utils::{parse_block_hash_or_number, parse_genesis, LogFormat}; + +const DEFAULT_DEV_SEED: &str = "0"; +const DEFAULT_DEV_ACCOUNTS: u16 = 10; + +#[derive(Debug, Args, Clone, Serialize, Deserialize, PartialEq)] +#[command(next_help_heading = "Metrics options")] +pub struct MetricsOptions { + /// Enable metrics. + /// + /// For now, metrics will still be collected even if this flag is not set. This only + /// controls whether the metrics server is started or not. + #[arg(long)] + pub metrics: bool, + + /// The metrics will be served at the given address. + #[arg(requires = "metrics")] + #[arg(long = "metrics.addr", value_name = "ADDRESS")] + #[arg(default_value_t = DEFAULT_METRICS_ADDR)] + pub metrics_addr: IpAddr, + + /// The metrics will be served at the given port. + #[arg(requires = "metrics")] + #[arg(long = "metrics.port", value_name = "PORT")] + #[arg(default_value_t = DEFAULT_METRICS_PORT)] + pub metrics_port: u16, +} + +impl Default for MetricsOptions { + fn default() -> Self { + MetricsOptions { + metrics: false, + metrics_addr: DEFAULT_METRICS_ADDR, + metrics_port: DEFAULT_METRICS_PORT, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, PartialEq)] +#[command(next_help_heading = "Server options")] +pub struct ServerOptions { + /// HTTP-RPC server listening interface. + #[arg(long = "http.addr", value_name = "ADDRESS")] + #[arg(default_value_t = DEFAULT_RPC_ADDR)] + pub http_addr: IpAddr, + + /// HTTP-RPC server listening port. + #[arg(long = "http.port", value_name = "PORT")] + #[arg(default_value_t = DEFAULT_RPC_PORT)] + pub http_port: u16, + + /// Comma separated list of domains from which to accept cross origin requests. + #[arg(long = "http.corsdomain")] + #[arg(value_delimiter = ',')] + pub http_cors_origins: Option>, + + /// Maximum number of concurrent connections allowed. + #[arg(long = "rpc.max-connections", value_name = "COUNT")] + #[arg(default_value_t = DEFAULT_RPC_MAX_CONNECTIONS)] + pub max_connections: u32, +} + +impl Default for ServerOptions { + fn default() -> Self { + ServerOptions { + http_addr: DEFAULT_RPC_ADDR, + http_port: DEFAULT_RPC_PORT, + max_connections: DEFAULT_RPC_MAX_CONNECTIONS, + http_cors_origins: None, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default, PartialEq)] +#[command(next_help_heading = "Starknet options")] +pub struct StarknetOptions { + #[command(flatten)] + #[serde(rename = "env")] + pub environment: EnvironmentOptions, + + #[arg(long)] + #[arg(value_parser = parse_genesis)] + #[arg(conflicts_with_all(["seed", "total_accounts"]))] + pub genesis: Option, +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, PartialEq)] +#[command(next_help_heading = "Environment options")] +pub struct EnvironmentOptions { + /// The chain ID. + /// + /// The chain ID. If a raw hex string (`0x` prefix) is provided, then it'd + /// used as the actual chain ID. Otherwise, it's represented as the raw + /// ASCII values. It must be a valid Cairo short string. + #[arg(long)] + #[arg(value_parser = ChainId::parse)] + pub chain_id: Option, + + /// The maximum number of steps available for the account validation logic. + #[arg(long)] + #[arg(default_value_t = DEFAULT_VALIDATION_MAX_STEPS)] + #[serde(default = "default_validate_max_steps")] + pub validate_max_steps: u32, + + /// The maximum number of steps available for the account execution logic. + #[arg(long)] + #[arg(default_value_t = DEFAULT_INVOCATION_MAX_STEPS)] + #[serde(default = "default_invoke_max_steps")] + pub invoke_max_steps: u32, +} + +impl Default for EnvironmentOptions { + fn default() -> Self { + EnvironmentOptions { + validate_max_steps: DEFAULT_VALIDATION_MAX_STEPS, + invoke_max_steps: DEFAULT_INVOCATION_MAX_STEPS, + chain_id: None, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, PartialEq)] +#[command(next_help_heading = "Development options")] +#[serde(rename = "dev")] +pub struct DevOptions { + /// Enable development mode. + #[arg(long)] + #[serde(default)] + pub dev: bool, + + /// Specify the seed for randomness of accounts to be predeployed. + #[arg(requires = "dev")] + #[arg(long = "dev.seed", default_value = DEFAULT_DEV_SEED)] + #[serde(default = "default_seed")] + pub seed: String, + + /// Number of pre-funded accounts to generate. + #[arg(requires = "dev")] + #[arg(long = "dev.accounts", value_name = "NUM")] + #[arg(default_value_t = DEFAULT_DEV_ACCOUNTS)] + #[serde(default = "default_accounts")] + pub total_accounts: u16, + + /// Disable charging fee when executing transactions. + #[arg(requires = "dev")] + #[arg(long = "dev.no-fee")] + #[serde(default)] + pub no_fee: bool, + + /// Disable account validation when executing transactions. + /// + /// Skipping the transaction sender's account validation function. + #[arg(requires = "dev")] + #[arg(long = "dev.no-account-validation")] + #[serde(default)] + pub no_account_validation: bool, +} + +impl Default for DevOptions { + fn default() -> Self { + DevOptions { + dev: false, + seed: DEFAULT_DEV_SEED.to_string(), + total_accounts: DEFAULT_DEV_ACCOUNTS, + no_fee: false, + no_account_validation: false, + } + } +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default, PartialEq)] +#[command(next_help_heading = "Forking options")] +pub struct ForkingOptions { + /// The RPC URL of the network to fork from. + /// + /// This will operate Katana in forked mode. Continuing from the tip of the forked network, or + /// at a specific block if `fork.block` is provided. + #[arg(long = "fork.provider", value_name = "URL", conflicts_with = "genesis")] + pub fork_provider: Option, + + /// Fork the network at a specific block id, can either be a hash (0x-prefixed) or a block + /// number. + #[arg(long = "fork.block", value_name = "BLOCK", requires = "fork_provider")] + #[arg(value_parser = parse_block_hash_or_number)] + pub fork_block: Option, +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default, PartialEq)] +#[command(next_help_heading = "Logging options")] +pub struct LoggingOptions { + /// Log format to use + #[arg(long = "log.format", value_name = "FORMAT")] + #[arg(default_value_t = LogFormat::Full)] + pub log_format: LogFormat, +} + +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default, PartialEq)] +#[command(next_help_heading = "Gas Price Oracle Options")] +pub struct GasPriceOracleOptions { + /// The L1 ETH gas price. (denominated in wei) + #[arg(long = "gpo.l1-eth-gas-price", value_name = "WEI")] + #[arg(default_value_t = 0)] + #[serde(serialize_with = "cainome_cairo_serde::serialize_as_hex")] + #[serde(deserialize_with = "cainome_cairo_serde::deserialize_from_hex")] + pub l1_eth_gas_price: u128, + + /// The L1 STRK gas price. (denominated in fri) + #[arg(long = "gpo.l1-strk-gas-price", value_name = "FRI")] + #[arg(default_value_t = 0)] + #[serde(serialize_with = "cainome_cairo_serde::serialize_as_hex")] + #[serde(deserialize_with = "cainome_cairo_serde::deserialize_from_hex")] + pub l1_strk_gas_price: u128, + + /// The L1 ETH data gas price. (denominated in wei) + #[arg(long = "gpo.l1-eth-data-gas-price", value_name = "WEI")] + #[arg(default_value_t = 0)] + #[serde(serialize_with = "cainome_cairo_serde::serialize_as_hex")] + #[serde(deserialize_with = "cainome_cairo_serde::deserialize_from_hex")] + pub l1_eth_data_gas_price: u128, + + /// The L1 STRK data gas price. (denominated in fri) + #[arg(long = "gpo.l1-strk-data-gas-price", value_name = "FRI")] + #[arg(default_value_t = 0)] + #[serde(serialize_with = "cainome_cairo_serde::serialize_as_hex")] + #[serde(deserialize_with = "cainome_cairo_serde::deserialize_from_hex")] + pub l1_strk_data_gas_price: u128, +} + +#[cfg(feature = "slot")] +#[derive(Debug, Args, Clone, Serialize, Deserialize, Default, PartialEq)] +#[command(next_help_heading = "Slot options")] +pub struct SlotOptions { + #[arg(hide = true)] + #[arg(long = "slot.controller")] + pub controller: bool, +} + +// ** Default functions to setup serde of the configuration file ** +fn default_seed() -> String { + DEFAULT_DEV_SEED.to_string() +} + +fn default_accounts() -> u16 { + DEFAULT_DEV_ACCOUNTS +} + +fn default_validate_max_steps() -> u32 { + DEFAULT_VALIDATION_MAX_STEPS +} + +fn default_invoke_max_steps() -> u32 { + DEFAULT_INVOCATION_MAX_STEPS +} diff --git a/bin/katana/src/utils.rs b/bin/katana/src/utils.rs index d580eada96..394ae8e596 100644 --- a/bin/katana/src/utils.rs +++ b/bin/katana/src/utils.rs @@ -7,6 +7,7 @@ use clap::ValueEnum; use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockNumber}; use katana_primitives::genesis::json::GenesisJson; use katana_primitives::genesis::Genesis; +use serde::{Deserialize, Serialize}; pub fn parse_seed(seed: &str) -> [u8; 32] { let seed = seed.as_bytes(); @@ -37,9 +38,10 @@ pub fn parse_block_hash_or_number(value: &str) -> Result { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)] pub enum LogFormat { Json, + #[default] Full, } diff --git a/crates/dojo/test-utils/src/sequencer.rs b/crates/dojo/test-utils/src/sequencer.rs index dbc4f661ef..290242c2bf 100644 --- a/crates/dojo/test-utils/src/sequencer.rs +++ b/crates/dojo/test-utils/src/sequencer.rs @@ -118,7 +118,7 @@ pub fn get_default_test_config(sequencing: SequencingConfig) -> Config { chain.genesis.sequencer_address = *DEFAULT_SEQUENCER_ADDRESS; let rpc = RpcConfig { - cors_domain: None, + cors_origins: None, port: 0, addr: DEFAULT_RPC_ADDR, max_connections: DEFAULT_RPC_MAX_CONNECTIONS, diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index cd064f44be..88423a219b 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -51,7 +51,7 @@ use futures::StreamExt; use katana_executor::ExecutorFactory; use katana_primitives::chain::ChainId; use katana_primitives::receipt::MessageToL1; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tracing::{error, info}; pub use self::service::{MessagingOutcome, MessagingService}; @@ -94,7 +94,7 @@ impl From for Error { } /// The config used to initialize the messaging service. -#[derive(Debug, Default, Deserialize, Clone)] +#[derive(Debug, Default, Deserialize, Clone, Serialize)] pub struct MessagingConfig { /// The settlement chain. pub chain: String, diff --git a/crates/katana/node/src/config/metrics.rs b/crates/katana/node/src/config/metrics.rs index 9830660485..ddceb84805 100644 --- a/crates/katana/node/src/config/metrics.rs +++ b/crates/katana/node/src/config/metrics.rs @@ -6,7 +6,7 @@ pub const DEFAULT_METRICS_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); pub const DEFAULT_METRICS_PORT: u16 = 9100; /// Node metrics configurations. -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct MetricsConfig { /// The address to bind the metrics server to. pub addr: IpAddr, diff --git a/crates/katana/node/src/config/rpc.rs b/crates/katana/node/src/config/rpc.rs index a0806847d4..0a71d84505 100644 --- a/crates/katana/node/src/config/rpc.rs +++ b/crates/katana/node/src/config/rpc.rs @@ -24,7 +24,7 @@ pub struct RpcConfig { pub port: u16, pub max_connections: u32, pub apis: HashSet, - pub cors_domain: Option>, + pub cors_origins: Option>, } impl RpcConfig { @@ -37,7 +37,7 @@ impl RpcConfig { impl Default for RpcConfig { fn default() -> Self { Self { - cors_domain: None, + cors_origins: None, addr: DEFAULT_RPC_ADDR, port: DEFAULT_RPC_PORT, max_connections: DEFAULT_RPC_MAX_CONNECTIONS, diff --git a/crates/katana/node/src/lib.rs b/crates/katana/node/src/lib.rs index b120701be2..233aa5d242 100644 --- a/crates/katana/node/src/lib.rs +++ b/crates/katana/node/src/lib.rs @@ -313,19 +313,20 @@ pub async fn spawn( .allow_methods([Method::POST, Method::GET]) .allow_headers([hyper::header::CONTENT_TYPE, "argent-client".parse().unwrap(), "argent-version".parse().unwrap()]); - let cors = config.cors_domain.clone().map(|allowed_origins| match allowed_origins.as_slice() { - [origin] if origin == "*" => cors.allow_origin(AllowOrigin::mirror_request()), - origins => cors.allow_origin( - origins - .iter() - .map(|o| { - let _ = o.parse::().expect("Invalid URI"); - - o.parse().expect("Invalid origin") - }) - .collect::>(), - ), - }); + let cors = + config.cors_origins.clone().map(|allowed_origins| match allowed_origins.as_slice() { + [origin] if origin == "*" => cors.allow_origin(AllowOrigin::mirror_request()), + origins => cors.allow_origin( + origins + .iter() + .map(|o| { + let _ = o.parse::().expect("Invalid URI"); + + o.parse().expect("Invalid origin") + }) + .collect::>(), + ), + }); let middleware = tower::ServiceBuilder::new() .option_layer(cors) diff --git a/crates/katana/primitives/src/block.rs b/crates/katana/primitives/src/block.rs index 4d1b989679..f78982eaa6 100644 --- a/crates/katana/primitives/src/block.rs +++ b/crates/katana/primitives/src/block.rs @@ -15,7 +15,7 @@ pub type BlockNumber = u64; /// Block hash type. pub type BlockHash = Felt; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BlockHashOrNumber { Hash(BlockHash), diff --git a/crates/katana/primitives/src/genesis/mod.rs b/crates/katana/primitives/src/genesis/mod.rs index 559099378c..ca391bfa5a 100644 --- a/crates/katana/primitives/src/genesis/mod.rs +++ b/crates/katana/primitives/src/genesis/mod.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use constant::DEFAULT_ACCOUNT_CLASS; #[cfg(feature = "slot")] use constant::{CONTROLLER_ACCOUNT_CLASS, CONTROLLER_ACCOUNT_CLASS_CASM, CONTROLLER_CLASS_HASH}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use self::allocation::{GenesisAccountAlloc, GenesisAllocation, GenesisContractAlloc}; use self::constant::{ @@ -23,7 +23,7 @@ use crate::class::{ClassHash, CompiledClass, CompiledClassHash, FlattenedSierraC use crate::contract::ContractAddress; use crate::Felt; -#[derive(Clone, Serialize, PartialEq, Eq)] +#[derive(Clone, Serialize, PartialEq, Eq, Deserialize)] pub struct GenesisClass { /// The compiled class hash of the contract class. pub compiled_class_hash: CompiledClassHash, @@ -47,7 +47,7 @@ impl core::fmt::Debug for GenesisClass { /// Genesis block configuration. #[serde_with::serde_as] -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Genesis { /// The genesis block parent hash. pub parent_hash: BlockHash,