|
| 1 | +//! Katana binary executable. |
| 2 | +//! |
| 3 | +//! ## Feature Flags |
| 4 | +//! |
| 5 | +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. |
| 6 | +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) |
| 7 | +//! for more info. |
| 8 | +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling |
| 9 | +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) |
| 10 | +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) |
| 11 | +//! for more info. |
| 12 | +
|
| 13 | +use std::net::SocketAddr; |
| 14 | +use std::path::PathBuf; |
| 15 | + |
| 16 | +use alloy_primitives::U256; |
| 17 | +use clap::{Args, Parser, Subcommand}; |
| 18 | +use clap_complete::Shell; |
| 19 | +use common::parse::parse_socket_address; |
| 20 | +use katana_core::backend::config::{Environment, StarknetConfig}; |
| 21 | +use katana_core::constants::{ |
| 22 | + DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_SEQUENCER_ADDRESS, |
| 23 | + DEFAULT_STRK_L1_GAS_PRICE, DEFAULT_VALIDATE_MAX_STEPS, |
| 24 | +}; |
| 25 | +use katana_core::sequencer::SequencerConfig; |
| 26 | +use katana_primitives::block::GasPrices; |
| 27 | +use katana_primitives::chain::ChainId; |
| 28 | +use katana_primitives::genesis::allocation::DevAllocationsGenerator; |
| 29 | +use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; |
| 30 | +use katana_primitives::genesis::Genesis; |
| 31 | +use katana_rpc::config::ServerConfig; |
| 32 | +use katana_rpc_api::ApiKind; |
| 33 | +use tracing::Subscriber; |
| 34 | +use tracing_subscriber::{fmt, EnvFilter}; |
| 35 | +use url::Url; |
| 36 | + |
| 37 | +use crate::utils::{parse_genesis, parse_seed}; |
| 38 | + |
| 39 | +#[derive(Parser, Debug)] |
| 40 | +#[command(author, version, about, long_about = None)] |
| 41 | +#[command(propagate_version = true)] |
| 42 | +pub struct KatanaArgs { |
| 43 | + #[arg(long)] |
| 44 | + #[arg(help = "Don't print anything on startup.")] |
| 45 | + pub silent: bool, |
| 46 | + |
| 47 | + #[arg(long)] |
| 48 | + #[arg(conflicts_with = "block_time")] |
| 49 | + #[arg(help = "Disable auto and interval mining, and mine on demand instead via an endpoint.")] |
| 50 | + pub no_mining: bool, |
| 51 | + |
| 52 | + #[arg(short, long)] |
| 53 | + #[arg(value_name = "MILLISECONDS")] |
| 54 | + #[arg(help = "Block time in milliseconds for interval mining.")] |
| 55 | + pub block_time: Option<u64>, |
| 56 | + |
| 57 | + #[arg(long)] |
| 58 | + #[arg(value_name = "PATH")] |
| 59 | + #[arg(help = "Directory path of the database to initialize from.")] |
| 60 | + #[arg(long_help = "Directory path of the database to initialize from. The path must either \ |
| 61 | + be an empty directory or a directory which already contains a previously \ |
| 62 | + initialized Katana database.")] |
| 63 | + pub db_dir: Option<PathBuf>, |
| 64 | + |
| 65 | + #[arg(long)] |
| 66 | + #[arg(value_name = "URL")] |
| 67 | + #[arg(help = "The Starknet RPC provider to fork the network from.")] |
| 68 | + pub rpc_url: Option<Url>, |
| 69 | + |
| 70 | + #[arg(long)] |
| 71 | + pub dev: bool, |
| 72 | + |
| 73 | + #[arg(long)] |
| 74 | + #[arg(help = "Output logs in JSON format.")] |
| 75 | + pub json_log: bool, |
| 76 | + |
| 77 | + /// Enable Prometheus metrics. |
| 78 | + /// |
| 79 | + /// The metrics will be served at the given interface and port. |
| 80 | + #[arg(long, value_name = "SOCKET", value_parser = parse_socket_address, help_heading = "Metrics")] |
| 81 | + pub metrics: Option<SocketAddr>, |
| 82 | + |
| 83 | + #[arg(long)] |
| 84 | + #[arg(requires = "rpc_url")] |
| 85 | + #[arg(value_name = "BLOCK_NUMBER")] |
| 86 | + #[arg(help = "Fork the network at a specific block.")] |
| 87 | + pub fork_block_number: Option<u64>, |
| 88 | + |
| 89 | + #[cfg(feature = "messaging")] |
| 90 | + #[arg(long)] |
| 91 | + #[arg(value_name = "PATH")] |
| 92 | + #[arg(value_parser = katana_core::service::messaging::MessagingConfig::parse)] |
| 93 | + #[arg(help = "Configure the messaging with an other chain.")] |
| 94 | + #[arg(long_help = "Configure the messaging to allow Katana listening/sending messages on a \ |
| 95 | + settlement chain that can be Ethereum or an other Starknet sequencer. \ |
| 96 | + The configuration file details and examples can be found here: https://book.dojoengine.org/toolchain/katana/reference#messaging")] |
| 97 | + pub messaging: Option<katana_core::service::messaging::MessagingConfig>, |
| 98 | + |
| 99 | + #[command(flatten)] |
| 100 | + #[command(next_help_heading = "Server options")] |
| 101 | + pub server: ServerOptions, |
| 102 | + |
| 103 | + #[command(flatten)] |
| 104 | + #[command(next_help_heading = "Starknet options")] |
| 105 | + pub starknet: StarknetOptions, |
| 106 | + |
| 107 | + #[command(subcommand)] |
| 108 | + pub command: Option<Commands>, |
| 109 | +} |
| 110 | + |
| 111 | +#[derive(Debug, Subcommand)] |
| 112 | +pub enum Commands { |
| 113 | + #[command(about = "Generate shell completion file for specified shell")] |
| 114 | + Completions { shell: Shell }, |
| 115 | +} |
| 116 | + |
| 117 | +#[derive(Debug, Args, Clone)] |
| 118 | +pub struct ServerOptions { |
| 119 | + #[arg(short, long)] |
| 120 | + #[arg(default_value = "5050")] |
| 121 | + #[arg(help = "Port number to listen on.")] |
| 122 | + pub port: u16, |
| 123 | + |
| 124 | + #[arg(long)] |
| 125 | + #[arg(help = "The IP address the server will listen on.")] |
| 126 | + pub host: Option<String>, |
| 127 | + |
| 128 | + #[arg(long)] |
| 129 | + #[arg(default_value = "100")] |
| 130 | + #[arg(help = "Maximum number of concurrent connections allowed.")] |
| 131 | + pub max_connections: u32, |
| 132 | + |
| 133 | + #[arg(long)] |
| 134 | + #[arg(value_delimiter = ',')] |
| 135 | + #[arg(help = "Enables the CORS layer and sets the allowed origins, separated by commas.")] |
| 136 | + pub allowed_origins: Option<Vec<String>>, |
| 137 | +} |
| 138 | + |
| 139 | +#[derive(Debug, Args, Clone)] |
| 140 | +pub struct StarknetOptions { |
| 141 | + #[arg(long)] |
| 142 | + #[arg(default_value = "0")] |
| 143 | + #[arg(help = "Specify the seed for randomness of accounts to be predeployed.")] |
| 144 | + pub seed: String, |
| 145 | + |
| 146 | + #[arg(long = "accounts")] |
| 147 | + #[arg(value_name = "NUM")] |
| 148 | + #[arg(default_value = "10")] |
| 149 | + #[arg(help = "Number of pre-funded accounts to generate.")] |
| 150 | + pub total_accounts: u16, |
| 151 | + |
| 152 | + #[arg(long)] |
| 153 | + #[arg(help = "Disable charging fee when executing transactions.")] |
| 154 | + pub disable_fee: bool, |
| 155 | + |
| 156 | + #[arg(long)] |
| 157 | + #[arg(help = "Disable validation when executing transactions.")] |
| 158 | + pub disable_validate: bool, |
| 159 | + |
| 160 | + #[command(flatten)] |
| 161 | + #[command(next_help_heading = "Environment options")] |
| 162 | + pub environment: EnvironmentOptions, |
| 163 | + |
| 164 | + #[arg(long)] |
| 165 | + #[arg(value_parser = parse_genesis)] |
| 166 | + #[arg(conflicts_with_all(["rpc_url", "seed", "total_accounts"]))] |
| 167 | + pub genesis: Option<Genesis>, |
| 168 | +} |
| 169 | + |
| 170 | +#[derive(Debug, Args, Clone)] |
| 171 | +pub struct EnvironmentOptions { |
| 172 | + #[arg(long)] |
| 173 | + #[arg(help = "The chain ID.")] |
| 174 | + #[arg(long_help = "The chain ID. If a raw hex string (`0x` prefix) is provided, then it'd \ |
| 175 | + used as the actual chain ID. Otherwise, it's represented as the raw \ |
| 176 | + ASCII values. It must be a valid Cairo short string.")] |
| 177 | + #[arg(default_value = "KATANA")] |
| 178 | + #[arg(value_parser = ChainId::parse)] |
| 179 | + pub chain_id: ChainId, |
| 180 | + |
| 181 | + #[arg(long)] |
| 182 | + #[arg(help = "The maximum number of steps available for the account validation logic.")] |
| 183 | + #[arg(default_value_t = DEFAULT_VALIDATE_MAX_STEPS)] |
| 184 | + pub validate_max_steps: u32, |
| 185 | + |
| 186 | + #[arg(long)] |
| 187 | + #[arg(help = "The maximum number of steps available for the account execution logic.")] |
| 188 | + #[arg(default_value_t = DEFAULT_INVOKE_MAX_STEPS)] |
| 189 | + pub invoke_max_steps: u32, |
| 190 | + |
| 191 | + #[arg(long = "eth-gas-price")] |
| 192 | + #[arg(conflicts_with = "genesis")] |
| 193 | + #[arg(help = "The L1 ETH gas price. (denominated in wei)")] |
| 194 | + #[arg(default_value_t = DEFAULT_ETH_L1_GAS_PRICE)] |
| 195 | + pub l1_eth_gas_price: u128, |
| 196 | + |
| 197 | + #[arg(long = "strk-gas-price")] |
| 198 | + #[arg(conflicts_with = "genesis")] |
| 199 | + #[arg(help = "The L1 STRK gas price. (denominated in fri)")] |
| 200 | + #[arg(default_value_t = DEFAULT_STRK_L1_GAS_PRICE)] |
| 201 | + pub l1_strk_gas_price: u128, |
| 202 | +} |
| 203 | + |
| 204 | +impl KatanaArgs { |
| 205 | + pub fn init_logging(&self) -> Result<(), Box<dyn std::error::Error>> { |
| 206 | + const DEFAULT_LOG_FILTER: &str = "info,executor=trace,forked_backend=trace,server=debug,\ |
| 207 | + katana_core=trace,blockifier=off,jsonrpsee_server=off,\ |
| 208 | + hyper=off,messaging=debug,node=error"; |
| 209 | + |
| 210 | + let builder = fmt::Subscriber::builder().with_env_filter( |
| 211 | + EnvFilter::try_from_default_env().or(EnvFilter::try_new(DEFAULT_LOG_FILTER))?, |
| 212 | + ); |
| 213 | + |
| 214 | + let subscriber: Box<dyn Subscriber + Send + Sync> = if self.json_log { |
| 215 | + Box::new(builder.json().finish()) |
| 216 | + } else { |
| 217 | + Box::new(builder.finish()) |
| 218 | + }; |
| 219 | + |
| 220 | + Ok(tracing::subscriber::set_global_default(subscriber)?) |
| 221 | + } |
| 222 | + |
| 223 | + pub fn sequencer_config(&self) -> SequencerConfig { |
| 224 | + SequencerConfig { |
| 225 | + block_time: self.block_time, |
| 226 | + no_mining: self.no_mining, |
| 227 | + #[cfg(feature = "messaging")] |
| 228 | + messaging: self.messaging.clone(), |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + pub fn server_config(&self) -> ServerConfig { |
| 233 | + let mut apis = vec![ApiKind::Starknet, ApiKind::Katana, ApiKind::Torii, ApiKind::Saya]; |
| 234 | + // only enable `katana` API in dev mode |
| 235 | + if self.dev { |
| 236 | + apis.push(ApiKind::Dev); |
| 237 | + } |
| 238 | + |
| 239 | + ServerConfig { |
| 240 | + apis, |
| 241 | + port: self.server.port, |
| 242 | + host: self.server.host.clone().unwrap_or("0.0.0.0".into()), |
| 243 | + max_connections: self.server.max_connections, |
| 244 | + allowed_origins: self.server.allowed_origins.clone(), |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + pub fn starknet_config(&self) -> StarknetConfig { |
| 249 | + let genesis = match self.starknet.genesis.clone() { |
| 250 | + Some(genesis) => genesis, |
| 251 | + None => { |
| 252 | + let gas_prices = GasPrices { |
| 253 | + eth: self.starknet.environment.l1_eth_gas_price, |
| 254 | + strk: self.starknet.environment.l1_strk_gas_price, |
| 255 | + }; |
| 256 | + |
| 257 | + let accounts = DevAllocationsGenerator::new(self.starknet.total_accounts) |
| 258 | + .with_seed(parse_seed(&self.starknet.seed)) |
| 259 | + .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) |
| 260 | + .generate(); |
| 261 | + |
| 262 | + let mut genesis = Genesis { |
| 263 | + gas_prices, |
| 264 | + sequencer_address: *DEFAULT_SEQUENCER_ADDRESS, |
| 265 | + ..Default::default() |
| 266 | + }; |
| 267 | + |
| 268 | + genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); |
| 269 | + genesis |
| 270 | + } |
| 271 | + }; |
| 272 | + |
| 273 | + StarknetConfig { |
| 274 | + disable_fee: self.starknet.disable_fee, |
| 275 | + disable_validate: self.starknet.disable_validate, |
| 276 | + fork_rpc_url: self.rpc_url.clone(), |
| 277 | + fork_block_number: self.fork_block_number, |
| 278 | + env: Environment { |
| 279 | + chain_id: self.starknet.environment.chain_id, |
| 280 | + invoke_max_steps: self.starknet.environment.invoke_max_steps, |
| 281 | + validate_max_steps: self.starknet.environment.validate_max_steps, |
| 282 | + }, |
| 283 | + db_dir: self.db_dir.clone(), |
| 284 | + genesis, |
| 285 | + } |
| 286 | + } |
| 287 | +} |
| 288 | + |
| 289 | +#[cfg(test)] |
| 290 | +mod test { |
| 291 | + use super::*; |
| 292 | + |
| 293 | + #[test] |
| 294 | + fn test_starknet_config_default() { |
| 295 | + let args = KatanaArgs::parse_from(["katana"]); |
| 296 | + let config = args.starknet_config(); |
| 297 | + |
| 298 | + assert!(!config.disable_fee); |
| 299 | + assert!(!config.disable_validate); |
| 300 | + assert_eq!(config.fork_rpc_url, None); |
| 301 | + assert_eq!(config.fork_block_number, None); |
| 302 | + assert_eq!(config.env.chain_id, ChainId::parse("KATANA").unwrap()); |
| 303 | + assert_eq!(config.env.invoke_max_steps, DEFAULT_INVOKE_MAX_STEPS); |
| 304 | + assert_eq!(config.env.validate_max_steps, DEFAULT_VALIDATE_MAX_STEPS); |
| 305 | + assert_eq!(config.db_dir, None); |
| 306 | + assert_eq!(config.genesis.gas_prices.eth, DEFAULT_ETH_L1_GAS_PRICE); |
| 307 | + assert_eq!(config.genesis.gas_prices.strk, DEFAULT_STRK_L1_GAS_PRICE); |
| 308 | + assert_eq!(config.genesis.sequencer_address, *DEFAULT_SEQUENCER_ADDRESS); |
| 309 | + } |
| 310 | + |
| 311 | + #[test] |
| 312 | + fn test_starknet_config_custom() { |
| 313 | + let args = KatanaArgs::parse_from([ |
| 314 | + "katana", |
| 315 | + "--disable-fee", |
| 316 | + "--disable-validate", |
| 317 | + "--chain-id", |
| 318 | + "SN_GOERLI", |
| 319 | + "--invoke-max-steps", |
| 320 | + "200", |
| 321 | + "--validate-max-steps", |
| 322 | + "100", |
| 323 | + "--db-dir", |
| 324 | + "/path/to/db", |
| 325 | + "--eth-gas-price", |
| 326 | + "10", |
| 327 | + "--strk-gas-price", |
| 328 | + "20", |
| 329 | + ]); |
| 330 | + let config = args.starknet_config(); |
| 331 | + |
| 332 | + assert!(config.disable_fee); |
| 333 | + assert!(config.disable_validate); |
| 334 | + assert_eq!(config.env.chain_id, ChainId::GOERLI); |
| 335 | + assert_eq!(config.env.invoke_max_steps, 200); |
| 336 | + assert_eq!(config.env.validate_max_steps, 100); |
| 337 | + assert_eq!(config.db_dir, Some(PathBuf::from("/path/to/db"))); |
| 338 | + assert_eq!(config.genesis.gas_prices.eth, 10); |
| 339 | + assert_eq!(config.genesis.gas_prices.strk, 20); |
| 340 | + } |
| 341 | +} |
0 commit comments