diff --git a/Cargo.lock b/Cargo.lock index 294f2dd455..fcfedda3cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8072,6 +8072,7 @@ dependencies = [ "katana-slot-controller", "serde_json", "shellexpand", + "starknet 0.12.0", "tokio", "tracing", "tracing-log 0.1.4", diff --git a/bin/katana/Cargo.toml b/bin/katana/Cargo.toml index 371e024ca0..8421bfe09c 100644 --- a/bin/katana/Cargo.toml +++ b/bin/katana/Cargo.toml @@ -31,6 +31,7 @@ url.workspace = true [dev-dependencies] assert_matches.workspace = true +starknet.workspace = true [features] default = [ "jemalloc", "slot" ] diff --git a/bin/katana/src/cli/node.rs b/bin/katana/src/cli/node.rs index c397ab9a49..f06fb84db4 100644 --- a/bin/katana/src/cli/node.rs +++ b/bin/katana/src/cli/node.rs @@ -19,12 +19,10 @@ use anyhow::{Context, Result}; use clap::{Args, Parser}; use console::Style; use dojo_utils::parse::parse_socket_address; -use katana_core::constants::{ - DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_SEQUENCER_ADDRESS, DEFAULT_STRK_L1_GAS_PRICE, -}; +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; +use katana_node::config::dev::{DevConfig, FixedL1GasPriceConfig}; use katana_node::config::execution::{ ExecutionConfig, DEFAULT_INVOCATION_MAX_STEPS, DEFAULT_VALIDATION_MAX_STEPS, }; @@ -34,7 +32,7 @@ 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; +use katana_primitives::block::{BlockHashOrNumber, GasPrices}; use katana_primitives::chain::ChainId; use katana_primitives::chain_spec::{self, ChainSpec}; use katana_primitives::class::ClassHash; @@ -189,25 +187,31 @@ pub struct EnvironmentOptions { #[arg(long)] #[arg(help = "The maximum number of steps available for the account validation logic.")] - #[arg(default_value_t = DEFAULT_VALIDATION_MAX_STEPS)] - pub validate_max_steps: u32, + pub validate_max_steps: Option, #[arg(long)] #[arg(help = "The maximum number of steps available for the account execution logic.")] - #[arg(default_value_t = DEFAULT_INVOCATION_MAX_STEPS)] - pub invoke_max_steps: u32, + pub invoke_max_steps: Option, - #[arg(long = "eth-gas-price")] - #[arg(conflicts_with = "genesis")] + #[arg(long = "l1-eth-gas-price", value_name = "WEI")] #[arg(help = "The L1 ETH gas price. (denominated in wei)")] - #[arg(default_value_t = DEFAULT_ETH_L1_GAS_PRICE)] - pub l1_eth_gas_price: u128, + #[arg(requires = "l1_strk_gas_price")] + pub l1_eth_gas_price: Option, - #[arg(long = "strk-gas-price")] - #[arg(conflicts_with = "genesis")] + #[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)")] - #[arg(default_value_t = DEFAULT_STRK_L1_GAS_PRICE)] - pub l1_strk_gas_price: u128, + 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)")] + 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)")] + pub l1_strk_data_gas_price: Option, } #[cfg(feature = "slot")] @@ -320,6 +324,8 @@ impl NodeArgs { if let Some(genesis) = self.starknet.genesis.clone() { chain_spec.genesis = genesis; + } else { + chain_spec.genesis.sequencer_address = *DEFAULT_SEQUENCER_ADDRESS; } // generate dev accounts @@ -329,9 +335,6 @@ impl NodeArgs { .generate(); chain_spec.genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); - chain_spec.genesis.sequencer_address = *DEFAULT_SEQUENCER_ADDRESS; - chain_spec.genesis.gas_prices.eth = self.starknet.environment.l1_eth_gas_price; - chain_spec.genesis.gas_prices.strk = self.starknet.environment.l1_strk_gas_price; #[cfg(feature = "slot")] if self.slot.controller { @@ -342,7 +345,25 @@ 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 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(); + + 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 }; + + Some(FixedL1GasPriceConfig { gas_price, data_gas_price }) + } else { + None + }; + DevConfig { + fixed_gas_prices, fee: !self.starknet.disable_fee, account_validation: !self.starknet.disable_validate, } @@ -350,8 +371,16 @@ impl NodeArgs { fn execution_config(&self) -> ExecutionConfig { ExecutionConfig { - invocation_max_steps: self.starknet.environment.invoke_max_steps, - validation_max_steps: self.starknet.environment.validate_max_steps, + 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), ..Default::default() } } @@ -487,6 +516,9 @@ PREFUNDED ACCOUNTS #[cfg(test)] mod test { + use assert_matches::assert_matches; + use katana_primitives::{address, felt}; + use super::*; #[test] @@ -501,8 +533,6 @@ mod test { assert_eq!(config.execution.validation_max_steps, DEFAULT_VALIDATION_MAX_STEPS); assert_eq!(config.db.dir, None); assert_eq!(config.chain.id, ChainId::parse("KATANA").unwrap()); - assert_eq!(config.chain.genesis.gas_prices.eth, DEFAULT_ETH_L1_GAS_PRICE); - assert_eq!(config.chain.genesis.gas_prices.strk, DEFAULT_STRK_L1_GAS_PRICE); assert_eq!(config.chain.genesis.sequencer_address, *DEFAULT_SEQUENCER_ADDRESS); } @@ -520,10 +550,40 @@ mod test { "100", "--db-dir", "/path/to/db", - "--eth-gas-price", + ]); + let config = args.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_eq!(config.chain.genesis.sequencer_address, *DEFAULT_SEQUENCER_ADDRESS); + } + + #[test] + fn custom_fixed_gas_prices() { + let args = 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", "10", - "--strk-gas-price", + "--l1-strk-gas-price", "20", + "--l1-eth-data-gas-price", + "1", + "--l1-strk-data-gas-price", + "2", ]); let config = args.config().unwrap(); @@ -533,7 +593,44 @@ mod test { 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_eq!(config.chain.genesis.gas_prices.eth, 10); - assert_eq!(config.chain.genesis.gas_prices.strk, 20); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, 10); + assert_eq!(prices.gas_price.strk, 20); + assert_eq!(prices.data_gas_price.eth, 1); + assert_eq!(prices.data_gas_price.strk, 2); + }) + } + + #[test] + fn genesis_with_fixed_gas_prices() { + let config = NodeArgs::parse_from([ + "katana", + "--genesis", + "./tests/test-data/genesis.json", + "--l1-eth-gas-price", + "100", + "--l1-strk-gas-price", + "200", + "--l1-eth-data-gas-price", + "111", + "--l1-strk-data-gas-price", + "222", + ]) + .config() + .unwrap(); + + 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); + assert_matches!(config.dev.fixed_gas_prices, Some(prices) => { + assert_eq!(prices.gas_price.eth, 100); + assert_eq!(prices.gas_price.strk, 200); + assert_eq!(prices.data_gas_price.eth, 111); + assert_eq!(prices.data_gas_price.strk, 222); + }) } } diff --git a/bin/katana/tests/test-data/genesis.json b/bin/katana/tests/test-data/genesis.json index 51436f4832..0a41860f30 100644 --- a/bin/katana/tests/test-data/genesis.json +++ b/bin/katana/tests/test-data/genesis.json @@ -1,42 +1,24 @@ { - "number": 0, - "parentHash": "0x999", - "timestamp": 5123512314, - "stateRoot": "0x99", - "sequencerAddress": "0x100", - "gasPrices": { - "ETH": 1111, - "STRK": 2222 - }, - "feeToken": { - "address": "0x55", - "name": "ETHER", - "symbol": "ETH", - "decimals": 18, - "storage": { - "0x111": "0x1", - "0x222": "0x2" - } - }, - "universalDeployer": { - "address": "0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf", - "storage": { - "0x10": "0x100" - } - }, - "accounts": { - "0x66efb28ac62686966ae85095ff3a772e014e7fbf56d4c5f6fac5606d4dde23a": { - "publicKey": "0x1", - "balance": "0xD3C21BCECCEDA1000000", - "nonce": "0x1", - "storage": { - "0x1": "0x1", - "0x2": "0x2" - } - } - }, - "contracts": { - }, - "classes": [ - ] + "number": 0, + "parentHash": "0x999", + "timestamp": 5123512314, + "stateRoot": "0x99", + "sequencerAddress": "0x100", + "gasPrices": { + "ETH": 9999, + "STRK": 8888 + }, + "accounts": { + "0x66efb28ac62686966ae85095ff3a772e014e7fbf56d4c5f6fac5606d4dde23a": { + "publicKey": "0x1", + "balance": "0xD3C21BCECCEDA1000000", + "nonce": "0x1", + "storage": { + "0x1": "0x1", + "0x2": "0x2" + } + } + }, + "contracts": {}, + "classes": [] } diff --git a/crates/dojo/test-utils/src/sequencer.rs b/crates/dojo/test-utils/src/sequencer.rs index 99acaf4b8c..63356be4ad 100644 --- a/crates/dojo/test-utils/src/sequencer.rs +++ b/crates/dojo/test-utils/src/sequencer.rs @@ -113,7 +113,7 @@ impl TestSequencer { } pub fn get_default_test_config(sequencing: SequencingConfig) -> Config { - let dev = DevConfig { fee: false, account_validation: true }; + let dev = DevConfig { fee: false, account_validation: true, fixed_gas_prices: None }; let mut chain = ChainSpec { id: ChainId::SEPOLIA, ..Default::default() }; chain.genesis.sequencer_address = *DEFAULT_SEQUENCER_ADDRESS; diff --git a/crates/katana/core/src/backend/gas_oracle.rs b/crates/katana/core/src/backend/gas_oracle.rs new file mode 100644 index 0000000000..1822612380 --- /dev/null +++ b/crates/katana/core/src/backend/gas_oracle.rs @@ -0,0 +1,25 @@ +use katana_primitives::block::GasPrices; + +// TODO: implement a proper gas oracle function - sample the l1 gas and data gas prices +// currently this just return the hardcoded value set from the cli or if not set, the default value. +#[derive(Debug)] +pub struct L1GasOracle { + gas_prices: GasPrices, + data_gas_prices: GasPrices, +} + +impl L1GasOracle { + pub fn fixed(gas_prices: GasPrices, data_gas_prices: GasPrices) -> Self { + Self { gas_prices, data_gas_prices } + } + + /// Returns the current gas prices. + pub fn current_gas_prices(&self) -> GasPrices { + self.gas_prices.clone() + } + + /// Returns the current data gas prices. + pub fn current_data_gas_prices(&self) -> GasPrices { + self.data_gas_prices.clone() + } +} diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 8e15426b98..7d8d74a142 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use gas_oracle::L1GasOracle; use katana_executor::{ExecutionOutput, ExecutionResult, ExecutorFactory}; use katana_primitives::block::{ FinalityStatus, Header, PartialHeader, SealedBlock, SealedBlockWithStatus, @@ -20,6 +21,7 @@ use starknet_types_core::hash::{self, StarkHash}; use tracing::info; pub mod contract; +pub mod gas_oracle; pub mod storage; use self::storage::Blockchain; @@ -38,6 +40,8 @@ pub struct Backend { pub block_context_generator: RwLock, pub executor_factory: Arc, + + pub gas_oracle: L1GasOracle, } impl Backend { @@ -104,6 +108,15 @@ impl Backend { block_env.number += 1; block_env.timestamp = timestamp; + + // update the gas prices + self.update_block_gas_prices(block_env); + } + + /// Updates the gas prices in the block environment. + pub fn update_block_gas_prices(&self, block_env: &mut BlockEnv) { + block_env.l1_gas_prices = self.gas_oracle.current_gas_prices(); + block_env.l1_data_gas_prices = self.gas_oracle.current_data_gas_prices(); } pub fn mine_empty_block( diff --git a/crates/katana/core/src/backend/storage.rs b/crates/katana/core/src/backend/storage.rs index f14f59d70d..a8f64e7b49 100644 --- a/crates/katana/core/src/backend/storage.rs +++ b/crates/katana/core/src/backend/storage.rs @@ -99,7 +99,7 @@ impl Blockchain { Ok(Self::new(provider)) } else { Err(anyhow!( - "Genesis block hash mismatch: expected {genesis_hash:#x}, got {db_hash:#}", + "Genesis block hash mismatch: expected {genesis_hash:#x}, got {db_hash:#x}", )) } } diff --git a/crates/katana/core/src/constants.rs b/crates/katana/core/src/constants.rs index 0d5a31b24f..b47908205f 100644 --- a/crates/katana/core/src/constants.rs +++ b/crates/katana/core/src/constants.rs @@ -6,6 +6,10 @@ use starknet::macros::felt; pub const DEFAULT_ETH_L1_GAS_PRICE: u128 = 100 * u128::pow(10, 9); // Given in units of Wei. pub const DEFAULT_STRK_L1_GAS_PRICE: u128 = 100 * u128::pow(10, 9); // Given in units of STRK. +// Default data gas prices +pub const DEFAULT_ETH_L1_DATA_GAS_PRICE: u128 = u128::pow(10, 6); // Given in units of Wei. +pub const DEFAULT_STRK_L1_DATA_GAS_PRICE: u128 = u128::pow(10, 9); // Given in units of STRK. + lazy_static! { // Predefined contract addresses diff --git a/crates/katana/node/src/config/dev.rs b/crates/katana/node/src/config/dev.rs index bd1dce94cf..cddd6bf64a 100644 --- a/crates/katana/node/src/config/dev.rs +++ b/crates/katana/node/src/config/dev.rs @@ -1,3 +1,5 @@ +use katana_primitives::block::GasPrices; + /// Development configuration. #[derive(Debug, Clone)] pub struct DevConfig { @@ -20,10 +22,22 @@ pub struct DevConfig { /// estimation/simulation was sent with `SKIP_VALIDATE`. Using `SKIP_VALIDATE` while /// validation is disabled is a no-op. pub account_validation: bool, + + /// Fixed L1 gas prices for development. + /// + /// These are the prices that will be used for calculating the gas fee for transactions. + pub fixed_gas_prices: Option, +} + +/// Fixed gas prices for development. +#[derive(Debug, Clone)] +pub struct FixedL1GasPriceConfig { + pub gas_price: GasPrices, + pub data_gas_price: GasPrices, } impl std::default::Default for DevConfig { fn default() -> Self { - Self { fee: true, account_validation: true } + Self { fee: true, account_validation: true, fixed_gas_prices: None } } } diff --git a/crates/katana/node/src/lib.rs b/crates/katana/node/src/lib.rs index 309fedf51d..294a244718 100644 --- a/crates/katana/node/src/lib.rs +++ b/crates/katana/node/src/lib.rs @@ -19,8 +19,13 @@ use hyper::{Method, Uri}; use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::server::{AllowHosts, ServerBuilder, ServerHandle}; use jsonrpsee::RpcModule; +use katana_core::backend::gas_oracle::L1GasOracle; use katana_core::backend::storage::Blockchain; use katana_core::backend::Backend; +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_core::env::BlockContextGenerator; use katana_core::service::block_producer::BlockProducer; use katana_core::service::messaging::MessagingConfig; @@ -31,6 +36,7 @@ use katana_pipeline::{stage, Pipeline}; use katana_pool::ordering::FiFo; use katana_pool::validation::stateful::TxValidator; use katana_pool::TxPool; +use katana_primitives::block::GasPrices; use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; use katana_rpc::dev::DevApi; use katana_rpc::metrics::RpcServerMetrics; @@ -196,8 +202,24 @@ pub async fn build(mut config: Config) -> Result { (Blockchain::new_with_db(db.clone(), &config.chain)?, Some(db), None) }; + // --- build l1 gas oracle + + // Check if the user specify a fixed gas price in the dev config. + let gas_oracle = if let Some(fixed_prices) = config.dev.fixed_gas_prices { + L1GasOracle::fixed(fixed_prices.gas_price, fixed_prices.data_gas_price) + } + // TODO: for now we just use the default gas prices, but this should be a proper oracle in the + // future that can perform actual sampling. + else { + L1GasOracle::fixed( + GasPrices { eth: DEFAULT_ETH_L1_GAS_PRICE, strk: DEFAULT_STRK_L1_GAS_PRICE }, + GasPrices { eth: DEFAULT_ETH_L1_DATA_GAS_PRICE, strk: DEFAULT_STRK_L1_DATA_GAS_PRICE }, + ) + }; + let block_context_generator = BlockContextGenerator::default().into(); let backend = Arc::new(Backend { + gas_oracle, blockchain, executor_factory, block_context_generator, diff --git a/crates/katana/rpc/rpc/src/starknet/mod.rs b/crates/katana/rpc/rpc/src/starknet/mod.rs index c06c778d50..3a15ef0130 100644 --- a/crates/katana/rpc/rpc/src/starknet/mod.rs +++ b/crates/katana/rpc/rpc/src/starknet/mod.rs @@ -200,11 +200,17 @@ impl StarknetApi { let env = match block_id { BlockIdOrTag::Tag(BlockTag::Pending) => { + // If there is a pending block, use the block env of the pending block. if let Some(exec) = self.pending_executor() { Some(exec.read().block_env()) - } else { + } + // else, we create a new block env and update the values to reflect the current + // state. + else { let num = provider.latest_number()?; - provider.block_env_at(num.into())? + let mut env = provider.block_env_at(num.into())?.expect("missing block env"); + self.inner.backend.update_block_env(&mut env); + Some(env) } } diff --git a/crates/katana/rpc/rpc/tests/starknet.rs b/crates/katana/rpc/rpc/tests/starknet.rs index f07f4ed663..9e4e07a286 100644 --- a/crates/katana/rpc/rpc/tests/starknet.rs +++ b/crates/katana/rpc/rpc/tests/starknet.rs @@ -207,7 +207,7 @@ async fn deploy_account( let contract = FeeToken::new(DEFAULT_ETH_FEE_TOKEN_ADDRESS.into(), &funding_account); // send enough tokens to the new_account's address just to send the deploy account tx - let amount = Uint256 { low: felt!("0x100000000000"), high: Felt::ZERO }; + let amount = Uint256 { low: felt!("0x1ba32524a3000"), high: Felt::ZERO }; let recipient = computed_address; let res = contract.transfer(&recipient, &amount).send().await?; dojo_utils::TransactionWaiter::new(res.transaction_hash, &provider).await?; diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index 00ba37748e..d892209a6d 100644 Binary files a/spawn-and-move-db.tar.gz and b/spawn-and-move-db.tar.gz differ diff --git a/types-test-db.tar.gz b/types-test-db.tar.gz index bcf559c601..2541418b31 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ