diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c7ba11e6f..81b6f52e9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -193,7 +193,7 @@ jobs: sudo apt update && sudo apt install ./hurl_3.0.0_amd64.deb chmod +x /tmp/bins/katana chmod +x /tmp/bins/sozo - nohup /tmp/bins/katana --accounts 2 --disable-fee & + nohup /tmp/bins/katana --dev --dev.accounts 2 --dev.no-fee & - run: | /tmp/bins/sozo --manifest-path examples/spawn-and-move/Scarb.toml build /tmp/bins/sozo --manifest-path examples/spawn-and-move/Scarb.toml migrate diff --git a/bin/katana/src/cli/node.rs b/bin/katana/src/cli/node.rs index f06fb84db4..60387c1882 100644 --- a/bin/katana/src/cli/node.rs +++ b/bin/katana/src/cli/node.rs @@ -11,14 +11,13 @@ //! for more info. use std::collections::HashSet; -use std::net::{IpAddr, SocketAddr}; +use std::net::IpAddr; use std::path::PathBuf; use alloy_primitives::U256; use anyhow::{Context, Result}; use clap::{Args, Parser}; use console::Style; -use dojo_utils::parse::parse_socket_address; use katana_core::constants::DEFAULT_SEQUENCER_ADDRESS; use katana_core::service::messaging::MessagingConfig; use katana_node::config::db::DbConfig; @@ -27,12 +26,12 @@ use katana_node::config::execution::{ ExecutionConfig, DEFAULT_INVOCATION_MAX_STEPS, DEFAULT_VALIDATION_MAX_STEPS, }; use katana_node::config::fork::ForkingConfig; -use katana_node::config::metrics::MetricsConfig; +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::{Config, SequencingConfig}; -use katana_primitives::block::{BlockHashOrNumber, GasPrices}; +use katana_primitives::block::BlockHashOrNumber; use katana_primitives::chain::ChainId; use katana_primitives::chain_spec::{self, ChainSpec}; use katana_primitives::class::ClassHash; @@ -48,174 +47,227 @@ use tracing_log::LogTracer; use tracing_subscriber::{fmt, EnvFilter}; use url::Url; -use crate::utils::{parse_block_hash_or_number, parse_genesis, parse_seed}; +use crate::utils::{parse_block_hash_or_number, parse_genesis, parse_seed, LogFormat}; #[derive(Parser, Debug)] pub struct NodeArgs { + /// Don't print anything on startup. #[arg(long)] - #[arg(help = "Don't print anything on startup.")] pub silent: bool, + /// Disable auto and interval mining, and mine on demand instead via an endpoint. #[arg(long)] #[arg(conflicts_with = "block_time")] - #[arg(help = "Disable auto and interval mining, and mine on demand instead via an endpoint.")] pub no_mining: bool, + /// Block time in milliseconds for interval mining. #[arg(short, long)] #[arg(value_name = "MILLISECONDS")] - #[arg(help = "Block time in milliseconds for interval mining.")] pub block_time: Option, + /// Directory path of the database to initialize from. + /// + /// The path must either be an empty directory or a directory which already contains a + /// previously initialized Katana database. #[arg(long)] #[arg(value_name = "PATH")] - #[arg(help = "Directory path of the database to initialize from.")] - #[arg(long_help = "Directory path of the database to initialize from. The path must either \ - be an empty directory or a directory which already contains a previously \ - initialized Katana database.")] pub db_dir: Option, - #[arg(long = "fork.rpc-url", value_name = "URL", alias = "rpc-url")] - #[arg(help = "The Starknet RPC provider to fork the network from.")] - pub fork_rpc_url: Option, - - #[arg(long = "fork.block", value_name = "BLOCK_ID", alias = "fork-block-number")] - #[arg(requires = "fork_rpc_url")] - #[arg(help = "Fork the network at a specific block id, can either be a hash (0x-prefixed) \ - or number.")] - #[arg(value_parser = parse_block_hash_or_number)] - pub fork_block: Option, - - #[arg(long)] - pub dev: bool, - - #[arg(long)] - #[arg(help = "Output logs in JSON format.")] - pub json_log: bool, - - /// Enable Prometheus metrics. + /// Configure the messaging with an other chain. /// - /// The metrics will be served at the given interface and port. - #[arg(long, value_name = "SOCKET", value_parser = parse_socket_address, help_heading = "Metrics")] - pub metrics: Option, - + /// Configure the messaging to allow Katana listening/sending messages on a + /// settlement chain that can be Ethereum or an other Starknet sequencer. #[arg(long)] #[arg(value_name = "PATH")] #[arg(value_parser = katana_core::service::messaging::MessagingConfig::parse)] - #[arg(help = "Configure the messaging with an other chain.")] - #[arg(long_help = "Configure the messaging to allow Katana listening/sending messages on a \ - settlement chain that can be Ethereum or an other Starknet sequencer. \ - The configuration file details and examples can be found here: https://book.dojoengine.org/toolchain/katana/reference#messaging")] pub messaging: Option, #[command(flatten)] - #[command(next_help_heading = "Server options")] + pub logging: LoggingOptions, + + #[command(flatten)] + pub metrics: MetricsOptions, + + #[command(flatten)] pub server: ServerOptions, #[command(flatten)] - #[command(next_help_heading = "Starknet options")] pub starknet: StarknetOptions, + #[command(flatten)] + pub gpo: GasPriceOracleOptions, + + #[command(flatten)] + pub forking: ForkingOptions, + + #[command(flatten)] + pub development: DevOptions, + #[cfg(feature = "slot")] #[command(flatten)] - #[command(next_help_heading = "Slot options")] 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. + #[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 { - #[arg(short, long)] + /// 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)] - #[arg(help = "Port number to listen on.")] - pub port: u16, + pub http_port: u16, - #[arg(long)] - #[arg(default_value_t = DEFAULT_RPC_ADDR)] - #[arg(help = "The IP address the server will listen on.")] - pub host: IpAddr, + /// Comma separated list of domains from which to accept cross origin requests. + #[arg(long = "http.corsdomain")] + #[arg(value_delimiter = ',')] + pub http_cors_domain: Option>, - #[arg(long)] + /// Maximum number of concurrent connections allowed. + #[arg(long = "rpc.max-connections", value_name = "COUNT")] #[arg(default_value_t = DEFAULT_RPC_MAX_CONNECTIONS)] - #[arg(help = "Maximum number of concurrent connections allowed.")] pub max_connections: u32, - - #[arg(long)] - #[arg(value_delimiter = ',')] - #[arg(help = "Enables the CORS layer and sets the allowed origins, separated by commas.")] - pub allowed_origins: Option>, } #[derive(Debug, Args, Clone)] +#[command(next_help_heading = "Starknet options")] pub struct StarknetOptions { - #[arg(long)] - #[arg(default_value = "0")] - #[arg(help = "Specify the seed for randomness of accounts to be predeployed.")] - pub seed: String, - - #[arg(long = "accounts")] - #[arg(value_name = "NUM")] - #[arg(default_value_t = 10)] - #[arg(help = "Number of pre-funded accounts to generate.")] - pub total_accounts: u16, - - #[arg(long)] - #[arg(help = "Disable charging fee when executing transactions.")] - pub disable_fee: bool, - - #[arg(long)] - #[arg(help = "Disable validation when executing transactions.")] - pub disable_validate: bool, - #[command(flatten)] - #[command(next_help_heading = "Environment options")] pub environment: EnvironmentOptions, #[arg(long)] #[arg(value_parser = parse_genesis)] - #[arg(conflicts_with_all(["fork_rpc_url", "seed", "total_accounts"]))] + #[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(help = "The chain ID.")] - #[arg(long_help = "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(value_parser = ChainId::parse)] pub chain_id: Option, + /// The maximum number of steps available for the account validation logic. #[arg(long)] - #[arg(help = "The maximum number of steps available for the account validation logic.")] pub validate_max_steps: Option, + /// The maximum number of steps available for the account execution logic. #[arg(long)] - #[arg(help = "The maximum number of steps available for the account execution logic.")] 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, +} - #[arg(long = "l1-eth-gas-price", value_name = "WEI")] - #[arg(help = "The L1 ETH gas price. (denominated in wei)")] - #[arg(requires = "l1_strk_gas_price")] +#[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, - #[arg(long = "l1-strk-gas-price", value_name = "FRI")] - #[arg(requires = "l1_eth_data_gas_price")] - #[arg(help = "The L1 STRK gas price. (denominated in fri)")] + /// The L1 STRK gas price. (denominated in fri) + #[arg(long = "gpo.l1-strk-gas-price", value_name = "FRI")] pub l1_strk_gas_price: Option, - #[arg(long = "l1-eth-data-gas-price", value_name = "WEI")] - #[arg(requires = "l1_strk_data_gas_price")] - #[arg(help = "The L1 ETH gas price. (denominated in wei)")] + /// 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, - #[arg(long = "l1-strk-data-gas-price", value_name = "FRI")] - #[arg(requires = "l1_eth_gas_price")] - #[arg(help = "The L1 STRK gas prick. (denominated in fri)")] + /// 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, } #[cfg(feature = "slot")] #[derive(Debug, Args, Clone)] +#[command(next_help_heading = "Slot options")] pub struct SlotOptions { #[arg(hide = true)] #[arg(long = "slot.controller")] @@ -263,19 +315,26 @@ impl NodeArgs { fn init_logging(&self) -> Result<()> { const DEFAULT_LOG_FILTER: &str = "info,tasks=debug,executor=trace,forking::backend=trace,\ - server=debug,blockifier=off,jsonrpsee_server=off,\ - hyper=off,messaging=debug,node=error"; + blockifier=off,jsonrpsee_server=off,hyper=off,\ + messaging=debug,node=error"; + + let filter = if self.development.dev { + &format!("{DEFAULT_LOG_FILTER},server=debug") + } else { + DEFAULT_LOG_FILTER + }; LogTracer::init()?; - let builder = fmt::Subscriber::builder().with_env_filter( - EnvFilter::try_from_default_env().or(EnvFilter::try_new(DEFAULT_LOG_FILTER))?, - ); + // If the user has set the `RUST_LOG` environment variable, then we prioritize it. + // Otherwise, we use the default log filter. + // TODO: change env var to `KATANA_LOG`. + let filter = EnvFilter::try_from_default_env().or(EnvFilter::try_new(filter))?; + let builder = fmt::Subscriber::builder().with_env_filter(filter); - let subscriber: Box = if self.json_log { - Box::new(builder.json().finish()) - } else { - Box::new(builder.finish()) + let subscriber: Box = match self.logging.log_format { + LogFormat::Full => Box::new(builder.finish()), + LogFormat::Json => Box::new(builder.json().finish()), }; Ok(tracing::subscriber::set_global_default(subscriber)?) @@ -302,16 +361,16 @@ impl NodeArgs { fn rpc_config(&self) -> RpcConfig { let mut apis = HashSet::from([ApiKind::Starknet, ApiKind::Torii, ApiKind::Saya]); // only enable `katana` API in dev mode - if self.dev { + if self.development.dev { apis.insert(ApiKind::Dev); } RpcConfig { apis, - port: self.server.port, - addr: self.server.host, + port: self.server.http_port, + addr: self.server.http_addr, max_connections: self.server.max_connections, - allowed_origins: self.server.allowed_origins.clone(), + cors_domain: self.server.http_cors_domain.clone(), } } @@ -329,8 +388,8 @@ impl NodeArgs { } // generate dev accounts - let accounts = DevAllocationsGenerator::new(self.starknet.total_accounts) - .with_seed(parse_seed(&self.starknet.seed)) + let accounts = DevAllocationsGenerator::new(self.development.total_accounts) + .with_seed(parse_seed(&self.development.seed)) .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) .generate(); @@ -345,27 +404,32 @@ impl NodeArgs { } fn dev_config(&self) -> DevConfig { - let fixed_gas_prices = if self.starknet.environment.l1_eth_gas_price.is_some() { - // It is safe to unwrap all of these here because the CLI parser ensures if one is set, - // all must be set. + let mut fixed_gas_prices = None; - let eth_gas_price = self.starknet.environment.l1_eth_gas_price.unwrap(); - let strk_gas_price = self.starknet.environment.l1_strk_gas_price.unwrap(); - let eth_data_gas_price = self.starknet.environment.l1_eth_data_gas_price.unwrap(); - let strk_data_gas_price = self.starknet.environment.l1_strk_data_gas_price.unwrap(); + if let Some(price) = self.gpo.l1_eth_gas_price { + let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); + prices.gas_price.eth = price; + } - let gas_price = GasPrices { eth: eth_gas_price, strk: strk_gas_price }; - let data_gas_price = GasPrices { eth: eth_data_gas_price, strk: strk_data_gas_price }; + if let Some(price) = self.gpo.l1_strk_gas_price { + let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); + prices.gas_price.strk = price; + } - Some(FixedL1GasPriceConfig { gas_price, data_gas_price }) - } else { - None - }; + if let Some(price) = self.gpo.l1_eth_data_gas_price { + let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); + prices.data_gas_price.eth = price; + } + + if let Some(price) = self.gpo.l1_strk_data_gas_price { + let prices = fixed_gas_prices.get_or_insert(FixedL1GasPriceConfig::default()); + prices.data_gas_price.strk = price; + } DevConfig { fixed_gas_prices, - fee: !self.starknet.disable_fee, - account_validation: !self.starknet.disable_validate, + fee: !self.development.no_fee, + account_validation: !self.development.no_account_validation, } } @@ -386,11 +450,12 @@ impl NodeArgs { } fn forking_config(&self) -> Result> { - if let Some(url) = self.fork_rpc_url.clone() { - Ok(Some(ForkingConfig { url, block: self.fork_block })) - } else { - Ok(None) + if let Some(ref url) = self.forking.fork_provider { + let cfg = ForkingConfig { url: url.clone(), block: self.forking.fork_block }; + return Ok(Some(cfg)); } + + Ok(None) } fn db_config(&self) -> DbConfig { @@ -398,16 +463,20 @@ impl NodeArgs { } fn metrics_config(&self) -> Option { - self.metrics.map(|addr| MetricsConfig { addr }) + if self.metrics.metrics { + Some(MetricsConfig { addr: self.metrics.metrics_addr, port: self.metrics.metrics_port }) + } else { + None + } } } fn print_intro(args: &NodeArgs, chain: &ChainSpec) { let mut accounts = chain.genesis.accounts().peekable(); let account_class_hash = accounts.peek().map(|e| e.1.class_hash()); - let seed = &args.starknet.seed; + let seed = &args.development.seed; - if args.json_log { + if args.logging.log_format == LogFormat::Json { info!( target: LOG_TARGET, "{}", @@ -517,6 +586,10 @@ PREFUNDED ACCOUNTS #[cfg(test)] mod test { use assert_matches::assert_matches; + use katana_core::constants::{ + DEFAULT_ETH_L1_DATA_GAS_PRICE, DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_STRK_L1_DATA_GAS_PRICE, + DEFAULT_STRK_L1_GAS_PRICE, + }; use katana_primitives::{address, felt}; use super::*; @@ -540,8 +613,9 @@ mod test { fn test_starknet_config_custom() { let args = NodeArgs::parse_from([ "katana", - "--disable-fee", - "--disable-validate", + "--dev", + "--dev.no-fee", + "--dev.no-account-validation", "--chain-id", "SN_GOERLI", "--invoke-max-steps", @@ -564,35 +638,78 @@ mod test { #[test] fn custom_fixed_gas_prices() { - let args = NodeArgs::parse_from([ + let config = NodeArgs::parse_from(["katana"]).config().unwrap(); + assert!(config.dev.fixed_gas_prices.is_none()); + + let config = + NodeArgs::parse_from(["katana", "--gpo.l1-eth-gas-price", "10"]).config().unwrap(); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, 10); + assert_eq!(prices.gas_price.strk, DEFAULT_STRK_L1_GAS_PRICE); + assert_eq!(prices.data_gas_price.eth, DEFAULT_ETH_L1_DATA_GAS_PRICE); + assert_eq!(prices.data_gas_price.strk, DEFAULT_STRK_L1_DATA_GAS_PRICE); + }); + + let config = + NodeArgs::parse_from(["katana", "--gpo.l1-strk-gas-price", "20"]).config().unwrap(); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, DEFAULT_ETH_L1_GAS_PRICE); + assert_eq!(prices.gas_price.strk, 20); + assert_eq!(prices.data_gas_price.eth, DEFAULT_ETH_L1_DATA_GAS_PRICE); + assert_eq!(prices.data_gas_price.strk, DEFAULT_STRK_L1_DATA_GAS_PRICE); + }); + + let config = + NodeArgs::parse_from(["katana", "--gpo.l1-eth-data-gas-price", "1"]).config().unwrap(); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, DEFAULT_ETH_L1_GAS_PRICE); + assert_eq!(prices.gas_price.strk, DEFAULT_STRK_L1_GAS_PRICE); + assert_eq!(prices.data_gas_price.eth, 1); + assert_eq!(prices.data_gas_price.strk, DEFAULT_STRK_L1_DATA_GAS_PRICE); + }); + + let config = + NodeArgs::parse_from(["katana", "--gpo.l1-strk-data-gas-price", "2"]).config().unwrap(); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, DEFAULT_ETH_L1_GAS_PRICE); + assert_eq!(prices.gas_price.strk, DEFAULT_STRK_L1_GAS_PRICE); + assert_eq!(prices.data_gas_price.eth, DEFAULT_ETH_L1_DATA_GAS_PRICE); + assert_eq!(prices.data_gas_price.strk, 2); + }); + + let config = NodeArgs::parse_from([ "katana", - "--disable-fee", - "--disable-validate", - "--chain-id", - "SN_GOERLI", - "--invoke-max-steps", - "200", - "--validate-max-steps", - "100", - "--db-dir", - "/path/to/db", - "--l1-eth-gas-price", + "--gpo.l1-eth-gas-price", + "10", + "--gpo.l1-strk-data-gas-price", + "2", + ]) + .config() + .unwrap(); + + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, 10); + assert_eq!(prices.gas_price.strk, DEFAULT_STRK_L1_GAS_PRICE); + assert_eq!(prices.data_gas_price.eth, DEFAULT_ETH_L1_DATA_GAS_PRICE); + assert_eq!(prices.data_gas_price.strk, 2); + }); + + // Set all the gas prices options + + let config = NodeArgs::parse_from([ + "katana", + "--gpo.l1-eth-gas-price", "10", - "--l1-strk-gas-price", + "--gpo.l1-strk-gas-price", "20", - "--l1-eth-data-gas-price", + "--gpo.l1-eth-data-gas-price", "1", - "--l1-strk-data-gas-price", + "--gpo.l1-strk-data-gas-price", "2", - ]); - let config = args.config().unwrap(); + ]) + .config() + .unwrap(); - assert!(!config.dev.fee); - assert!(!config.dev.account_validation); - assert_eq!(config.execution.invocation_max_steps, 200); - assert_eq!(config.execution.validation_max_steps, 100); - assert_eq!(config.db.dir, Some(PathBuf::from("/path/to/db"))); - assert_eq!(config.chain.id, ChainId::GOERLI); assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { assert_eq!(prices.gas_price.eth, 10); assert_eq!(prices.gas_price.strk, 20); @@ -607,13 +724,13 @@ mod test { "katana", "--genesis", "./tests/test-data/genesis.json", - "--l1-eth-gas-price", + "--gpo.l1-eth-gas-price", "100", - "--l1-strk-gas-price", + "--gpo.l1-strk-gas-price", "200", - "--l1-eth-data-gas-price", + "--gpo.l1-eth-data-gas-price", "111", - "--l1-strk-data-gas-price", + "--gpo.l1-strk-data-gas-price", "222", ]) .config() diff --git a/bin/katana/src/utils.rs b/bin/katana/src/utils.rs index 580924962e..d580eada96 100644 --- a/bin/katana/src/utils.rs +++ b/bin/katana/src/utils.rs @@ -1,6 +1,9 @@ +use std::fmt::Display; use std::path::PathBuf; use anyhow::{Context, Result}; +use clap::builder::PossibleValue; +use clap::ValueEnum; use katana_primitives::block::{BlockHash, BlockHashOrNumber, BlockNumber}; use katana_primitives::genesis::json::GenesisJson; use katana_primitives::genesis::Genesis; @@ -34,6 +37,34 @@ pub fn parse_block_hash_or_number(value: &str) -> Result { } } +#[derive(Debug, Clone, PartialEq)] +pub enum LogFormat { + Json, + Full, +} + +impl ValueEnum for LogFormat { + fn value_variants<'a>() -> &'a [Self] { + &[Self::Json, Self::Full] + } + + fn to_possible_value(&self) -> Option { + match self { + Self::Json => Some(PossibleValue::new("json")), + Self::Full => Some(PossibleValue::new("full")), + } + } +} + +impl Display for LogFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Json => write!(f, "json"), + Self::Full => write!(f, "full"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/dojo/test-utils/src/sequencer.rs b/crates/dojo/test-utils/src/sequencer.rs index 63356be4ad..dbc4f661ef 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 { - allowed_origins: None, + cors_domain: None, port: 0, addr: DEFAULT_RPC_ADDR, max_connections: DEFAULT_RPC_MAX_CONNECTIONS, diff --git a/crates/katana/node-bindings/src/lib.rs b/crates/katana/node-bindings/src/lib.rs index f270e0d384..dec102f63b 100644 --- a/crates/katana/node-bindings/src/lib.rs +++ b/crates/katana/node-bindings/src/lib.rs @@ -176,20 +176,21 @@ pub struct Katana { json_log: bool, block_time: Option, db_dir: Option, - rpc_url: Option, + l1_provider: Option, fork_block_number: Option, messaging: Option, // Metrics options - metrics: Option, + metrics_addr: Option, + metrics_port: Option, // Server options - port: Option, - host: Option, - max_connections: Option, - allowed_origins: Option, + http_addr: Option, + http_port: Option, + rpc_max_connections: Option, + http_cors_domain: Option, - // Starknet options + // Dev options seed: Option, accounts: Option, disable_fee: bool, @@ -253,7 +254,7 @@ impl Katana { /// Sets the port which will be used when the `katana` instance is launched. pub fn port>(mut self, port: T) -> Self { - self.port = Some(port.into()); + self.http_port = Some(port.into()); self } @@ -271,8 +272,8 @@ impl Katana { } /// Sets the RPC URL to fork the network from. - pub fn rpc_url>(mut self, rpc_url: T) -> Self { - self.rpc_url = Some(rpc_url.into()); + pub fn l1_provider>(mut self, rpc_url: T) -> Self { + self.l1_provider = Some(rpc_url.into()); self } @@ -301,27 +302,33 @@ impl Katana { self } - /// Enables Prometheus metrics and sets the socket address. - pub fn metrics>(mut self, metrics: T) -> Self { - self.metrics = Some(metrics.into()); + /// Enables Prometheus metrics and sets the metrics server address. + pub fn metrics_addr>(mut self, addr: T) -> Self { + self.metrics_addr = Some(addr.into()); + self + } + + /// Enables Prometheus metrics and sets the metrics server port. + pub fn metrics_port>(mut self, port: T) -> Self { + self.metrics_port = Some(port.into()); self } /// Sets the host IP address the server will listen on. - pub fn host>(mut self, host: T) -> Self { - self.host = Some(host.into()); + pub fn http_addr>(mut self, addr: T) -> Self { + self.http_addr = Some(addr.into()); self } /// Sets the maximum number of concurrent connections allowed. - pub const fn max_connections(mut self, max_connections: u64) -> Self { - self.max_connections = Some(max_connections); + pub const fn rpc_max_connections(mut self, max_connections: u64) -> Self { + self.rpc_max_connections = Some(max_connections); self } /// Enables the CORS layer and sets the allowed origins, separated by commas. - pub fn allowed_origins>(mut self, allowed_origins: T) -> Self { - self.allowed_origins = Some(allowed_origins.into()); + pub fn http_cors_domain>(mut self, allowed_origins: T) -> Self { + self.http_cors_domain = Some(allowed_origins.into()); self } @@ -414,8 +421,14 @@ impl Katana { let mut cmd = self.program.as_ref().map_or_else(|| Command::new("katana"), Command::new); cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit()); - let mut port = self.port.unwrap_or(0); - cmd.arg("--port").arg(port.to_string()); + if let Some(host) = self.http_addr { + cmd.arg("--http.addr").arg(host.to_string()); + } + + // In the case where port 0 is set, we will need to extract the actual port number + // from the logs. + let mut port = self.http_port.unwrap_or(0); + cmd.arg("--http.port").arg(port.to_string()); if self.no_mining { cmd.arg("--no-mining"); @@ -428,56 +441,92 @@ impl Katana { if let Some(db_dir) = self.db_dir { cmd.arg("--db-dir").arg(db_dir); } - if let Some(rpc_url) = self.rpc_url { - cmd.arg("--rpc-url").arg(rpc_url); + + if let Some(url) = self.l1_provider { + cmd.arg("--l1.provider").arg(url); } + // Need to make sure that the `--dev` is not being set twice. + let mut is_dev = false; + if self.dev { cmd.arg("--dev"); + is_dev = true; } - if self.json_log { - cmd.arg("--json-log"); + if let Some(seed) = self.seed { + if !is_dev { + cmd.arg("--dev"); + is_dev = true; + } + + cmd.arg("--dev.seed").arg(seed.to_string()); } - if let Some(fork_block_number) = self.fork_block_number { - cmd.arg("--fork-block-number").arg(fork_block_number.to_string()); + if let Some(accounts) = self.accounts { + if !is_dev { + cmd.arg("--dev"); + is_dev = true; + } + + cmd.arg("--dev.accounts").arg(accounts.to_string()); } - if let Some(messaging) = self.messaging { - cmd.arg("--messaging").arg(messaging); + if self.disable_fee { + if !is_dev { + cmd.arg("--dev"); + is_dev = true; + } + + cmd.arg("--dev.no-fee"); } - if let Some(metrics) = self.metrics { - cmd.arg("--metrics").arg(metrics); + if self.disable_validate { + if !is_dev { + cmd.arg("--dev"); + } + + cmd.arg("--dev.no-account-validation"); } - if let Some(host) = self.host { - cmd.arg("--host").arg(host); + if self.json_log { + cmd.args(["--log.format", "json"]); } - if let Some(max_connections) = self.max_connections { - cmd.arg("--max-connections").arg(max_connections.to_string()); + if let Some(fork_block_number) = self.fork_block_number { + cmd.args(["--fork", "--fork.block"]).arg(fork_block_number.to_string()); } - if let Some(allowed_origins) = self.allowed_origins { - cmd.arg("--allowed-origins").arg(allowed_origins); + if let Some(messaging) = self.messaging { + cmd.arg("--messaging").arg(messaging); } - if let Some(seed) = self.seed { - cmd.arg("--seed").arg(seed.to_string()); + // Need to make sure that the `--metrics` is not being set twice. + let mut metrics_enabled = false; + + if let Some(addr) = self.metrics_addr { + if !metrics_enabled { + cmd.arg("--metrics"); + metrics_enabled = true; + } + + cmd.arg("--metrics.addr").arg(addr.to_string()); } - if let Some(accounts) = self.accounts { - cmd.arg("--accounts").arg(accounts.to_string()); + if let Some(port) = self.metrics_port { + if !metrics_enabled { + cmd.arg("--metrics"); + } + + cmd.arg("--metrics.port").arg(port.to_string()); } - if self.disable_fee { - cmd.arg("--disable-fee"); + if let Some(max_connections) = self.rpc_max_connections { + cmd.arg("--rpc.max-connections").arg(max_connections.to_string()); } - if self.disable_validate { - cmd.arg("--disable-validate"); + if let Some(allowed_origins) = self.http_cors_domain { + cmd.arg("--http.corsdomain").arg(allowed_origins); } if let Some(chain_id) = self.chain_id { diff --git a/crates/katana/node/src/config/dev.rs b/crates/katana/node/src/config/dev.rs index cddd6bf64a..5ae4b73193 100644 --- a/crates/katana/node/src/config/dev.rs +++ b/crates/katana/node/src/config/dev.rs @@ -1,3 +1,7 @@ +use katana_core::constants::{ + DEFAULT_ETH_L1_DATA_GAS_PRICE, DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_STRK_L1_DATA_GAS_PRICE, + DEFAULT_STRK_L1_GAS_PRICE, +}; use katana_primitives::block::GasPrices; /// Development configuration. @@ -36,6 +40,18 @@ pub struct FixedL1GasPriceConfig { pub data_gas_price: GasPrices, } +impl std::default::Default for FixedL1GasPriceConfig { + fn default() -> Self { + Self { + gas_price: GasPrices { eth: DEFAULT_ETH_L1_GAS_PRICE, strk: DEFAULT_STRK_L1_GAS_PRICE }, + data_gas_price: GasPrices { + eth: DEFAULT_ETH_L1_DATA_GAS_PRICE, + strk: DEFAULT_STRK_L1_DATA_GAS_PRICE, + }, + } + } +} + impl std::default::Default for DevConfig { fn default() -> Self { Self { fee: true, account_validation: true, fixed_gas_prices: None } diff --git a/crates/katana/node/src/config/metrics.rs b/crates/katana/node/src/config/metrics.rs index 40a1b234ad..9830660485 100644 --- a/crates/katana/node/src/config/metrics.rs +++ b/crates/katana/node/src/config/metrics.rs @@ -1,8 +1,22 @@ -use std::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +/// Metrics server default address. +pub const DEFAULT_METRICS_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); +/// Metrics server default port. +pub const DEFAULT_METRICS_PORT: u16 = 9100; /// Node metrics configurations. #[derive(Debug, Clone)] pub struct MetricsConfig { /// The address to bind the metrics server to. - pub addr: SocketAddr, + pub addr: IpAddr, + /// The port to bind the metrics server to. + pub port: u16, +} + +impl MetricsConfig { + /// Returns the [`SocketAddr`] for the metrics server. + pub fn socket_addr(&self) -> SocketAddr { + SocketAddr::new(self.addr, self.port) + } } diff --git a/crates/katana/node/src/config/rpc.rs b/crates/katana/node/src/config/rpc.rs index bd1d893d39..a0806847d4 100644 --- a/crates/katana/node/src/config/rpc.rs +++ b/crates/katana/node/src/config/rpc.rs @@ -23,8 +23,8 @@ pub struct RpcConfig { pub addr: IpAddr, pub port: u16, pub max_connections: u32, - pub allowed_origins: Option>, pub apis: HashSet, + pub cors_domain: Option>, } impl RpcConfig { @@ -37,7 +37,7 @@ impl RpcConfig { impl Default for RpcConfig { fn default() -> Self { Self { - allowed_origins: None, + cors_domain: 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 294a244718..b120701be2 100644 --- a/crates/katana/node/src/lib.rs +++ b/crates/katana/node/src/lib.rs @@ -107,6 +107,7 @@ impl Node { // TODO: maybe move this to the build stage if let Some(ref cfg) = self.metrics_config { + let addr = cfg.socket_addr(); let mut reports: Vec> = Vec::new(); if let Some(ref db) = self.db { @@ -116,8 +117,8 @@ impl Node { let exporter = PrometheusRecorder::current().expect("qed; should exist at this point"); let server = MetricsServer::new(exporter).with_process_metrics().with_reports(reports); - self.task_manager.task_spawner().build_task().spawn(server.start(cfg.addr)); - info!(addr = %cfg.addr, "Metrics server started."); + self.task_manager.task_spawner().build_task().spawn(server.start(addr)); + info!(%addr, "Metrics server started."); } let pool = self.pool.clone(); @@ -312,20 +313,19 @@ 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.allowed_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 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 middleware = tower::ServiceBuilder::new() .option_layer(cors) diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index 816c3b490e..f771e28511 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -121,7 +121,7 @@ impl KatanaRunner { .port(port) .accounts(n_accounts) .json_log(true) - .max_connections(10000) + .rpc_max_connections(10000) .dev(config.dev) .fee(!config.disable_fee);