From ab24518cfe3ad74ae3b14d3f7e034e6c49169218 Mon Sep 17 00:00:00 2001 From: kwiss Date: Thu, 9 May 2024 15:08:22 +0200 Subject: [PATCH 01/31] feat(solis): update katana for solis --- bin/solis/Cargo.toml | 43 +++ bin/solis/src/args.rs | 341 ++++++++++++++++++ bin/solis/src/main.rs | 231 ++++++++++++ bin/solis/src/utils.rs | 34 ++ bin/solis/tests/test-data/genesis.json | 42 +++ crates/dojo-test-utils/src/sequencer.rs | 9 +- crates/katana/core/src/hooker.rs | 130 +++++++ crates/katana/core/src/lib.rs | 1 + crates/katana/core/src/sequencer.rs | 34 +- .../katana/core/src/service/messaging/mod.rs | 9 +- .../core/src/service/messaging/service.rs | 13 +- .../core/src/service/messaging/starknet.rs | 89 ++++- crates/katana/core/tests/sequencer.rs | 10 +- crates/katana/rpc/rpc-api/src/lib.rs | 2 + crates/katana/rpc/rpc-api/src/solis.rs | 10 + crates/katana/rpc/rpc/src/lib.rs | 5 + crates/katana/rpc/rpc/src/solis.rs | 47 +++ 17 files changed, 1015 insertions(+), 35 deletions(-) create mode 100644 bin/solis/Cargo.toml create mode 100644 bin/solis/src/args.rs create mode 100644 bin/solis/src/main.rs create mode 100644 bin/solis/src/utils.rs create mode 100644 bin/solis/tests/test-data/genesis.json create mode 100644 crates/katana/core/src/hooker.rs create mode 100644 crates/katana/rpc/rpc-api/src/solis.rs create mode 100644 crates/katana/rpc/rpc/src/solis.rs diff --git a/bin/solis/Cargo.toml b/bin/solis/Cargo.toml new file mode 100644 index 0000000000..384c576fc0 --- /dev/null +++ b/bin/solis/Cargo.toml @@ -0,0 +1,43 @@ +[package] +description = "A fast and lightweight local Starknet development sequencer." +edition.workspace = true +license-file.workspace = true +name = "solis" +repository.workspace = true +version.workspace = true + +[dependencies] +alloy-primitives.workspace = true +anyhow.workspace = true +cfg-if = "1.0.0" +clap.workspace = true +clap_complete.workspace = true +common.workspace = true +console.workspace = true +dojo-metrics.workspace = true +katana-core.workspace = true +katana-executor.workspace = true +katana-primitives.workspace = true +katana-rpc-api.workspace = true +katana-rpc.workspace = true +serde_json.workspace = true +shellexpand = "3.1.0" +starknet_api.workspace = true +tokio.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true +url.workspace = true + +[dev-dependencies] +assert_matches = "1.5.0" + +[features] +default = [ "blockifier", "jemalloc", "messaging" ] + +blockifier = [ "katana-executor/blockifier" ] +# Disable until SIR support Cairo 2.6.3 +#sir = [ "katana-executor/sir" ] + +jemalloc = [ "dojo-metrics/jemalloc" ] +messaging = [ "katana-core/messaging" ] +starknet-messaging = [ "katana-core/starknet-messaging", "messaging" ] diff --git a/bin/solis/src/args.rs b/bin/solis/src/args.rs new file mode 100644 index 0000000000..3c3c99499c --- /dev/null +++ b/bin/solis/src/args.rs @@ -0,0 +1,341 @@ +//! Katana binary executable. +//! +//! ## Feature Flags +//! +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. + +use std::net::SocketAddr; +use std::path::PathBuf; + +use alloy_primitives::U256; +use clap::{Args, Parser, Subcommand}; +use clap_complete::Shell; +use common::parse::parse_socket_address; +use katana_core::backend::config::{Environment, StarknetConfig}; +use katana_core::constants::{ + DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_SEQUENCER_ADDRESS, + DEFAULT_STRK_L1_GAS_PRICE, DEFAULT_VALIDATE_MAX_STEPS, +}; +use katana_core::sequencer::SequencerConfig; +use katana_primitives::block::GasPrices; +use katana_primitives::chain::ChainId; +use katana_primitives::genesis::allocation::DevAllocationsGenerator; +use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; +use katana_primitives::genesis::Genesis; +use katana_rpc::config::ServerConfig; +use katana_rpc_api::ApiKind; +use tracing::Subscriber; +use tracing_subscriber::{fmt, EnvFilter}; +use url::Url; + +use crate::utils::{parse_genesis, parse_seed}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct KatanaArgs { + #[arg(long)] + #[arg(help = "Don't print anything on startup.")] + pub silent: bool, + + #[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, + + #[arg(short, long)] + #[arg(value_name = "MILLISECONDS")] + #[arg(help = "Block time in milliseconds for interval mining.")] + pub block_time: Option, + + #[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)] + #[arg(value_name = "URL")] + #[arg(help = "The Starknet RPC provider to fork the network from.")] + pub rpc_url: Option, + + #[arg(long)] + pub dev: bool, + + #[arg(long)] + #[arg(help = "Output logs in JSON format.")] + pub json_log: bool, + + /// Enable Prometheus metrics. + /// + /// 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, + + #[arg(long)] + #[arg(requires = "rpc_url")] + #[arg(value_name = "BLOCK_NUMBER")] + #[arg(help = "Fork the network at a specific block.")] + pub fork_block_number: Option, + + #[cfg(feature = "messaging")] + #[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 server: ServerOptions, + + #[command(flatten)] + #[command(next_help_heading = "Starknet options")] + pub starknet: StarknetOptions, + + #[command(subcommand)] + pub command: Option, +} + +#[derive(Debug, Subcommand)] +pub enum Commands { + #[command(about = "Generate shell completion file for specified shell")] + Completions { shell: Shell }, +} + +#[derive(Debug, Args, Clone)] +pub struct ServerOptions { + #[arg(short, long)] + #[arg(default_value = "5050")] + #[arg(help = "Port number to listen on.")] + pub port: u16, + + #[arg(long)] + #[arg(help = "The IP address the server will listen on.")] + pub host: Option, + + #[arg(long)] + #[arg(default_value = "100")] + #[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)] +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 = "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(["rpc_url", "seed", "total_accounts"]))] + pub genesis: Option, +} + +#[derive(Debug, Args, Clone)] +pub struct EnvironmentOptions { + #[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(default_value = "KATANA")] + #[arg(value_parser = ChainId::parse)] + pub chain_id: ChainId, + + #[arg(long)] + #[arg(help = "The maximum number of steps available for the account validation logic.")] + #[arg(default_value_t = DEFAULT_VALIDATE_MAX_STEPS)] + pub validate_max_steps: u32, + + #[arg(long)] + #[arg(help = "The maximum number of steps available for the account execution logic.")] + #[arg(default_value_t = DEFAULT_INVOKE_MAX_STEPS)] + pub invoke_max_steps: u32, + + #[arg(long = "eth-gas-price")] + #[arg(conflicts_with = "genesis")] + #[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(long = "strk-gas-price")] + #[arg(conflicts_with = "genesis")] + #[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, +} + +impl KatanaArgs { + pub fn init_logging(&self) -> Result<(), Box> { + const DEFAULT_LOG_FILTER: &str = "info,executor=trace,forked_backend=trace,server=debug,\ + katana_core=trace,blockifier=off,jsonrpsee_server=off,\ + hyper=off,messaging=debug,node=error"; + + let builder = fmt::Subscriber::builder().with_env_filter( + EnvFilter::try_from_default_env().or(EnvFilter::try_new(DEFAULT_LOG_FILTER))?, + ); + + let subscriber: Box = if self.json_log { + Box::new(builder.json().finish()) + } else { + Box::new(builder.finish()) + }; + + Ok(tracing::subscriber::set_global_default(subscriber)?) + } + + pub fn sequencer_config(&self) -> SequencerConfig { + SequencerConfig { + block_time: self.block_time, + no_mining: self.no_mining, + #[cfg(feature = "messaging")] + messaging: self.messaging.clone(), + } + } + + pub fn server_config(&self) -> ServerConfig { + let mut apis = vec![ApiKind::Starknet, ApiKind::Katana, ApiKind::Torii, ApiKind::Saya]; + // only enable `katana` API in dev mode + if self.dev { + apis.push(ApiKind::Dev); + } + + ServerConfig { + apis, + port: self.server.port, + host: self.server.host.clone().unwrap_or("0.0.0.0".into()), + max_connections: self.server.max_connections, + allowed_origins: self.server.allowed_origins.clone(), + } + } + + pub fn starknet_config(&self) -> StarknetConfig { + let genesis = match self.starknet.genesis.clone() { + Some(genesis) => genesis, + None => { + let gas_prices = GasPrices { + eth: self.starknet.environment.l1_eth_gas_price, + strk: self.starknet.environment.l1_strk_gas_price, + }; + + let accounts = DevAllocationsGenerator::new(self.starknet.total_accounts) + .with_seed(parse_seed(&self.starknet.seed)) + .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) + .generate(); + + let mut genesis = Genesis { + gas_prices, + sequencer_address: *DEFAULT_SEQUENCER_ADDRESS, + ..Default::default() + }; + + genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); + genesis + } + }; + + StarknetConfig { + disable_fee: self.starknet.disable_fee, + disable_validate: self.starknet.disable_validate, + fork_rpc_url: self.rpc_url.clone(), + fork_block_number: self.fork_block_number, + env: Environment { + chain_id: self.starknet.environment.chain_id, + invoke_max_steps: self.starknet.environment.invoke_max_steps, + validate_max_steps: self.starknet.environment.validate_max_steps, + }, + db_dir: self.db_dir.clone(), + genesis, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_starknet_config_default() { + let args = KatanaArgs::parse_from(["katana"]); + let config = args.starknet_config(); + + assert!(!config.disable_fee); + assert!(!config.disable_validate); + assert_eq!(config.fork_rpc_url, None); + assert_eq!(config.fork_block_number, None); + assert_eq!(config.env.chain_id, ChainId::parse("KATANA").unwrap()); + assert_eq!(config.env.invoke_max_steps, DEFAULT_INVOKE_MAX_STEPS); + assert_eq!(config.env.validate_max_steps, DEFAULT_VALIDATE_MAX_STEPS); + assert_eq!(config.db_dir, None); + assert_eq!(config.genesis.gas_prices.eth, DEFAULT_ETH_L1_GAS_PRICE); + assert_eq!(config.genesis.gas_prices.strk, DEFAULT_STRK_L1_GAS_PRICE); + assert_eq!(config.genesis.sequencer_address, *DEFAULT_SEQUENCER_ADDRESS); + } + + #[test] + fn test_starknet_config_custom() { + let args = KatanaArgs::parse_from([ + "katana", + "--disable-fee", + "--disable-validate", + "--chain-id", + "SN_GOERLI", + "--invoke-max-steps", + "200", + "--validate-max-steps", + "100", + "--db-dir", + "/path/to/db", + "--eth-gas-price", + "10", + "--strk-gas-price", + "20", + ]); + let config = args.starknet_config(); + + assert!(config.disable_fee); + assert!(config.disable_validate); + assert_eq!(config.env.chain_id, ChainId::GOERLI); + assert_eq!(config.env.invoke_max_steps, 200); + assert_eq!(config.env.validate_max_steps, 100); + assert_eq!(config.db_dir, Some(PathBuf::from("/path/to/db"))); + assert_eq!(config.genesis.gas_prices.eth, 10); + assert_eq!(config.genesis.gas_prices.strk, 20); + } +} diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs new file mode 100644 index 0000000000..ffd96c4dec --- /dev/null +++ b/bin/solis/src/main.rs @@ -0,0 +1,231 @@ +use std::io; +use std::net::SocketAddr; +use std::sync::Arc; + +use clap::{CommandFactory, Parser}; +use clap_complete::{generate, Shell}; +use console::Style; +use dojo_metrics::{metrics_process, prometheus_exporter}; +use katana_core::constants::MAX_RECURSION_DEPTH; +use katana_core::env::get_default_vm_resource_fee_cost; +use katana_core::sequencer::KatanaSequencer; +use katana_executor::SimulationFlag; +use katana_primitives::class::ClassHash; +use katana_primitives::contract::ContractAddress; +use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; +use katana_primitives::genesis::allocation::GenesisAccountAlloc; +use katana_primitives::genesis::Genesis; +use katana_rpc::{spawn, NodeHandle}; +use tokio::signal::ctrl_c; +use tracing::info; + +mod args; +mod utils; + +use args::Commands::Completions; +use args::KatanaArgs; + +pub(crate) const LOG_TARGET: &str = "katana::cli"; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = KatanaArgs::parse(); + args.init_logging()?; + + if let Some(command) = args.command { + match command { + Completions { shell } => { + print_completion(shell); + return Ok(()); + } + } + } + + let server_config = args.server_config(); + let sequencer_config = args.sequencer_config(); + let starknet_config = args.starknet_config(); + + let cfg_env = CfgEnv { + chain_id: starknet_config.env.chain_id, + vm_resource_fee_cost: get_default_vm_resource_fee_cost(), + invoke_tx_max_n_steps: starknet_config.env.invoke_max_steps, + validate_max_n_steps: starknet_config.env.validate_max_steps, + max_recursion_depth: MAX_RECURSION_DEPTH, + fee_token_addresses: FeeTokenAddressses { + eth: starknet_config.genesis.fee_token.address, + strk: Default::default(), + }, + }; + + let simulation_flags = SimulationFlag { + skip_validate: starknet_config.disable_validate, + skip_fee_transfer: starknet_config.disable_fee, + ..Default::default() + }; + + // TODO: Uncomment this once we enable the 'sir' feature again because it's not compatible with + // our current Cairo version (2.6.3). cfg_if::cfg_if! { + // if #[cfg(all(feature = "blockifier", feature = "sir"))] { + // compile_error!("Cannot enable both `blockifier` and `sir` features at the same + // time"); } else if #[cfg(feature = "blockifier")] { + // use katana_executor::implementation::blockifier::BlockifierFactory; + // let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); + // } else if #[cfg(feature = "sir")] { + // use katana_executor::implementation::sir::NativeExecutorFactory; + // let executor_factory = NativeExecutorFactory::new(cfg_env, simulation_flags); + // } else { + // compile_error!("At least one of the following features must be enabled: blockifier, + // sir"); } + // } + + use katana_executor::implementation::blockifier::BlockifierFactory; + let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); + + if let Some(listen_addr) = args.metrics { + let prometheus_handle = prometheus_exporter::install_recorder("katana")?; + + info!(target: LOG_TARGET, addr = %listen_addr, "Starting metrics endpoint."); + prometheus_exporter::serve( + listen_addr, + prometheus_handle, + metrics_process::Collector::default(), + ) + .await?; + } + + let sequencer = + Arc::new(KatanaSequencer::new(executor_factory, sequencer_config, starknet_config).await?); + let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; + + if !args.silent { + let genesis = &sequencer.backend().config.genesis; + print_intro(&args, genesis, addr); + } + + // Wait until Ctrl + C is pressed, then shutdown + ctrl_c().await?; + handle.stop()?; + + Ok(()) +} + +fn print_completion(shell: Shell) { + let mut command = KatanaArgs::command(); + let name = command.get_name().to_string(); + generate(shell, &mut command, name, &mut io::stdout()); +} + +fn print_intro(args: &KatanaArgs, genesis: &Genesis, address: SocketAddr) { + let mut accounts = genesis.accounts().peekable(); + let account_class_hash = accounts.peek().map(|e| e.1.class_hash()); + let seed = &args.starknet.seed; + + if args.json_log { + info!( + target: LOG_TARGET, + "{}", + serde_json::json!({ + "accounts": accounts.map(|a| serde_json::json!(a)).collect::>(), + "seed": format!("{}", seed), + "address": format!("{address}"), + }) + ) + } else { + println!( + "{}", + Style::new().red().apply_to( + r" + + +██╗ ██╗ █████╗ ████████╗ █████╗ ███╗ ██╗ █████╗ +██║ ██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗ ██║██╔══██╗ +█████╔╝ ███████║ ██║ ███████║██╔██╗ ██║███████║ +██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║ +██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ +" + ) + ); + + print_genesis_contracts(genesis, account_class_hash); + print_genesis_accounts(accounts); + + println!( + r" + +ACCOUNTS SEED +============= +{seed} + " + ); + + let addr = format!( + "🚀 JSON-RPC server started: {}", + Style::new().red().apply_to(format!("http://{address}")) + ); + + println!("\n{addr}\n\n",); + } +} + +fn print_genesis_contracts(genesis: &Genesis, account_class_hash: Option) { + println!( + r" +PREDEPLOYED CONTRACTS +================== + +| Contract | Fee Token +| Address | {} +| Class Hash | {:#064x}", + genesis.fee_token.address, genesis.fee_token.class_hash, + ); + + if let Some(ref udc) = genesis.universal_deployer { + println!( + r" +| Contract | Universal Deployer +| Address | {} +| Class Hash | {:#064x}", + udc.address, udc.class_hash + ) + } + + if let Some(hash) = account_class_hash { + println!( + r" +| Contract | Account Contract +| Class Hash | {hash:#064x}" + ) + } +} + +fn print_genesis_accounts<'a, Accounts>(accounts: Accounts) +where + Accounts: Iterator, +{ + println!( + r" + +PREFUNDED ACCOUNTS +==================" + ); + + for (addr, account) in accounts { + if let Some(pk) = account.private_key() { + println!( + r" +| Account address | {addr} +| Private key | {pk:#x} +| Public key | {:#x}", + account.public_key() + ) + } else { + println!( + r" +| Account address | {addr} +| Public key | {:#x}", + account.public_key() + ) + } + } +} diff --git a/bin/solis/src/utils.rs b/bin/solis/src/utils.rs new file mode 100644 index 0000000000..f05f5a7634 --- /dev/null +++ b/bin/solis/src/utils.rs @@ -0,0 +1,34 @@ +use std::path::PathBuf; + +use katana_primitives::genesis::json::GenesisJson; +use katana_primitives::genesis::Genesis; + +pub fn parse_seed(seed: &str) -> [u8; 32] { + let seed = seed.as_bytes(); + + if seed.len() >= 32 { + unsafe { *(seed[..32].as_ptr() as *const [u8; 32]) } + } else { + let mut actual_seed = [0u8; 32]; + seed.iter().enumerate().for_each(|(i, b)| actual_seed[i] = *b); + actual_seed + } +} + +/// Used as clap value parser for [Genesis]. +pub fn parse_genesis(value: &str) -> Result { + let path = PathBuf::from(shellexpand::full(value)?.into_owned()); + let genesis = Genesis::try_from(GenesisJson::load(path)?)?; + Ok(genesis) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_genesis_file() { + let path = "./tests/test-data/genesis.json"; + parse_genesis(path).unwrap(); + } +} diff --git a/bin/solis/tests/test-data/genesis.json b/bin/solis/tests/test-data/genesis.json new file mode 100644 index 0000000000..51436f4832 --- /dev/null +++ b/bin/solis/tests/test-data/genesis.json @@ -0,0 +1,42 @@ +{ + "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": [ + ] +} diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index 086d4dc26b..e447d10d32 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -4,9 +4,11 @@ use jsonrpsee::core::Error; pub use katana_core::backend::config::{Environment, StarknetConfig}; use katana_core::constants::MAX_RECURSION_DEPTH; use katana_core::env::get_default_vm_resource_fee_cost; +use katana_core::hooker::DefaultKatanaHooker; use katana_core::sequencer::KatanaSequencer; pub use katana_core::sequencer::SequencerConfig; use katana_executor::implementation::blockifier::BlockifierFactory; +use katana_executor::implementation::noop::NoopExecutorFactory; use katana_executor::SimulationFlag; use katana_primitives::chain::ChainId; use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; @@ -19,6 +21,7 @@ use starknet::core::types::{BlockId, BlockTag, FieldElement}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use starknet::signers::{LocalWallet, SigningKey}; +use tokio::sync::RwLock; use url::Url; pub struct TestAccount { @@ -56,8 +59,11 @@ impl TestSequencer { let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); + let mut hooker = DefaultKatanaHooker::::new(); + let hooker_arc = Arc::new(RwLock::new(hooker)); + let sequencer = Arc::new( - KatanaSequencer::new(executor_factory, config, starknet_config) + KatanaSequencer::new(executor_factory, config, starknet_config, hooker_arc) .await .expect("Failed to create sequencer"), ); @@ -75,6 +81,7 @@ impl TestSequencer { ApiKind::Dev, ApiKind::Saya, ApiKind::Torii, + ApiKind::Solis, ], }, ) diff --git a/crates/katana/core/src/hooker.rs b/crates/katana/core/src/hooker.rs new file mode 100644 index 0000000000..316f9b0b39 --- /dev/null +++ b/crates/katana/core/src/hooker.rs @@ -0,0 +1,130 @@ +//! This module contains a hooker trait, that is added to katana in order to +//! allow external code to react at some precise moment of katana processing. +use std::sync::Arc; + +use async_trait::async_trait; +use katana_executor::ExecutorFactory; +use starknet::accounts::Call; +use starknet::core::types::{BroadcastedInvokeTransaction, FieldElement}; + +use crate::sequencer::KatanaSequencer; + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Copy, PartialEq, Eq)] +pub struct HookerAddresses { + pub orderbook_arkchain: FieldElement, + pub executor_starknet: FieldElement, +} + +#[async_trait] +pub trait KatanaHooker { + /// Sets a reference to the underlying sequencer. + fn set_sequencer(&mut self, sequencer: Arc>); + + /// Runs code right before a message from the L1 is converted + /// into a `L1HandlerTransaction`. This hook is usefull to + /// apply conditions on the message being captured. + /// + /// # Arguments + /// + /// * `from` - The contract on L2 sending the message. + /// * `to` - The recipient contract on the appchain. + /// * `selector` - The l1_handler of the appchain contract to execute. + async fn verify_message_to_appchain( + &self, + from: FieldElement, + to: FieldElement, + selector: FieldElement, + ) -> bool; + + /// Runs code right before an invoke transaction + /// is being added to the pool. + /// Returns true if the transaction should be included + /// in the pool, false otherwise. + /// + /// # Arguments + /// + /// * `transaction` - The invoke transaction to be verified. + async fn verify_invoke_tx_before_pool(&self, transaction: BroadcastedInvokeTransaction) + -> bool; + + /// Runs code right before a message to starknet + /// is being sent via a direct transaction. + /// As the message is sent to starknet in a transaction + /// the `Call` of the transaction is being verified. + /// + /// # Arguments + /// + /// * `call` - The `Call` to inspect, built from the + /// message. + async fn verify_tx_for_starknet(&self, call: Call) -> bool; + + /// Runs when Solis attempts to execute an order on Starknet, + /// but it fails. + /// + /// # Arguments + /// + /// * `call` - The `Call` of the transaction that has failed. Usually the same as + /// `verify_message_to_starknet_before_tx`. + async fn on_starknet_tx_failed(&self, call: Call); + + /// Sets important addresses. + /// + /// # Arguments + /// + /// * `addresses` - Important addresses related to solis. + fn set_addresses(&mut self, addresses: HookerAddresses); +} + +#[derive()] +pub struct DefaultKatanaHooker { + sequencer: Option>>, + addresses: Option, +} + +impl DefaultKatanaHooker { + pub fn new() -> Self { + DefaultKatanaHooker { + sequencer: None, + addresses: None, + } + } + pub fn set_sequencer(&mut self, sequencer: Arc>) { + self.sequencer = Some(sequencer); + } +} + +#[async_trait] +impl KatanaHooker for DefaultKatanaHooker { + fn set_sequencer(&mut self, sequencer: Arc>) { + self.sequencer = Some(sequencer); + } + + async fn verify_message_to_appchain( + &self, + _from: FieldElement, + _to: FieldElement, + _selector: FieldElement, + ) -> bool { + true + } + + async fn verify_invoke_tx_before_pool( + &self, + _transaction: BroadcastedInvokeTransaction, + ) -> bool { + true + } + + async fn verify_tx_for_starknet(&self, _call: Call) -> bool { + true + } + + async fn on_starknet_tx_failed(&self, _call: Call) { + // Log the failure or handle it according to your needs. No-op by default. + print!("Starknet transaction failed: {:?}", _call); + } + + fn set_addresses(&mut self, addresses: HookerAddresses) { + self.addresses = Some(addresses); + } +} diff --git a/crates/katana/core/src/lib.rs b/crates/katana/core/src/lib.rs index 44d3fa5ab0..099e04b025 100644 --- a/crates/katana/core/src/lib.rs +++ b/crates/katana/core/src/lib.rs @@ -5,5 +5,6 @@ pub mod pool; pub mod sequencer; pub mod service; pub mod utils; +pub mod hooker; pub mod sequencer_error; diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 0a0db8678d..ede1257064 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -1,3 +1,6 @@ +use crate::hooker::{HookerAddresses, KatanaHooker}; +use tokio::sync::RwLock as AsyncRwLock; + use std::cmp::Ordering; use std::iter::Skip; use std::slice::Iter; @@ -52,6 +55,7 @@ pub struct KatanaSequencer { pub pool: Arc, pub backend: Arc>, pub block_producer: Arc>, + pub hooker: Arc + Send + Sync>>, } impl KatanaSequencer { @@ -59,6 +63,7 @@ impl KatanaSequencer { executor_factory: EF, config: SequencerConfig, starknet_config: StarknetConfig, + hooker: Arc + Send + Sync>>, ) -> anyhow::Result { let executor_factory = Arc::new(executor_factory); let backend = Arc::new(Backend::new(executor_factory.clone(), starknet_config).await); @@ -78,7 +83,9 @@ impl KatanaSequencer { #[cfg(feature = "messaging")] let messaging = if let Some(config) = config.messaging.clone() { - MessagingService::new(config, Arc::clone(&pool), Arc::clone(&backend)).await.ok() + MessagingService::new(config, Arc::clone(&pool), Arc::clone(&backend), hooker.clone()) + .await + .ok() } else { None }; @@ -93,7 +100,7 @@ impl KatanaSequencer { messaging, )); - Ok(Self { pool, config, backend, block_producer }) + Ok(Self { pool, config, backend, block_producer, hooker }) } /// Returns the pending state if the sequencer is running in _interval_ mode. Otherwise `None`. @@ -286,10 +293,13 @@ impl KatanaSequencer { let tx @ Some(_) = tx else { return Ok(self.pending_executor().as_ref().and_then(|exec| { - exec.read() - .transactions() - .iter() - .find_map(|tx| if tx.0.hash == *hash { Some(tx.0.clone()) } else { None }) + exec.read().transactions().iter().find_map(|tx| { + if tx.0.hash == *hash { + Some(tx.0.clone()) + } else { + None + } + }) })); }; @@ -497,26 +507,28 @@ fn filter_events_by_params( #[cfg(test)] mod tests { + use super::*; + use crate::hooker::DefaultKatanaHooker; use katana_executor::implementation::noop::NoopExecutorFactory; use katana_provider::traits::block::BlockNumberProvider; - - use super::{KatanaSequencer, SequencerConfig}; - use crate::backend::config::StarknetConfig; - + use std::sync::Arc; + use tokio::sync::RwLock; #[tokio::test] async fn init_interval_block_producer_with_correct_block_env() { let executor_factory = NoopExecutorFactory::default(); + let hooker = DefaultKatanaHooker::::new(); + let hooker_arc = Arc::new(RwLock::new(hooker)); let sequencer = KatanaSequencer::new( executor_factory, SequencerConfig { no_mining: true, ..Default::default() }, StarknetConfig::default(), + hooker_arc, // Pass the hooker instance here ) .await .unwrap(); let provider = sequencer.backend.blockchain.provider(); - let latest_num = provider.latest_number().unwrap(); let producer_block_env = sequencer.pending_executor().unwrap().read().block_env(); diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index f07b353e8c..a21b456552 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -32,6 +32,10 @@ //! configuration file following the `MessagingConfig` format. An example of this file can be found //! in the messaging contracts. +use crate::hooker::KatanaHooker; +use std::sync::Arc; +use tokio::sync::RwLock as AsyncRwLock; + mod ethereum; mod service; #[cfg(feature = "starknet-messaging")] @@ -169,7 +173,10 @@ pub enum MessengerMode { } impl MessengerMode { - pub async fn from_config(config: MessagingConfig) -> MessengerResult { + pub async fn from_config( + config: MessagingConfig, + hooker: Arc>>, + ) -> MessengerResult { match config.chain.as_str() { CONFIG_CHAIN_ETHEREUM => match EthereumMessaging::new(config).await { Ok(m_eth) => { diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index a4a6810c29..d221b88798 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -1,7 +1,5 @@ -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Duration; +use crate::hooker::KatanaHooker; +use tokio::sync::RwLock as AsyncRwLock; use futures::{Future, FutureExt, Stream}; use katana_executor::ExecutorFactory; @@ -10,6 +8,10 @@ use katana_primitives::receipt::MessageToL1; use katana_primitives::transaction::{ExecutableTxWithHash, L1HandlerTx, TxHash}; use katana_provider::traits::block::BlockNumberProvider; use katana_provider::traits::transaction::ReceiptProvider; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; use tokio::time::{interval_at, Instant, Interval}; use tracing::{error, info}; @@ -45,10 +47,11 @@ impl MessagingService { config: MessagingConfig, pool: Arc, backend: Arc>, + hooker: Arc + Send + Sync>>, ) -> anyhow::Result { let gather_from_block = config.from_block; let interval = interval_from_seconds(config.interval); - let messenger = match MessengerMode::from_config(config).await { + let messenger = match MessengerMode::from_config(config, hooker).await { Ok(m) => Arc::new(m), Err(_) => { panic!( diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 0c1b242721..ec0a6871e6 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -1,3 +1,6 @@ +use crate::hooker::KatanaHooker; +use tokio::sync::RwLock as AsyncRwLock; + use std::collections::HashMap; use std::sync::Arc; @@ -29,16 +32,20 @@ const EXE_MAGIC: FieldElement = felt!("0x455845"); pub const HASH_EXEC: FieldElement = felt!("0xee"); -pub struct StarknetMessaging { +pub struct StarknetMessaging { chain_id: FieldElement, provider: AnyProvider, wallet: LocalWallet, sender_account_address: FieldElement, messaging_contract_address: FieldElement, + hooker: Arc + Send + Sync>>, } -impl StarknetMessaging { - pub async fn new(config: MessagingConfig) -> Result { +impl StarknetMessaging { + pub async fn new( + config: MessagingConfig, + hooker: Arc + Send + Sync>>, + ) -> Result { let provider = AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new( Url::parse(&config.rpc_url)?, ))); @@ -57,6 +64,7 @@ impl StarknetMessaging { chain_id, sender_account_address, messaging_contract_address, + hooker, }) } @@ -125,7 +133,19 @@ impl StarknetMessaging { let estimated_fee = (execution.estimate_fee().await?.overall_fee) * 10u64.into(); let tx = execution.max_fee(estimated_fee).send().await?; - Ok(tx.transaction_hash) + // We need logs to debug the starknet transactions. + match execution.send().await { + Ok(tx) => { + info!("Transaction successful: {:?}", tx); + println!("tx: {:?}", tx); + println!("tx_hash: {:?}", tx.transaction_hash); + Ok(tx.transaction_hash) + } + Err(e) => { + error!("Error sending transaction: {:?}", e); + Err(e.into()) + } + } } /// Sends messages hashes to settlement layer by sending a transaction. @@ -210,7 +230,18 @@ impl Messenger for StarknetMessaging { block_events.iter().for_each(|e| { if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { - l1_handler_txs.push(tx) + if let Ok((from, to, selector)) = info_from_event(e) { + let is_message_accepted = self + .hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; + + if is_message_accepted { + l1_handler_txs.push(tx) + } + } } }) }); @@ -228,18 +259,23 @@ impl Messenger for StarknetMessaging { let (hashes, calls) = parse_messages(messages)?; - if !calls.is_empty() { - match self.send_invoke_tx(calls).await { - Ok(tx_hash) => { - trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); - } - Err(e) => { - error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); - return Err(Error::SendError); - } - }; + for call in &calls { + if !self.hooker.read().await.verify_tx_for_starknet(call.clone()).await { + continue; + } + if !calls.is_empty() { + match self.send_invoke_tx(calls).await { + Ok(tx_hash) => { + trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); + } + Err(e) => { + error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); + self.hooker.read().await.on_starknet_tx_failed(call.clone()).await; + return Err(Error::SendError); + } + }; + } } - self.send_hashes(hashes.clone()).await?; Ok(hashes) @@ -351,6 +387,27 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result Result<(FieldElement, FieldElement, FieldElement)> { + if event.keys[0] != selector!("MessageSentToAppchain") { + debug!( + target: LOG_TARGET, + "Event with key {:?} can't be converted into L1HandlerTx", event.keys[0], + ); + return Err(Error::GatherError.into()); + } + + if event.keys.len() != 4 || event.data.len() < 2 { + error!(target: LOG_TARGET, "Event MessageSentToAppchain is not well formatted"); + } + + // See contrat appchain_messaging.cairo for MessageSentToAppchain event. + let from_address = event.keys[2]; + let to_address = event.keys[3]; + let entry_point_selector = event.data[0]; + + Ok((from_address, to_address, entry_point_selector)) +} + #[cfg(test)] mod tests { diff --git a/crates/katana/core/tests/sequencer.rs b/crates/katana/core/tests/sequencer.rs index 4d3ffb1494..a19417ade6 100644 --- a/crates/katana/core/tests/sequencer.rs +++ b/crates/katana/core/tests/sequencer.rs @@ -1,3 +1,4 @@ +use katana_core::hooker::DefaultKatanaHooker; use alloy_primitives::U256; use katana_core::backend::config::StarknetConfig; use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; @@ -7,6 +8,8 @@ use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; use katana_primitives::genesis::Genesis; use katana_provider::traits::block::{BlockNumberProvider, BlockProvider}; use katana_provider::traits::env::BlockEnvProvider; +use std::sync::Arc; +use tokio::sync::RwLock; fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { let accounts = DevAllocationsGenerator::new(2) @@ -24,8 +27,13 @@ fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { async fn create_test_sequencer() -> KatanaSequencer { let executor_factory = NoopExecutorFactory::new(); + let hooker = DefaultKatanaHooker::::new(); + let hooker_arc = Arc::new(RwLock::new(hooker)); + let (sequencer_config, starknet_config) = create_test_sequencer_config(); - KatanaSequencer::new(executor_factory, sequencer_config, starknet_config).await.unwrap() + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, hooker_arc) + .await + .unwrap() } #[tokio::test] diff --git a/crates/katana/rpc/rpc-api/src/lib.rs b/crates/katana/rpc/rpc-api/src/lib.rs index 198766158b..c56f5cbd27 100644 --- a/crates/katana/rpc/rpc-api/src/lib.rs +++ b/crates/katana/rpc/rpc-api/src/lib.rs @@ -1,6 +1,7 @@ pub mod dev; pub mod katana; pub mod saya; +pub mod solis; pub mod starknet; pub mod torii; @@ -12,4 +13,5 @@ pub enum ApiKind { Torii, Dev, Saya, + Solis } diff --git a/crates/katana/rpc/rpc-api/src/solis.rs b/crates/katana/rpc/rpc-api/src/solis.rs new file mode 100644 index 0000000000..ea47906f35 --- /dev/null +++ b/crates/katana/rpc/rpc-api/src/solis.rs @@ -0,0 +1,10 @@ +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; +use katana_core::hooker::HookerAddresses; + +#[cfg_attr(not(feature = "client"), rpc(server, namespace = "solis"))] +#[cfg_attr(feature = "client", rpc(client, server, namespace = "solis"))] +pub trait SolisApi { + #[method(name = "setSolisAddresses")] + async fn set_addresses(&self, addresses: HookerAddresses, basic_auth: String) -> RpcResult<()>; +} diff --git a/crates/katana/rpc/rpc/src/lib.rs b/crates/katana/rpc/rpc/src/lib.rs index edc855f0a3..af23cfdd25 100644 --- a/crates/katana/rpc/rpc/src/lib.rs +++ b/crates/katana/rpc/rpc/src/lib.rs @@ -23,6 +23,7 @@ use katana_executor::ExecutorFactory; use katana_rpc_api::dev::DevApiServer; use katana_rpc_api::katana::KatanaApiServer; use katana_rpc_api::saya::SayaApiServer; +use katana_rpc_api::solis::SolisApiServer; use katana_rpc_api::starknet::StarknetApiServer; use katana_rpc_api::torii::ToriiApiServer; use katana_rpc_api::ApiKind; @@ -34,6 +35,7 @@ use crate::katana::KatanaApi; use crate::saya::SayaApi; use crate::starknet::StarknetApi; use crate::torii::ToriiApi; +use crate::solis::SolisApi; pub async fn spawn( sequencer: Arc>, @@ -59,6 +61,9 @@ pub async fn spawn( ApiKind::Saya => { methods.merge(SayaApi::new(sequencer.clone()).into_rpc())?; } + ApiKind::Solis => { + methods.merge(SolisApi::new(sequencer.clone()).into_rpc())?; + } } } diff --git a/crates/katana/rpc/rpc/src/solis.rs b/crates/katana/rpc/rpc/src/solis.rs new file mode 100644 index 0000000000..e2d53ab689 --- /dev/null +++ b/crates/katana/rpc/rpc/src/solis.rs @@ -0,0 +1,47 @@ +use std::sync::Arc; + +use jsonrpsee::core::{async_trait, Error}; +use katana_core::sequencer::KatanaSequencer; +use katana_executor::ExecutorFactory; +use katana_rpc_api::katana::SolisApiServer; +use katana_rpc_types::account::Account; + +pub struct SolisApi { + sequencer: Arc>, +} + +impl SolisApi { + pub fn new(sequencer: Arc>) -> Self { + Self { sequencer } + } + + fn verify_basic_auth(&self, encoded_credentials: &str) -> bool { + if let Ok(credentials) = decode(encoded_credentials) { + if let Ok(credentials_str) = String::from_utf8(credentials) { + let parts: Vec<&str> = credentials_str.split(':').collect(); + if parts.len() == 2 { + let (username, password) = (parts[0], parts[1]); + return username == self.config.rpc_user + && password == self.config.rpc_password; + } + } + } + false + } +} + +#[async_trait] +impl SolisApiServer for SolisApi { + async fn set_addresses( + &self, + addresses: HookerAddresses, + basic_auth: String, + ) -> Result<(), Error> { + if !self.verify_basic_auth(&basic_auth) { + panic!("authentication failed"); + } + + self.sequencer.set_addresses(addresses).await; + Ok(()) + } +} From d55091885c7bc757f95423738276e522167aa7bd Mon Sep 17 00:00:00 2001 From: kwiss Date: Fri, 10 May 2024 16:51:35 +0200 Subject: [PATCH 02/31] fix(hooker): update hooker & api --- Cargo.lock | 1 + bin/solis/src/args.rs | 2 +- crates/dojo-test-utils/src/sequencer.rs | 2 ++ crates/katana/core/src/sequencer.rs | 2 +- crates/katana/rpc/rpc/Cargo.toml | 1 + crates/katana/rpc/rpc/src/lib.rs | 1 + crates/katana/rpc/rpc/src/solis.rs | 17 ++++++++++------- 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8b0ff571c..9516175c5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6928,6 +6928,7 @@ version = "0.7.0-alpha.1" dependencies = [ "anyhow", "assert_matches", + "base64 0.13.1", "cairo-lang-starknet", "cairo-lang-starknet-classes", "dojo-metrics", diff --git a/bin/solis/src/args.rs b/bin/solis/src/args.rs index 3c3c99499c..78eaba08f5 100644 --- a/bin/solis/src/args.rs +++ b/bin/solis/src/args.rs @@ -230,7 +230,7 @@ impl KatanaArgs { } pub fn server_config(&self) -> ServerConfig { - let mut apis = vec![ApiKind::Starknet, ApiKind::Katana, ApiKind::Torii, ApiKind::Saya]; + let mut apis = vec![ApiKind::Starknet, ApiKind::Katana, ApiKind::Torii, ApiKind::Saya, ApiKind::Solis]; // only enable `katana` API in dev mode if self.dev { apis.push(ApiKind::Dev); diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index e447d10d32..f0e2498ade 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -75,6 +75,8 @@ impl TestSequencer { host: "127.0.0.1".into(), max_connections: 100, allowed_origins: None, + rpc_password: "password".into(), + rpc_user: "user".into(), apis: vec![ ApiKind::Starknet, ApiKind::Katana, diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index ede1257064..ab76fb07ef 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -1,4 +1,4 @@ -use crate::hooker::{HookerAddresses, KatanaHooker}; +use crate::hooker::KatanaHooker; use tokio::sync::RwLock as AsyncRwLock; use std::cmp::Ordering; diff --git a/crates/katana/rpc/rpc/Cargo.toml b/crates/katana/rpc/rpc/Cargo.toml index 0ffc37bd24..1ea4ee797d 100644 --- a/crates/katana/rpc/rpc/Cargo.toml +++ b/crates/katana/rpc/rpc/Cargo.toml @@ -16,6 +16,7 @@ katana-rpc-api.workspace = true katana-rpc-types-builder.workspace = true katana-rpc-types.workspace = true katana-tasks.workspace = true +base64 = "0.13.0" anyhow.workspace = true flate2.workspace = true diff --git a/crates/katana/rpc/rpc/src/lib.rs b/crates/katana/rpc/rpc/src/lib.rs index af23cfdd25..01d70d8ca9 100644 --- a/crates/katana/rpc/rpc/src/lib.rs +++ b/crates/katana/rpc/rpc/src/lib.rs @@ -7,6 +7,7 @@ pub mod metrics; pub mod saya; pub mod starknet; pub mod torii; +pub mod solis; use std::net::SocketAddr; use std::sync::Arc; diff --git a/crates/katana/rpc/rpc/src/solis.rs b/crates/katana/rpc/rpc/src/solis.rs index e2d53ab689..6a54276327 100644 --- a/crates/katana/rpc/rpc/src/solis.rs +++ b/crates/katana/rpc/rpc/src/solis.rs @@ -1,28 +1,30 @@ +use base64::decode; use std::sync::Arc; use jsonrpsee::core::{async_trait, Error}; +use katana_core::hooker::HookerAddresses; use katana_core::sequencer::KatanaSequencer; use katana_executor::ExecutorFactory; -use katana_rpc_api::katana::SolisApiServer; -use katana_rpc_types::account::Account; +use katana_rpc_api::solis::SolisApiServer; pub struct SolisApi { sequencer: Arc>, + pub rpc_user: String, + pub rpc_password: String, } impl SolisApi { pub fn new(sequencer: Arc>) -> Self { - Self { sequencer } + Self { sequencer, rpc_user: "".to_string(), rpc_password: "".to_string() } } - + fn verify_basic_auth(&self, encoded_credentials: &str) -> bool { if let Ok(credentials) = decode(encoded_credentials) { if let Ok(credentials_str) = String::from_utf8(credentials) { let parts: Vec<&str> = credentials_str.split(':').collect(); if parts.len() == 2 { let (username, password) = (parts[0], parts[1]); - return username == self.config.rpc_user - && password == self.config.rpc_password; + return username == self.rpc_user && password == self.rpc_password; } } } @@ -41,7 +43,8 @@ impl SolisApiServer for SolisApi { panic!("authentication failed"); } - self.sequencer.set_addresses(addresses).await; + let mut hooker = self.sequencer.hooker.write().await; + hooker.set_addresses(addresses); Ok(()) } } From 0a7113afdadcedb5d29627aca080ed3448d75089 Mon Sep 17 00:00:00 2001 From: kwiss Date: Fri, 10 May 2024 17:04:33 +0200 Subject: [PATCH 03/31] fix(config): pass config to solis api --- crates/katana/rpc/rpc/src/config.rs | 2 ++ crates/katana/rpc/rpc/src/lib.rs | 6 +++--- crates/katana/rpc/rpc/src/solis.rs | 9 +++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/katana/rpc/rpc/src/config.rs b/crates/katana/rpc/rpc/src/config.rs index 5a88af1bb9..2d5b910280 100644 --- a/crates/katana/rpc/rpc/src/config.rs +++ b/crates/katana/rpc/rpc/src/config.rs @@ -7,6 +7,8 @@ pub struct ServerConfig { pub max_connections: u32, pub allowed_origins: Option>, pub apis: Vec, + pub rpc_user: String, + pub rpc_password: String, } impl ServerConfig { diff --git a/crates/katana/rpc/rpc/src/lib.rs b/crates/katana/rpc/rpc/src/lib.rs index 01d70d8ca9..5ec0d9167a 100644 --- a/crates/katana/rpc/rpc/src/lib.rs +++ b/crates/katana/rpc/rpc/src/lib.rs @@ -5,9 +5,9 @@ pub mod dev; pub mod katana; pub mod metrics; pub mod saya; +pub mod solis; pub mod starknet; pub mod torii; -pub mod solis; use std::net::SocketAddr; use std::sync::Arc; @@ -34,9 +34,9 @@ use tower_http::cors::{AllowOrigin, CorsLayer}; use crate::dev::DevApi; use crate::katana::KatanaApi; use crate::saya::SayaApi; +use crate::solis::SolisApi; use crate::starknet::StarknetApi; use crate::torii::ToriiApi; -use crate::solis::SolisApi; pub async fn spawn( sequencer: Arc>, @@ -63,7 +63,7 @@ pub async fn spawn( methods.merge(SayaApi::new(sequencer.clone()).into_rpc())?; } ApiKind::Solis => { - methods.merge(SolisApi::new(sequencer.clone()).into_rpc())?; + methods.merge(SolisApi::new(sequencer.clone(), &config).into_rpc())?; } } } diff --git a/crates/katana/rpc/rpc/src/solis.rs b/crates/katana/rpc/rpc/src/solis.rs index 6a54276327..6e0f2f6efe 100644 --- a/crates/katana/rpc/rpc/src/solis.rs +++ b/crates/katana/rpc/rpc/src/solis.rs @@ -1,6 +1,7 @@ use base64::decode; use std::sync::Arc; +use crate::config::ServerConfig; use jsonrpsee::core::{async_trait, Error}; use katana_core::hooker::HookerAddresses; use katana_core::sequencer::KatanaSequencer; @@ -14,8 +15,12 @@ pub struct SolisApi { } impl SolisApi { - pub fn new(sequencer: Arc>) -> Self { - Self { sequencer, rpc_user: "".to_string(), rpc_password: "".to_string() } + pub fn new(sequencer: Arc>, config: &ServerConfig) -> Self { + Self { + sequencer, + rpc_user: config.rpc_user.clone(), + rpc_password: config.rpc_password.clone(), + } } fn verify_basic_auth(&self, encoded_credentials: &str) -> bool { From bf7dfa684a6cafeeeffcaea47d2833d383a7b1a7 Mon Sep 17 00:00:00 2001 From: kwiss Date: Sat, 11 May 2024 00:48:49 +0200 Subject: [PATCH 04/31] feat(errors): add error handling for solis api & add optionnal hooker --- bin/katana/src/args.rs | 2 ++ bin/katana/src/main.rs | 5 +-- crates/dojo-test-utils/src/sequencer.rs | 8 +---- crates/katana/core/src/sequencer.rs | 23 ++++++++------ crates/katana/core/tests/sequencer.rs | 10 +----- crates/katana/rpc/rpc-types/src/error/mod.rs | 1 + .../katana/rpc/rpc-types/src/error/solis.rs | 31 +++++++++++++++++++ crates/katana/rpc/rpc/src/solis.rs | 18 ++++++----- 8 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 crates/katana/rpc/rpc-types/src/error/solis.rs diff --git a/bin/katana/src/args.rs b/bin/katana/src/args.rs index 3c3c99499c..8eb4a84524 100644 --- a/bin/katana/src/args.rs +++ b/bin/katana/src/args.rs @@ -242,6 +242,8 @@ impl KatanaArgs { host: self.server.host.clone().unwrap_or("0.0.0.0".into()), max_connections: self.server.max_connections, allowed_origins: self.server.allowed_origins.clone(), + rpc_user: "user".into(), + rpc_password: "password".into(), } } diff --git a/bin/katana/src/main.rs b/bin/katana/src/main.rs index ffd96c4dec..f0d8668f92 100644 --- a/bin/katana/src/main.rs +++ b/bin/katana/src/main.rs @@ -93,8 +93,9 @@ async fn main() -> Result<(), Box> { .await?; } - let sequencer = - Arc::new(KatanaSequencer::new(executor_factory, sequencer_config, starknet_config).await?); + let sequencer = Arc::new( + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, None).await?, + ); let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; if !args.silent { diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index f0e2498ade..6435cc8918 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -4,11 +4,9 @@ use jsonrpsee::core::Error; pub use katana_core::backend::config::{Environment, StarknetConfig}; use katana_core::constants::MAX_RECURSION_DEPTH; use katana_core::env::get_default_vm_resource_fee_cost; -use katana_core::hooker::DefaultKatanaHooker; use katana_core::sequencer::KatanaSequencer; pub use katana_core::sequencer::SequencerConfig; use katana_executor::implementation::blockifier::BlockifierFactory; -use katana_executor::implementation::noop::NoopExecutorFactory; use katana_executor::SimulationFlag; use katana_primitives::chain::ChainId; use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; @@ -21,7 +19,6 @@ use starknet::core::types::{BlockId, BlockTag, FieldElement}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use starknet::signers::{LocalWallet, SigningKey}; -use tokio::sync::RwLock; use url::Url; pub struct TestAccount { @@ -59,11 +56,8 @@ impl TestSequencer { let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); - let mut hooker = DefaultKatanaHooker::::new(); - let hooker_arc = Arc::new(RwLock::new(hooker)); - let sequencer = Arc::new( - KatanaSequencer::new(executor_factory, config, starknet_config, hooker_arc) + KatanaSequencer::new(executor_factory, config, starknet_config, None) .await .expect("Failed to create sequencer"), ); diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index ab76fb07ef..76bfa23bf0 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -55,7 +55,7 @@ pub struct KatanaSequencer { pub pool: Arc, pub backend: Arc>, pub block_producer: Arc>, - pub hooker: Arc + Send + Sync>>, + pub hooker: Option + Send + Sync>>>, } impl KatanaSequencer { @@ -63,7 +63,7 @@ impl KatanaSequencer { executor_factory: EF, config: SequencerConfig, starknet_config: StarknetConfig, - hooker: Arc + Send + Sync>>, + hooker: Option + Send + Sync>>>, ) -> anyhow::Result { let executor_factory = Arc::new(executor_factory); let backend = Arc::new(Backend::new(executor_factory.clone(), starknet_config).await); @@ -83,9 +83,17 @@ impl KatanaSequencer { #[cfg(feature = "messaging")] let messaging = if let Some(config) = config.messaging.clone() { - MessagingService::new(config, Arc::clone(&pool), Arc::clone(&backend), hooker.clone()) + match &hooker { + Some(hooker_ref) => MessagingService::new( + config, + Arc::clone(&pool), + Arc::clone(&backend), + hooker_ref.clone(), + ) .await - .ok() + .ok(), + None => None, // Handle the case where no hooker is provided + } } else { None }; @@ -508,22 +516,17 @@ fn filter_events_by_params( #[cfg(test)] mod tests { use super::*; - use crate::hooker::DefaultKatanaHooker; use katana_executor::implementation::noop::NoopExecutorFactory; use katana_provider::traits::block::BlockNumberProvider; - use std::sync::Arc; - use tokio::sync::RwLock; #[tokio::test] async fn init_interval_block_producer_with_correct_block_env() { let executor_factory = NoopExecutorFactory::default(); - let hooker = DefaultKatanaHooker::::new(); - let hooker_arc = Arc::new(RwLock::new(hooker)); let sequencer = KatanaSequencer::new( executor_factory, SequencerConfig { no_mining: true, ..Default::default() }, StarknetConfig::default(), - hooker_arc, // Pass the hooker instance here + None, // Pass the hooker instance here ) .await .unwrap(); diff --git a/crates/katana/core/tests/sequencer.rs b/crates/katana/core/tests/sequencer.rs index a19417ade6..da74ce0472 100644 --- a/crates/katana/core/tests/sequencer.rs +++ b/crates/katana/core/tests/sequencer.rs @@ -1,4 +1,3 @@ -use katana_core::hooker::DefaultKatanaHooker; use alloy_primitives::U256; use katana_core::backend::config::StarknetConfig; use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; @@ -8,8 +7,6 @@ use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; use katana_primitives::genesis::Genesis; use katana_provider::traits::block::{BlockNumberProvider, BlockProvider}; use katana_provider::traits::env::BlockEnvProvider; -use std::sync::Arc; -use tokio::sync::RwLock; fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { let accounts = DevAllocationsGenerator::new(2) @@ -27,13 +24,8 @@ fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { async fn create_test_sequencer() -> KatanaSequencer { let executor_factory = NoopExecutorFactory::new(); - let hooker = DefaultKatanaHooker::::new(); - let hooker_arc = Arc::new(RwLock::new(hooker)); - let (sequencer_config, starknet_config) = create_test_sequencer_config(); - KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, hooker_arc) - .await - .unwrap() + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, None).await.unwrap() } #[tokio::test] diff --git a/crates/katana/rpc/rpc-types/src/error/mod.rs b/crates/katana/rpc/rpc-types/src/error/mod.rs index e935e90516..c68defcea8 100644 --- a/crates/katana/rpc/rpc-types/src/error/mod.rs +++ b/crates/katana/rpc/rpc-types/src/error/mod.rs @@ -2,3 +2,4 @@ pub mod katana; pub mod saya; pub mod starknet; pub mod torii; +pub mod solis; \ No newline at end of file diff --git a/crates/katana/rpc/rpc-types/src/error/solis.rs b/crates/katana/rpc/rpc-types/src/error/solis.rs new file mode 100644 index 0000000000..bff847171f --- /dev/null +++ b/crates/katana/rpc/rpc-types/src/error/solis.rs @@ -0,0 +1,31 @@ +use futures::channel::mpsc::Receiver; +use jsonrpsee::core::{async_trait, Error as RpcError}; +use jsonrpsee::types::error::CallError; +use jsonrpsee::types::ErrorObject; +use std::sync::Arc; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SolisApiError { + #[error("Authentication failed")] + AuthenticationFailed, + + #[error("Hooker service is unavailable")] + HookerServiceUnavailable, + + #[error("An unexpected error occurred: {0}")] + UnexpectedError(String), +} + +impl SolisApiError { + pub fn to_rpc_error(&self) -> RpcError { + let error_code = match self { + SolisApiError::AuthenticationFailed => -32000, + SolisApiError::HookerServiceUnavailable => -32001, + SolisApiError::UnexpectedError(_) => -32002, + }; + let message = self.to_string(); + let error_object = ErrorObject::owned(error_code, message, None::<()>); + RpcError::Call(CallError::Custom(error_object)) + } +} diff --git a/crates/katana/rpc/rpc/src/solis.rs b/crates/katana/rpc/rpc/src/solis.rs index 6e0f2f6efe..16d4690674 100644 --- a/crates/katana/rpc/rpc/src/solis.rs +++ b/crates/katana/rpc/rpc/src/solis.rs @@ -2,12 +2,12 @@ use base64::decode; use std::sync::Arc; use crate::config::ServerConfig; -use jsonrpsee::core::{async_trait, Error}; +use jsonrpsee::core::{async_trait, Error as RpcError}; +use katana_rpc_types::error::solis::SolisApiError; use katana_core::hooker::HookerAddresses; use katana_core::sequencer::KatanaSequencer; use katana_executor::ExecutorFactory; use katana_rpc_api::solis::SolisApiServer; - pub struct SolisApi { sequencer: Arc>, pub rpc_user: String, @@ -43,13 +43,17 @@ impl SolisApiServer for SolisApi { &self, addresses: HookerAddresses, basic_auth: String, - ) -> Result<(), Error> { + ) -> Result<(), RpcError> { if !self.verify_basic_auth(&basic_auth) { - panic!("authentication failed"); + return Err(SolisApiError::AuthenticationFailed.to_rpc_error()); } - let mut hooker = self.sequencer.hooker.write().await; - hooker.set_addresses(addresses); - Ok(()) + if let Some(hooker_lock) = self.sequencer.hooker.as_ref() { + let mut hooker = hooker_lock.write().await; + hooker.set_addresses(addresses); + Ok(()) + } else { + return Err(SolisApiError::HookerServiceUnavailable.to_rpc_error()); + } } } From 519eab103b4be7fbb7e6bbf5b9f9adc30e9345f2 Mon Sep 17 00:00:00 2001 From: kwiss Date: Sat, 11 May 2024 00:50:40 +0200 Subject: [PATCH 05/31] feat(solis): remove solis bin in favor of external impl --- bin/solis/Cargo.toml | 43 ---- bin/solis/src/args.rs | 341 ------------------------- bin/solis/src/main.rs | 231 ----------------- bin/solis/src/utils.rs | 34 --- bin/solis/tests/test-data/genesis.json | 42 --- 5 files changed, 691 deletions(-) delete mode 100644 bin/solis/Cargo.toml delete mode 100644 bin/solis/src/args.rs delete mode 100644 bin/solis/src/main.rs delete mode 100644 bin/solis/src/utils.rs delete mode 100644 bin/solis/tests/test-data/genesis.json diff --git a/bin/solis/Cargo.toml b/bin/solis/Cargo.toml deleted file mode 100644 index 384c576fc0..0000000000 --- a/bin/solis/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -description = "A fast and lightweight local Starknet development sequencer." -edition.workspace = true -license-file.workspace = true -name = "solis" -repository.workspace = true -version.workspace = true - -[dependencies] -alloy-primitives.workspace = true -anyhow.workspace = true -cfg-if = "1.0.0" -clap.workspace = true -clap_complete.workspace = true -common.workspace = true -console.workspace = true -dojo-metrics.workspace = true -katana-core.workspace = true -katana-executor.workspace = true -katana-primitives.workspace = true -katana-rpc-api.workspace = true -katana-rpc.workspace = true -serde_json.workspace = true -shellexpand = "3.1.0" -starknet_api.workspace = true -tokio.workspace = true -tracing-subscriber.workspace = true -tracing.workspace = true -url.workspace = true - -[dev-dependencies] -assert_matches = "1.5.0" - -[features] -default = [ "blockifier", "jemalloc", "messaging" ] - -blockifier = [ "katana-executor/blockifier" ] -# Disable until SIR support Cairo 2.6.3 -#sir = [ "katana-executor/sir" ] - -jemalloc = [ "dojo-metrics/jemalloc" ] -messaging = [ "katana-core/messaging" ] -starknet-messaging = [ "katana-core/starknet-messaging", "messaging" ] diff --git a/bin/solis/src/args.rs b/bin/solis/src/args.rs deleted file mode 100644 index 78eaba08f5..0000000000 --- a/bin/solis/src/args.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! Katana binary executable. -//! -//! ## Feature Flags -//! -//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. -//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) -//! for more info. -//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling -//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) -//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) -//! for more info. - -use std::net::SocketAddr; -use std::path::PathBuf; - -use alloy_primitives::U256; -use clap::{Args, Parser, Subcommand}; -use clap_complete::Shell; -use common::parse::parse_socket_address; -use katana_core::backend::config::{Environment, StarknetConfig}; -use katana_core::constants::{ - DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_SEQUENCER_ADDRESS, - DEFAULT_STRK_L1_GAS_PRICE, DEFAULT_VALIDATE_MAX_STEPS, -}; -use katana_core::sequencer::SequencerConfig; -use katana_primitives::block::GasPrices; -use katana_primitives::chain::ChainId; -use katana_primitives::genesis::allocation::DevAllocationsGenerator; -use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; -use katana_primitives::genesis::Genesis; -use katana_rpc::config::ServerConfig; -use katana_rpc_api::ApiKind; -use tracing::Subscriber; -use tracing_subscriber::{fmt, EnvFilter}; -use url::Url; - -use crate::utils::{parse_genesis, parse_seed}; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -#[command(propagate_version = true)] -pub struct KatanaArgs { - #[arg(long)] - #[arg(help = "Don't print anything on startup.")] - pub silent: bool, - - #[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, - - #[arg(short, long)] - #[arg(value_name = "MILLISECONDS")] - #[arg(help = "Block time in milliseconds for interval mining.")] - pub block_time: Option, - - #[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)] - #[arg(value_name = "URL")] - #[arg(help = "The Starknet RPC provider to fork the network from.")] - pub rpc_url: Option, - - #[arg(long)] - pub dev: bool, - - #[arg(long)] - #[arg(help = "Output logs in JSON format.")] - pub json_log: bool, - - /// Enable Prometheus metrics. - /// - /// 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, - - #[arg(long)] - #[arg(requires = "rpc_url")] - #[arg(value_name = "BLOCK_NUMBER")] - #[arg(help = "Fork the network at a specific block.")] - pub fork_block_number: Option, - - #[cfg(feature = "messaging")] - #[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 server: ServerOptions, - - #[command(flatten)] - #[command(next_help_heading = "Starknet options")] - pub starknet: StarknetOptions, - - #[command(subcommand)] - pub command: Option, -} - -#[derive(Debug, Subcommand)] -pub enum Commands { - #[command(about = "Generate shell completion file for specified shell")] - Completions { shell: Shell }, -} - -#[derive(Debug, Args, Clone)] -pub struct ServerOptions { - #[arg(short, long)] - #[arg(default_value = "5050")] - #[arg(help = "Port number to listen on.")] - pub port: u16, - - #[arg(long)] - #[arg(help = "The IP address the server will listen on.")] - pub host: Option, - - #[arg(long)] - #[arg(default_value = "100")] - #[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)] -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 = "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(["rpc_url", "seed", "total_accounts"]))] - pub genesis: Option, -} - -#[derive(Debug, Args, Clone)] -pub struct EnvironmentOptions { - #[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(default_value = "KATANA")] - #[arg(value_parser = ChainId::parse)] - pub chain_id: ChainId, - - #[arg(long)] - #[arg(help = "The maximum number of steps available for the account validation logic.")] - #[arg(default_value_t = DEFAULT_VALIDATE_MAX_STEPS)] - pub validate_max_steps: u32, - - #[arg(long)] - #[arg(help = "The maximum number of steps available for the account execution logic.")] - #[arg(default_value_t = DEFAULT_INVOKE_MAX_STEPS)] - pub invoke_max_steps: u32, - - #[arg(long = "eth-gas-price")] - #[arg(conflicts_with = "genesis")] - #[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(long = "strk-gas-price")] - #[arg(conflicts_with = "genesis")] - #[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, -} - -impl KatanaArgs { - pub fn init_logging(&self) -> Result<(), Box> { - const DEFAULT_LOG_FILTER: &str = "info,executor=trace,forked_backend=trace,server=debug,\ - katana_core=trace,blockifier=off,jsonrpsee_server=off,\ - hyper=off,messaging=debug,node=error"; - - let builder = fmt::Subscriber::builder().with_env_filter( - EnvFilter::try_from_default_env().or(EnvFilter::try_new(DEFAULT_LOG_FILTER))?, - ); - - let subscriber: Box = if self.json_log { - Box::new(builder.json().finish()) - } else { - Box::new(builder.finish()) - }; - - Ok(tracing::subscriber::set_global_default(subscriber)?) - } - - pub fn sequencer_config(&self) -> SequencerConfig { - SequencerConfig { - block_time: self.block_time, - no_mining: self.no_mining, - #[cfg(feature = "messaging")] - messaging: self.messaging.clone(), - } - } - - pub fn server_config(&self) -> ServerConfig { - let mut apis = vec![ApiKind::Starknet, ApiKind::Katana, ApiKind::Torii, ApiKind::Saya, ApiKind::Solis]; - // only enable `katana` API in dev mode - if self.dev { - apis.push(ApiKind::Dev); - } - - ServerConfig { - apis, - port: self.server.port, - host: self.server.host.clone().unwrap_or("0.0.0.0".into()), - max_connections: self.server.max_connections, - allowed_origins: self.server.allowed_origins.clone(), - } - } - - pub fn starknet_config(&self) -> StarknetConfig { - let genesis = match self.starknet.genesis.clone() { - Some(genesis) => genesis, - None => { - let gas_prices = GasPrices { - eth: self.starknet.environment.l1_eth_gas_price, - strk: self.starknet.environment.l1_strk_gas_price, - }; - - let accounts = DevAllocationsGenerator::new(self.starknet.total_accounts) - .with_seed(parse_seed(&self.starknet.seed)) - .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) - .generate(); - - let mut genesis = Genesis { - gas_prices, - sequencer_address: *DEFAULT_SEQUENCER_ADDRESS, - ..Default::default() - }; - - genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); - genesis - } - }; - - StarknetConfig { - disable_fee: self.starknet.disable_fee, - disable_validate: self.starknet.disable_validate, - fork_rpc_url: self.rpc_url.clone(), - fork_block_number: self.fork_block_number, - env: Environment { - chain_id: self.starknet.environment.chain_id, - invoke_max_steps: self.starknet.environment.invoke_max_steps, - validate_max_steps: self.starknet.environment.validate_max_steps, - }, - db_dir: self.db_dir.clone(), - genesis, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_starknet_config_default() { - let args = KatanaArgs::parse_from(["katana"]); - let config = args.starknet_config(); - - assert!(!config.disable_fee); - assert!(!config.disable_validate); - assert_eq!(config.fork_rpc_url, None); - assert_eq!(config.fork_block_number, None); - assert_eq!(config.env.chain_id, ChainId::parse("KATANA").unwrap()); - assert_eq!(config.env.invoke_max_steps, DEFAULT_INVOKE_MAX_STEPS); - assert_eq!(config.env.validate_max_steps, DEFAULT_VALIDATE_MAX_STEPS); - assert_eq!(config.db_dir, None); - assert_eq!(config.genesis.gas_prices.eth, DEFAULT_ETH_L1_GAS_PRICE); - assert_eq!(config.genesis.gas_prices.strk, DEFAULT_STRK_L1_GAS_PRICE); - assert_eq!(config.genesis.sequencer_address, *DEFAULT_SEQUENCER_ADDRESS); - } - - #[test] - fn test_starknet_config_custom() { - let args = KatanaArgs::parse_from([ - "katana", - "--disable-fee", - "--disable-validate", - "--chain-id", - "SN_GOERLI", - "--invoke-max-steps", - "200", - "--validate-max-steps", - "100", - "--db-dir", - "/path/to/db", - "--eth-gas-price", - "10", - "--strk-gas-price", - "20", - ]); - let config = args.starknet_config(); - - assert!(config.disable_fee); - assert!(config.disable_validate); - assert_eq!(config.env.chain_id, ChainId::GOERLI); - assert_eq!(config.env.invoke_max_steps, 200); - assert_eq!(config.env.validate_max_steps, 100); - assert_eq!(config.db_dir, Some(PathBuf::from("/path/to/db"))); - assert_eq!(config.genesis.gas_prices.eth, 10); - assert_eq!(config.genesis.gas_prices.strk, 20); - } -} diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs deleted file mode 100644 index ffd96c4dec..0000000000 --- a/bin/solis/src/main.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::io; -use std::net::SocketAddr; -use std::sync::Arc; - -use clap::{CommandFactory, Parser}; -use clap_complete::{generate, Shell}; -use console::Style; -use dojo_metrics::{metrics_process, prometheus_exporter}; -use katana_core::constants::MAX_RECURSION_DEPTH; -use katana_core::env::get_default_vm_resource_fee_cost; -use katana_core::sequencer::KatanaSequencer; -use katana_executor::SimulationFlag; -use katana_primitives::class::ClassHash; -use katana_primitives::contract::ContractAddress; -use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; -use katana_primitives::genesis::allocation::GenesisAccountAlloc; -use katana_primitives::genesis::Genesis; -use katana_rpc::{spawn, NodeHandle}; -use tokio::signal::ctrl_c; -use tracing::info; - -mod args; -mod utils; - -use args::Commands::Completions; -use args::KatanaArgs; - -pub(crate) const LOG_TARGET: &str = "katana::cli"; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let args = KatanaArgs::parse(); - args.init_logging()?; - - if let Some(command) = args.command { - match command { - Completions { shell } => { - print_completion(shell); - return Ok(()); - } - } - } - - let server_config = args.server_config(); - let sequencer_config = args.sequencer_config(); - let starknet_config = args.starknet_config(); - - let cfg_env = CfgEnv { - chain_id: starknet_config.env.chain_id, - vm_resource_fee_cost: get_default_vm_resource_fee_cost(), - invoke_tx_max_n_steps: starknet_config.env.invoke_max_steps, - validate_max_n_steps: starknet_config.env.validate_max_steps, - max_recursion_depth: MAX_RECURSION_DEPTH, - fee_token_addresses: FeeTokenAddressses { - eth: starknet_config.genesis.fee_token.address, - strk: Default::default(), - }, - }; - - let simulation_flags = SimulationFlag { - skip_validate: starknet_config.disable_validate, - skip_fee_transfer: starknet_config.disable_fee, - ..Default::default() - }; - - // TODO: Uncomment this once we enable the 'sir' feature again because it's not compatible with - // our current Cairo version (2.6.3). cfg_if::cfg_if! { - // if #[cfg(all(feature = "blockifier", feature = "sir"))] { - // compile_error!("Cannot enable both `blockifier` and `sir` features at the same - // time"); } else if #[cfg(feature = "blockifier")] { - // use katana_executor::implementation::blockifier::BlockifierFactory; - // let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); - // } else if #[cfg(feature = "sir")] { - // use katana_executor::implementation::sir::NativeExecutorFactory; - // let executor_factory = NativeExecutorFactory::new(cfg_env, simulation_flags); - // } else { - // compile_error!("At least one of the following features must be enabled: blockifier, - // sir"); } - // } - - use katana_executor::implementation::blockifier::BlockifierFactory; - let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); - - if let Some(listen_addr) = args.metrics { - let prometheus_handle = prometheus_exporter::install_recorder("katana")?; - - info!(target: LOG_TARGET, addr = %listen_addr, "Starting metrics endpoint."); - prometheus_exporter::serve( - listen_addr, - prometheus_handle, - metrics_process::Collector::default(), - ) - .await?; - } - - let sequencer = - Arc::new(KatanaSequencer::new(executor_factory, sequencer_config, starknet_config).await?); - let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; - - if !args.silent { - let genesis = &sequencer.backend().config.genesis; - print_intro(&args, genesis, addr); - } - - // Wait until Ctrl + C is pressed, then shutdown - ctrl_c().await?; - handle.stop()?; - - Ok(()) -} - -fn print_completion(shell: Shell) { - let mut command = KatanaArgs::command(); - let name = command.get_name().to_string(); - generate(shell, &mut command, name, &mut io::stdout()); -} - -fn print_intro(args: &KatanaArgs, genesis: &Genesis, address: SocketAddr) { - let mut accounts = genesis.accounts().peekable(); - let account_class_hash = accounts.peek().map(|e| e.1.class_hash()); - let seed = &args.starknet.seed; - - if args.json_log { - info!( - target: LOG_TARGET, - "{}", - serde_json::json!({ - "accounts": accounts.map(|a| serde_json::json!(a)).collect::>(), - "seed": format!("{}", seed), - "address": format!("{address}"), - }) - ) - } else { - println!( - "{}", - Style::new().red().apply_to( - r" - - -██╗ ██╗ █████╗ ████████╗ █████╗ ███╗ ██╗ █████╗ -██║ ██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗ ██║██╔══██╗ -█████╔╝ ███████║ ██║ ███████║██╔██╗ ██║███████║ -██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║ -██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║ -╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ -" - ) - ); - - print_genesis_contracts(genesis, account_class_hash); - print_genesis_accounts(accounts); - - println!( - r" - -ACCOUNTS SEED -============= -{seed} - " - ); - - let addr = format!( - "🚀 JSON-RPC server started: {}", - Style::new().red().apply_to(format!("http://{address}")) - ); - - println!("\n{addr}\n\n",); - } -} - -fn print_genesis_contracts(genesis: &Genesis, account_class_hash: Option) { - println!( - r" -PREDEPLOYED CONTRACTS -================== - -| Contract | Fee Token -| Address | {} -| Class Hash | {:#064x}", - genesis.fee_token.address, genesis.fee_token.class_hash, - ); - - if let Some(ref udc) = genesis.universal_deployer { - println!( - r" -| Contract | Universal Deployer -| Address | {} -| Class Hash | {:#064x}", - udc.address, udc.class_hash - ) - } - - if let Some(hash) = account_class_hash { - println!( - r" -| Contract | Account Contract -| Class Hash | {hash:#064x}" - ) - } -} - -fn print_genesis_accounts<'a, Accounts>(accounts: Accounts) -where - Accounts: Iterator, -{ - println!( - r" - -PREFUNDED ACCOUNTS -==================" - ); - - for (addr, account) in accounts { - if let Some(pk) = account.private_key() { - println!( - r" -| Account address | {addr} -| Private key | {pk:#x} -| Public key | {:#x}", - account.public_key() - ) - } else { - println!( - r" -| Account address | {addr} -| Public key | {:#x}", - account.public_key() - ) - } - } -} diff --git a/bin/solis/src/utils.rs b/bin/solis/src/utils.rs deleted file mode 100644 index f05f5a7634..0000000000 --- a/bin/solis/src/utils.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::path::PathBuf; - -use katana_primitives::genesis::json::GenesisJson; -use katana_primitives::genesis::Genesis; - -pub fn parse_seed(seed: &str) -> [u8; 32] { - let seed = seed.as_bytes(); - - if seed.len() >= 32 { - unsafe { *(seed[..32].as_ptr() as *const [u8; 32]) } - } else { - let mut actual_seed = [0u8; 32]; - seed.iter().enumerate().for_each(|(i, b)| actual_seed[i] = *b); - actual_seed - } -} - -/// Used as clap value parser for [Genesis]. -pub fn parse_genesis(value: &str) -> Result { - let path = PathBuf::from(shellexpand::full(value)?.into_owned()); - let genesis = Genesis::try_from(GenesisJson::load(path)?)?; - Ok(genesis) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_genesis_file() { - let path = "./tests/test-data/genesis.json"; - parse_genesis(path).unwrap(); - } -} diff --git a/bin/solis/tests/test-data/genesis.json b/bin/solis/tests/test-data/genesis.json deleted file mode 100644 index 51436f4832..0000000000 --- a/bin/solis/tests/test-data/genesis.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "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": [ - ] -} From dfbc077c101d6791add25ff8eb7ed8bbe761c2d8 Mon Sep 17 00:00:00 2001 From: kwiss Date: Mon, 13 May 2024 23:03:29 +0200 Subject: [PATCH 06/31] feat(solis): add solis bin --- Cargo.lock | 27 ++ Cargo.toml | 1 + bin/solis/Cargo.toml | 43 ++++ bin/solis/src/args.rs | 343 +++++++++++++++++++++++++ bin/solis/src/main.rs | 232 +++++++++++++++++ bin/solis/src/utils.rs | 34 +++ bin/solis/tests/test-data/genesis.json | 42 +++ messaging.local.json | 9 + 8 files changed, 731 insertions(+) create mode 100644 bin/solis/Cargo.toml create mode 100644 bin/solis/src/args.rs create mode 100644 bin/solis/src/main.rs create mode 100644 bin/solis/src/utils.rs create mode 100644 bin/solis/tests/test-data/genesis.json create mode 100644 messaging.local.json diff --git a/Cargo.lock b/Cargo.lock index 9516175c5c..ed8e31e9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11309,6 +11309,33 @@ dependencies = [ "sha-1", ] +[[package]] +name = "solis" +version = "0.7.0-alpha.1" +dependencies = [ + "alloy-primitives", + "anyhow", + "assert_matches", + "cfg-if", + "clap", + "clap_complete", + "common", + "console", + "dojo-metrics", + "katana-core", + "katana-executor", + "katana-primitives", + "katana-rpc", + "katana-rpc-api", + "serde_json", + "shellexpand", + "starknet_api", + "tokio", + "tracing", + "tracing-subscriber", + "url", +] + [[package]] name = "sozo" version = "0.7.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 7a285254d1..78030f2f55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "bin/dojo-language-server", "bin/katana", + "bin/solis", "bin/saya", "bin/sozo", "bin/torii", diff --git a/bin/solis/Cargo.toml b/bin/solis/Cargo.toml new file mode 100644 index 0000000000..fb009ea6aa --- /dev/null +++ b/bin/solis/Cargo.toml @@ -0,0 +1,43 @@ +[package] +description = "Modified katana to match ArkProject requirement." +edition.workspace = true +license-file.workspace = true +name = "solis" +repository.workspace = true +version.workspace = true + +[dependencies] +alloy-primitives.workspace = true +anyhow.workspace = true +cfg-if = "1.0.0" +clap.workspace = true +clap_complete.workspace = true +common.workspace = true +console.workspace = true +dojo-metrics.workspace = true +katana-core.workspace = true +katana-executor.workspace = true +katana-primitives.workspace = true +katana-rpc-api.workspace = true +katana-rpc.workspace = true +serde_json.workspace = true +shellexpand = "3.1.0" +starknet_api.workspace = true +tokio.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true +url.workspace = true + +[dev-dependencies] +assert_matches = "1.5.0" + +[features] +default = [ "blockifier", "jemalloc", "messaging" ] + +blockifier = [ "katana-executor/blockifier" ] +# Disable until SIR support Cairo 2.6.3 +#sir = [ "katana-executor/sir" ] + +jemalloc = [ "dojo-metrics/jemalloc" ] +messaging = [ "katana-core/messaging" ] +starknet-messaging = [ "katana-core/starknet-messaging", "messaging" ] diff --git a/bin/solis/src/args.rs b/bin/solis/src/args.rs new file mode 100644 index 0000000000..f603c87b41 --- /dev/null +++ b/bin/solis/src/args.rs @@ -0,0 +1,343 @@ +//! Katana binary executable. +//! +//! ## Feature Flags +//! +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. + +use std::net::SocketAddr; +use std::path::PathBuf; + +use alloy_primitives::U256; +use clap::{Args, Parser, Subcommand}; +use clap_complete::Shell; +use common::parse::parse_socket_address; +use katana_core::backend::config::{Environment, StarknetConfig}; +use katana_core::constants::{ + DEFAULT_ETH_L1_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_SEQUENCER_ADDRESS, + DEFAULT_STRK_L1_GAS_PRICE, DEFAULT_VALIDATE_MAX_STEPS, +}; +use katana_core::sequencer::SequencerConfig; +use katana_primitives::block::GasPrices; +use katana_primitives::chain::ChainId; +use katana_primitives::genesis::allocation::DevAllocationsGenerator; +use katana_primitives::genesis::constant::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; +use katana_primitives::genesis::Genesis; +use katana_rpc::config::ServerConfig; +use katana_rpc_api::ApiKind; +use tracing::Subscriber; +use tracing_subscriber::{fmt, EnvFilter}; +use url::Url; + +use crate::utils::{parse_genesis, parse_seed}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct KatanaArgs { + #[arg(long)] + #[arg(help = "Don't print anything on startup.")] + pub silent: bool, + + #[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, + + #[arg(short, long)] + #[arg(value_name = "MILLISECONDS")] + #[arg(help = "Block time in milliseconds for interval mining.")] + pub block_time: Option, + + #[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)] + #[arg(value_name = "URL")] + #[arg(help = "The Starknet RPC provider to fork the network from.")] + pub rpc_url: Option, + + #[arg(long)] + pub dev: bool, + + #[arg(long)] + #[arg(help = "Output logs in JSON format.")] + pub json_log: bool, + + /// Enable Prometheus metrics. + /// + /// 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, + + #[arg(long)] + #[arg(requires = "rpc_url")] + #[arg(value_name = "BLOCK_NUMBER")] + #[arg(help = "Fork the network at a specific block.")] + pub fork_block_number: Option, + + #[cfg(feature = "messaging")] + #[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 server: ServerOptions, + + #[command(flatten)] + #[command(next_help_heading = "Starknet options")] + pub starknet: StarknetOptions, + + #[command(subcommand)] + pub command: Option, +} + +#[derive(Debug, Subcommand)] +pub enum Commands { + #[command(about = "Generate shell completion file for specified shell")] + Completions { shell: Shell }, +} + +#[derive(Debug, Args, Clone)] +pub struct ServerOptions { + #[arg(short, long)] + #[arg(default_value = "7777")] + #[arg(help = "Port number to listen on.")] + pub port: u16, + + #[arg(long)] + #[arg(help = "The IP address the server will listen on.")] + pub host: Option, + + #[arg(long)] + #[arg(default_value = "100")] + #[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)] +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 = "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(["rpc_url", "seed", "total_accounts"]))] + pub genesis: Option, +} + +#[derive(Debug, Args, Clone)] +pub struct EnvironmentOptions { + #[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(default_value = "SOLIS")] + #[arg(value_parser = ChainId::parse)] + pub chain_id: ChainId, + + #[arg(long)] + #[arg(help = "The maximum number of steps available for the account validation logic.")] + #[arg(default_value_t = DEFAULT_VALIDATE_MAX_STEPS)] + pub validate_max_steps: u32, + + #[arg(long)] + #[arg(help = "The maximum number of steps available for the account execution logic.")] + #[arg(default_value_t = DEFAULT_INVOKE_MAX_STEPS)] + pub invoke_max_steps: u32, + + #[arg(long = "eth-gas-price")] + #[arg(conflicts_with = "genesis")] + #[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(long = "strk-gas-price")] + #[arg(conflicts_with = "genesis")] + #[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, +} + +impl KatanaArgs { + pub fn init_logging(&self) -> Result<(), Box> { + const DEFAULT_LOG_FILTER: &str = "info,executor=trace,forked_backend=trace,server=debug,\ + katana_core=trace,blockifier=off,jsonrpsee_server=off,\ + hyper=off,messaging=debug,node=error"; + + let builder = fmt::Subscriber::builder().with_env_filter( + EnvFilter::try_from_default_env().or(EnvFilter::try_new(DEFAULT_LOG_FILTER))?, + ); + + let subscriber: Box = if self.json_log { + Box::new(builder.json().finish()) + } else { + Box::new(builder.finish()) + }; + + Ok(tracing::subscriber::set_global_default(subscriber)?) + } + + pub fn sequencer_config(&self) -> SequencerConfig { + SequencerConfig { + block_time: self.block_time, + no_mining: self.no_mining, + #[cfg(feature = "messaging")] + messaging: self.messaging.clone(), + } + } + + pub fn server_config(&self) -> ServerConfig { + let mut apis = vec![ApiKind::Starknet, ApiKind::Katana, ApiKind::Torii, ApiKind::Saya, ApiKind::Solis]; + // only enable `katana` API in dev mode + if self.dev { + apis.push(ApiKind::Dev); + } + + ServerConfig { + apis, + port: self.server.port, + host: self.server.host.clone().unwrap_or("0.0.0.0".into()), + max_connections: self.server.max_connections, + allowed_origins: self.server.allowed_origins.clone(), + rpc_user: "user".into(), + rpc_password: "password".into(), + } + } + + pub fn starknet_config(&self) -> StarknetConfig { + let genesis = match self.starknet.genesis.clone() { + Some(genesis) => genesis, + None => { + let gas_prices = GasPrices { + eth: self.starknet.environment.l1_eth_gas_price, + strk: self.starknet.environment.l1_strk_gas_price, + }; + + let accounts = DevAllocationsGenerator::new(self.starknet.total_accounts) + .with_seed(parse_seed(&self.starknet.seed)) + .with_balance(U256::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE)) + .generate(); + + let mut genesis = Genesis { + gas_prices, + sequencer_address: *DEFAULT_SEQUENCER_ADDRESS, + ..Default::default() + }; + + genesis.extend_allocations(accounts.into_iter().map(|(k, v)| (k, v.into()))); + genesis + } + }; + + StarknetConfig { + disable_fee: self.starknet.disable_fee, + disable_validate: self.starknet.disable_validate, + fork_rpc_url: self.rpc_url.clone(), + fork_block_number: self.fork_block_number, + env: Environment { + chain_id: self.starknet.environment.chain_id, + invoke_max_steps: self.starknet.environment.invoke_max_steps, + validate_max_steps: self.starknet.environment.validate_max_steps, + }, + db_dir: self.db_dir.clone(), + genesis, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_starknet_config_default() { + let args = KatanaArgs::parse_from(["katana"]); + let config = args.starknet_config(); + + assert!(!config.disable_fee); + assert!(!config.disable_validate); + assert_eq!(config.fork_rpc_url, None); + assert_eq!(config.fork_block_number, None); + assert_eq!(config.env.chain_id, ChainId::parse("KATANA").unwrap()); + assert_eq!(config.env.invoke_max_steps, DEFAULT_INVOKE_MAX_STEPS); + assert_eq!(config.env.validate_max_steps, DEFAULT_VALIDATE_MAX_STEPS); + assert_eq!(config.db_dir, None); + assert_eq!(config.genesis.gas_prices.eth, DEFAULT_ETH_L1_GAS_PRICE); + assert_eq!(config.genesis.gas_prices.strk, DEFAULT_STRK_L1_GAS_PRICE); + assert_eq!(config.genesis.sequencer_address, *DEFAULT_SEQUENCER_ADDRESS); + } + + #[test] + fn test_starknet_config_custom() { + let args = KatanaArgs::parse_from([ + "katana", + "--disable-fee", + "--disable-validate", + "--chain-id", + "SN_GOERLI", + "--invoke-max-steps", + "200", + "--validate-max-steps", + "100", + "--db-dir", + "/path/to/db", + "--eth-gas-price", + "10", + "--strk-gas-price", + "20", + ]); + let config = args.starknet_config(); + + assert!(config.disable_fee); + assert!(config.disable_validate); + assert_eq!(config.env.chain_id, ChainId::GOERLI); + assert_eq!(config.env.invoke_max_steps, 200); + assert_eq!(config.env.validate_max_steps, 100); + assert_eq!(config.db_dir, Some(PathBuf::from("/path/to/db"))); + assert_eq!(config.genesis.gas_prices.eth, 10); + assert_eq!(config.genesis.gas_prices.strk, 20); + } +} diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs new file mode 100644 index 0000000000..f0d8668f92 --- /dev/null +++ b/bin/solis/src/main.rs @@ -0,0 +1,232 @@ +use std::io; +use std::net::SocketAddr; +use std::sync::Arc; + +use clap::{CommandFactory, Parser}; +use clap_complete::{generate, Shell}; +use console::Style; +use dojo_metrics::{metrics_process, prometheus_exporter}; +use katana_core::constants::MAX_RECURSION_DEPTH; +use katana_core::env::get_default_vm_resource_fee_cost; +use katana_core::sequencer::KatanaSequencer; +use katana_executor::SimulationFlag; +use katana_primitives::class::ClassHash; +use katana_primitives::contract::ContractAddress; +use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; +use katana_primitives::genesis::allocation::GenesisAccountAlloc; +use katana_primitives::genesis::Genesis; +use katana_rpc::{spawn, NodeHandle}; +use tokio::signal::ctrl_c; +use tracing::info; + +mod args; +mod utils; + +use args::Commands::Completions; +use args::KatanaArgs; + +pub(crate) const LOG_TARGET: &str = "katana::cli"; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = KatanaArgs::parse(); + args.init_logging()?; + + if let Some(command) = args.command { + match command { + Completions { shell } => { + print_completion(shell); + return Ok(()); + } + } + } + + let server_config = args.server_config(); + let sequencer_config = args.sequencer_config(); + let starknet_config = args.starknet_config(); + + let cfg_env = CfgEnv { + chain_id: starknet_config.env.chain_id, + vm_resource_fee_cost: get_default_vm_resource_fee_cost(), + invoke_tx_max_n_steps: starknet_config.env.invoke_max_steps, + validate_max_n_steps: starknet_config.env.validate_max_steps, + max_recursion_depth: MAX_RECURSION_DEPTH, + fee_token_addresses: FeeTokenAddressses { + eth: starknet_config.genesis.fee_token.address, + strk: Default::default(), + }, + }; + + let simulation_flags = SimulationFlag { + skip_validate: starknet_config.disable_validate, + skip_fee_transfer: starknet_config.disable_fee, + ..Default::default() + }; + + // TODO: Uncomment this once we enable the 'sir' feature again because it's not compatible with + // our current Cairo version (2.6.3). cfg_if::cfg_if! { + // if #[cfg(all(feature = "blockifier", feature = "sir"))] { + // compile_error!("Cannot enable both `blockifier` and `sir` features at the same + // time"); } else if #[cfg(feature = "blockifier")] { + // use katana_executor::implementation::blockifier::BlockifierFactory; + // let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); + // } else if #[cfg(feature = "sir")] { + // use katana_executor::implementation::sir::NativeExecutorFactory; + // let executor_factory = NativeExecutorFactory::new(cfg_env, simulation_flags); + // } else { + // compile_error!("At least one of the following features must be enabled: blockifier, + // sir"); } + // } + + use katana_executor::implementation::blockifier::BlockifierFactory; + let executor_factory = BlockifierFactory::new(cfg_env, simulation_flags); + + if let Some(listen_addr) = args.metrics { + let prometheus_handle = prometheus_exporter::install_recorder("katana")?; + + info!(target: LOG_TARGET, addr = %listen_addr, "Starting metrics endpoint."); + prometheus_exporter::serve( + listen_addr, + prometheus_handle, + metrics_process::Collector::default(), + ) + .await?; + } + + let sequencer = Arc::new( + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, None).await?, + ); + let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; + + if !args.silent { + let genesis = &sequencer.backend().config.genesis; + print_intro(&args, genesis, addr); + } + + // Wait until Ctrl + C is pressed, then shutdown + ctrl_c().await?; + handle.stop()?; + + Ok(()) +} + +fn print_completion(shell: Shell) { + let mut command = KatanaArgs::command(); + let name = command.get_name().to_string(); + generate(shell, &mut command, name, &mut io::stdout()); +} + +fn print_intro(args: &KatanaArgs, genesis: &Genesis, address: SocketAddr) { + let mut accounts = genesis.accounts().peekable(); + let account_class_hash = accounts.peek().map(|e| e.1.class_hash()); + let seed = &args.starknet.seed; + + if args.json_log { + info!( + target: LOG_TARGET, + "{}", + serde_json::json!({ + "accounts": accounts.map(|a| serde_json::json!(a)).collect::>(), + "seed": format!("{}", seed), + "address": format!("{address}"), + }) + ) + } else { + println!( + "{}", + Style::new().red().apply_to( + r" + + +██╗ ██╗ █████╗ ████████╗ █████╗ ███╗ ██╗ █████╗ +██║ ██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗ ██║██╔══██╗ +█████╔╝ ███████║ ██║ ███████║██╔██╗ ██║███████║ +██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║ +██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ +" + ) + ); + + print_genesis_contracts(genesis, account_class_hash); + print_genesis_accounts(accounts); + + println!( + r" + +ACCOUNTS SEED +============= +{seed} + " + ); + + let addr = format!( + "🚀 JSON-RPC server started: {}", + Style::new().red().apply_to(format!("http://{address}")) + ); + + println!("\n{addr}\n\n",); + } +} + +fn print_genesis_contracts(genesis: &Genesis, account_class_hash: Option) { + println!( + r" +PREDEPLOYED CONTRACTS +================== + +| Contract | Fee Token +| Address | {} +| Class Hash | {:#064x}", + genesis.fee_token.address, genesis.fee_token.class_hash, + ); + + if let Some(ref udc) = genesis.universal_deployer { + println!( + r" +| Contract | Universal Deployer +| Address | {} +| Class Hash | {:#064x}", + udc.address, udc.class_hash + ) + } + + if let Some(hash) = account_class_hash { + println!( + r" +| Contract | Account Contract +| Class Hash | {hash:#064x}" + ) + } +} + +fn print_genesis_accounts<'a, Accounts>(accounts: Accounts) +where + Accounts: Iterator, +{ + println!( + r" + +PREFUNDED ACCOUNTS +==================" + ); + + for (addr, account) in accounts { + if let Some(pk) = account.private_key() { + println!( + r" +| Account address | {addr} +| Private key | {pk:#x} +| Public key | {:#x}", + account.public_key() + ) + } else { + println!( + r" +| Account address | {addr} +| Public key | {:#x}", + account.public_key() + ) + } + } +} diff --git a/bin/solis/src/utils.rs b/bin/solis/src/utils.rs new file mode 100644 index 0000000000..f05f5a7634 --- /dev/null +++ b/bin/solis/src/utils.rs @@ -0,0 +1,34 @@ +use std::path::PathBuf; + +use katana_primitives::genesis::json::GenesisJson; +use katana_primitives::genesis::Genesis; + +pub fn parse_seed(seed: &str) -> [u8; 32] { + let seed = seed.as_bytes(); + + if seed.len() >= 32 { + unsafe { *(seed[..32].as_ptr() as *const [u8; 32]) } + } else { + let mut actual_seed = [0u8; 32]; + seed.iter().enumerate().for_each(|(i, b)| actual_seed[i] = *b); + actual_seed + } +} + +/// Used as clap value parser for [Genesis]. +pub fn parse_genesis(value: &str) -> Result { + let path = PathBuf::from(shellexpand::full(value)?.into_owned()); + let genesis = Genesis::try_from(GenesisJson::load(path)?)?; + Ok(genesis) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_genesis_file() { + let path = "./tests/test-data/genesis.json"; + parse_genesis(path).unwrap(); + } +} diff --git a/bin/solis/tests/test-data/genesis.json b/bin/solis/tests/test-data/genesis.json new file mode 100644 index 0000000000..51436f4832 --- /dev/null +++ b/bin/solis/tests/test-data/genesis.json @@ -0,0 +1,42 @@ +{ + "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": [ + ] +} diff --git a/messaging.local.json b/messaging.local.json new file mode 100644 index 0000000000..fd318e48a9 --- /dev/null +++ b/messaging.local.json @@ -0,0 +1,9 @@ +{ + "chain": "starknet", + "rpc_url": "http://0.0.0.0:5050", + "contract_address": "0x5776e8ba9acd1b67985b005a4fa9d2b03216e0d2df18d8131f2c42330090b37", + "sender_address": "0x2d71e9c974539bb3ffb4b115e66a23d0f62a641ea66c4016e903454c8753bbc", + "private_key": "0x33003003001800009900180300d206308b0070db00121318d17b5e6262150b", + "interval": 2, + "from_block": 0 +} \ No newline at end of file From 6c95e0ef9011887bf7ebeb831eeea3d47e86f81e Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 15 May 2024 19:08:02 +0200 Subject: [PATCH 07/31] feat(solis): remove feature flag & fix hooker impl errors --- .../katana/core/src/service/messaging/mod.rs | 17 +-- .../core/src/service/messaging/service.rs | 14 +-- .../core/src/service/messaging/starknet.rs | 104 +++++++++--------- .../katana/rpc/rpc-types/src/error/solis.rs | 4 +- 4 files changed, 65 insertions(+), 74 deletions(-) diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index a21b456552..259cb483f1 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -38,7 +38,6 @@ use tokio::sync::RwLock as AsyncRwLock; mod ethereum; mod service; -#[cfg(feature = "starknet-messaging")] mod starknet; use std::path::Path; @@ -54,12 +53,10 @@ use serde::Deserialize; use tracing::{error, info}; pub use self::service::{MessagingOutcome, MessagingService}; -#[cfg(feature = "starknet-messaging")] use self::starknet::StarknetMessaging; pub(crate) const LOG_TARGET: &str = "messaging"; pub(crate) const CONFIG_CHAIN_ETHEREUM: &str = "ethereum"; -#[cfg(feature = "starknet-messaging")] pub(crate) const CONFIG_CHAIN_STARKNET: &str = "starknet"; type MessengerResult = Result; @@ -166,16 +163,15 @@ pub trait Messenger { ) -> MessengerResult>; } -pub enum MessengerMode { +pub enum MessengerMode { Ethereum(EthereumMessaging), - #[cfg(feature = "starknet-messaging")] - Starknet(StarknetMessaging), + Starknet(StarknetMessaging), } -impl MessengerMode { - pub async fn from_config( +impl MessengerMode { + pub async fn from_config( config: MessagingConfig, - hooker: Arc>>, + hooker: Arc + Send + Sync>>, ) -> MessengerResult { match config.chain.as_str() { CONFIG_CHAIN_ETHEREUM => match EthereumMessaging::new(config).await { @@ -189,8 +185,7 @@ impl MessengerMode { } }, - #[cfg(feature = "starknet-messaging")] - CONFIG_CHAIN_STARKNET => match StarknetMessaging::new(config).await { + CONFIG_CHAIN_STARKNET => match StarknetMessaging::new(config, hooker).await { Ok(m_sn) => { info!(target: LOG_TARGET, "Messaging enabled [Starknet]."); Ok(MessengerMode::Starknet(m_sn)) diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index d221b88798..52da9c474a 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -29,7 +29,7 @@ pub struct MessagingService { backend: Arc>, pool: Arc, /// The messenger mode the service is running in. - messenger: Arc, + messenger: Arc>, /// The block number of the settlement chain from which messages will be gathered. gather_from_block: u64, /// The message gathering future. @@ -56,7 +56,7 @@ impl MessagingService { Err(_) => { panic!( "Messaging could not be initialized.\nVerify that the messaging target node \ - (anvil or other katana) is running.\n", + (anvil or other katana) is running.\n" ) } }; @@ -74,12 +74,12 @@ impl MessagingService { } async fn gather_messages( - messenger: Arc, + messenger: Arc>, pool: Arc, backend: Arc>, from_block: u64, ) -> MessengerResult<(u64, usize)> { - // 200 avoids any possible rejection from RPC with possibly lot's of messages. + // 200 avoids any possible rejection from RPC with possibly lots of messages. // TODO: May this be configurable? let max_block = 200; @@ -98,7 +98,6 @@ impl MessagingService { Ok((block_num, txs_count)) } - #[cfg(feature = "starknet-messaging")] MessengerMode::Starknet(inner) => { let (block_num, txs) = inner.gather_messages(from_block, max_block, backend.chain_id).await?; @@ -118,7 +117,7 @@ impl MessagingService { async fn send_messages( block_num: u64, backend: Arc>, - messenger: Arc, + messenger: Arc>, ) -> MessengerResult> { let Some(messages) = ReceiptProvider::receipts_by_block( backend.blockchain.provider(), @@ -141,7 +140,6 @@ impl MessagingService { Ok(Some((block_num, hashes.len()))) } - #[cfg(feature = "starknet-messaging")] MessengerMode::Starknet(inner) => { let hashes = inner.send_messages(&messages).await.map(|hashes| { hashes.iter().map(|h| format!("{h:#x}")).collect::>() @@ -259,7 +257,6 @@ fn interval_from_seconds(secs: u64) -> Interval { fn trace_msg_to_l1_sent(messages: &[MessageToL1], hashes: &[String]) { assert_eq!(messages.len(), hashes.len()); - #[cfg(feature = "starknet-messaging")] let hash_exec_str = format!("{:#064x}", super::starknet::HASH_EXEC); for (i, m) in messages.iter().enumerate() { @@ -267,7 +264,6 @@ fn trace_msg_to_l1_sent(messages: &[MessageToL1], hashes: &[String]) { let hash = &hashes[i]; - #[cfg(feature = "starknet-messaging")] if hash == &hash_exec_str { let to_address = &payload_str[0]; let selector = &payload_str[1]; diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index ec0a6871e6..80120645b0 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -1,9 +1,4 @@ use crate::hooker::KatanaHooker; -use tokio::sync::RwLock as AsyncRwLock; - -use std::collections::HashMap; -use std::sync::Arc; - use anyhow::Result; use async_trait::async_trait; use katana_primitives::chain::ChainId; @@ -17,6 +12,9 @@ use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{AnyProvider, JsonRpcClient, Provider}; use starknet::signers::{LocalWallet, SigningKey}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock as AsyncRwLock; use tracing::{debug, error, trace, warn}; use url::Url; @@ -32,7 +30,7 @@ const EXE_MAGIC: FieldElement = felt!("0x455845"); pub const HASH_EXEC: FieldElement = felt!("0xee"); -pub struct StarknetMessaging { +pub struct StarknetMessaging { chain_id: FieldElement, provider: AnyProvider, wallet: LocalWallet, @@ -41,11 +39,11 @@ pub struct StarknetMessaging { hooker: Arc + Send + Sync>>, } -impl StarknetMessaging { +impl StarknetMessaging { pub async fn new( config: MessagingConfig, hooker: Arc + Send + Sync>>, - ) -> Result { + ) -> Result> { let provider = AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new( Url::parse(&config.rpc_url)?, ))); @@ -131,12 +129,12 @@ impl StarknetMessaging { // TODO: we need to have maximum fee configurable. let execution = account.execute(calls).fee_estimate_multiplier(10f64); let estimated_fee = (execution.estimate_fee().await?.overall_fee) * 10u64.into(); - let tx = execution.max_fee(estimated_fee).send().await?; + let execution_with_fee = execution.max_fee(estimated_fee); // We need logs to debug the starknet transactions. - match execution.send().await { + match execution_with_fee.send().await { Ok(tx) => { - info!("Transaction successful: {:?}", tx); + tracing::info!("Transaction successful: {:?}", tx); println!("tx: {:?}", tx); println!("tx_hash: {:?}", tx.transaction_hash); Ok(tx.transaction_hash) @@ -179,7 +177,7 @@ impl StarknetMessaging { } #[async_trait] -impl Messenger for StarknetMessaging { +impl Messenger for StarknetMessaging { type MessageHash = FieldElement; type MessageTransaction = L1HandlerTx; @@ -215,36 +213,36 @@ impl Messenger for StarknetMessaging { let mut l1_handler_txs: Vec = vec![]; - self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) + let block_to_events = self + .fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) .await - .map_err(|_| Error::SendError) - .unwrap() - .iter() - .for_each(|(block_number, block_events)| { - debug!( - target: LOG_TARGET, - block_number = %block_number, - events_count = %block_events.len(), - "Converting events of block into L1HandlerTx." - ); - - block_events.iter().for_each(|e| { - if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { - if let Ok((from, to, selector)) = info_from_event(e) { - let is_message_accepted = self - .hooker - .read() - .await - .verify_message_to_appchain(from, to, selector) - .await; - - if is_message_accepted { - l1_handler_txs.push(tx) - } + .map_err(|_| Error::SendError)?; + + for (block_number, block_events) in block_to_events.iter() { + debug!( + target: LOG_TARGET, + block_number = %block_number, + events_count = %block_events.len(), + "Converting events of block into L1HandlerTx." + ); + + for event in block_events.iter() { + if let Ok(tx) = l1_handler_tx_from_event(event, chain_id) { + if let Ok((from, to, selector)) = info_from_event(event) { + let hooker = Arc::clone(&self.hooker); + let is_message_accepted = hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; + + if is_message_accepted { + l1_handler_txs.push(tx); } } - }) - }); + } + } + } Ok((to_block, l1_handler_txs)) } @@ -252,7 +250,7 @@ impl Messenger for StarknetMessaging { async fn send_messages( &self, messages: &[MessageToL1], - ) -> MessengerResult> { + ) -> MessengerResult::MessageHash>> { if messages.is_empty() { return Ok(vec![]); } @@ -263,19 +261,23 @@ impl Messenger for StarknetMessaging { if !self.hooker.read().await.verify_tx_for_starknet(call.clone()).await { continue; } - if !calls.is_empty() { - match self.send_invoke_tx(calls).await { - Ok(tx_hash) => { - trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); - } - Err(e) => { - error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); - self.hooker.read().await.on_starknet_tx_failed(call.clone()).await; - return Err(Error::SendError); + } + + if !calls.is_empty() { + match self.send_invoke_tx(calls.clone()).await { + Ok(tx_hash) => { + trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); + } + Err(e) => { + error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); + for call in calls { + self.hooker.read().await.on_starknet_tx_failed(call).await; } - }; - } + return Err(Error::SendError); + } + }; } + self.send_hashes(hashes.clone()).await?; Ok(hashes) diff --git a/crates/katana/rpc/rpc-types/src/error/solis.rs b/crates/katana/rpc/rpc-types/src/error/solis.rs index bff847171f..fb49ee0210 100644 --- a/crates/katana/rpc/rpc-types/src/error/solis.rs +++ b/crates/katana/rpc/rpc-types/src/error/solis.rs @@ -1,8 +1,6 @@ -use futures::channel::mpsc::Receiver; -use jsonrpsee::core::{async_trait, Error as RpcError}; +use jsonrpsee::core::Error as RpcError; use jsonrpsee::types::error::CallError; use jsonrpsee::types::ErrorObject; -use std::sync::Arc; use thiserror::Error; #[derive(Debug, Error)] From 1648411f0c5237ef7c370058f89abad8f9a31129 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 15 May 2024 19:23:16 +0200 Subject: [PATCH 08/31] fix(hooker): update hooker remove useless new & fix typing --- crates/katana/core/src/hooker.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/katana/core/src/hooker.rs b/crates/katana/core/src/hooker.rs index 316f9b0b39..25268cad14 100644 --- a/crates/katana/core/src/hooker.rs +++ b/crates/katana/core/src/hooker.rs @@ -1,13 +1,12 @@ //! This module contains a hooker trait, that is added to katana in order to //! allow external code to react at some precise moment of katana processing. -use std::sync::Arc; +use crate::sequencer::KatanaSequencer; use async_trait::async_trait; use katana_executor::ExecutorFactory; use starknet::accounts::Call; use starknet::core::types::{BroadcastedInvokeTransaction, FieldElement}; - -use crate::sequencer::KatanaSequencer; +use std::sync::Arc; #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Copy, PartialEq, Eq)] pub struct HookerAddresses { @@ -16,12 +15,12 @@ pub struct HookerAddresses { } #[async_trait] -pub trait KatanaHooker { +pub trait KatanaHooker { /// Sets a reference to the underlying sequencer. fn set_sequencer(&mut self, sequencer: Arc>); /// Runs code right before a message from the L1 is converted - /// into a `L1HandlerTransaction`. This hook is usefull to + /// into a `L1HandlerTransaction`. This hook is useful to /// apply conditions on the message being captured. /// /// # Arguments @@ -75,7 +74,6 @@ pub trait KatanaHooker { fn set_addresses(&mut self, addresses: HookerAddresses); } -#[derive()] pub struct DefaultKatanaHooker { sequencer: Option>>, addresses: Option, @@ -83,13 +81,7 @@ pub struct DefaultKatanaHooker { impl DefaultKatanaHooker { pub fn new() -> Self { - DefaultKatanaHooker { - sequencer: None, - addresses: None, - } - } - pub fn set_sequencer(&mut self, sequencer: Arc>) { - self.sequencer = Some(sequencer); + DefaultKatanaHooker { sequencer: None, addresses: None } } } @@ -121,7 +113,7 @@ impl KatanaHooker for DefaultKa async fn on_starknet_tx_failed(&self, _call: Call) { // Log the failure or handle it according to your needs. No-op by default. - print!("Starknet transaction failed: {:?}", _call); + tracing::error!("Starknet transaction failed: {:?}", _call); } fn set_addresses(&mut self, addresses: HookerAddresses) { From f882722757bc7cf2d929b7ec71db7fd69328ba54 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 15 May 2024 22:28:49 +0200 Subject: [PATCH 09/31] feat(solis): add hooker in solis bin & logs to starknet messaging --- bin/solis/src/main.rs | 11 ++++- crates/katana/core/src/sequencer.rs | 6 ++- .../core/src/service/messaging/starknet.rs | 40 +++++++++++++------ messaging.local.json | 2 +- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs index f0d8668f92..754831d379 100644 --- a/bin/solis/src/main.rs +++ b/bin/solis/src/main.rs @@ -8,6 +8,7 @@ use console::Style; use dojo_metrics::{metrics_process, prometheus_exporter}; use katana_core::constants::MAX_RECURSION_DEPTH; use katana_core::env::get_default_vm_resource_fee_cost; +use katana_core::hooker::{DefaultKatanaHooker, KatanaHooker}; use katana_core::sequencer::KatanaSequencer; use katana_executor::SimulationFlag; use katana_primitives::class::ClassHash; @@ -17,6 +18,7 @@ use katana_primitives::genesis::allocation::GenesisAccountAlloc; use katana_primitives::genesis::Genesis; use katana_rpc::{spawn, NodeHandle}; use tokio::signal::ctrl_c; +use tokio::sync::RwLock as AsyncRwLock; use tracing::info; mod args; @@ -93,8 +95,15 @@ async fn main() -> Result<(), Box> { .await?; } + // Create a default hooker instance + // Create a default hooker instance + // Create a default hooker instance + let hooker: Arc + Send + Sync>> = + Arc::new(AsyncRwLock::new(DefaultKatanaHooker::new())); + let sequencer = Arc::new( - KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, None).await?, + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, Some(hooker)) + .await?, ); let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 76bfa23bf0..cf1815eaed 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -27,6 +27,7 @@ use katana_provider::traits::transaction::{ ReceiptProvider, TransactionProvider, TransactionsProviderExt, }; use starknet::core::types::{BlockTag, EmittedEvent, EventsPage}; +use tracing::error; use crate::backend::config::StarknetConfig; use crate::backend::contract::StarknetContract; @@ -92,7 +93,10 @@ impl KatanaSequencer { ) .await .ok(), - None => None, // Handle the case where no hooker is provided + None => { + error!("Messaging service is enabled but no hooker is provided. Messaging service will not be started."); + None + } } } else { None diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 80120645b0..fc7b864975 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -15,7 +15,7 @@ use starknet::signers::{LocalWallet, SigningKey}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock as AsyncRwLock; -use tracing::{debug, error, trace, warn}; +use tracing::{debug, error, info, trace, warn}; use url::Url; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; @@ -56,6 +56,8 @@ impl StarknetMessaging { let sender_account_address = FieldElement::from_hex_be(&config.sender_address)?; let messaging_contract_address = FieldElement::from_hex_be(&config.contract_address)?; + info!(target: LOG_TARGET, "StarknetMessaging instance created."); + Ok(StarknetMessaging { wallet, provider, @@ -131,16 +133,17 @@ impl StarknetMessaging { let estimated_fee = (execution.estimate_fee().await?.overall_fee) * 10u64.into(); let execution_with_fee = execution.max_fee(estimated_fee); - // We need logs to debug the starknet transactions. + info!(target: LOG_TARGET, "Sending invoke transaction."); + match execution_with_fee.send().await { Ok(tx) => { - tracing::info!("Transaction successful: {:?}", tx); + info!(target: LOG_TARGET, "Transaction successful: {:?}", tx); println!("tx: {:?}", tx); println!("tx_hash: {:?}", tx.transaction_hash); Ok(tx.transaction_hash) } Err(e) => { - error!("Error sending transaction: {:?}", e); + error!(target: LOG_TARGET, "Error sending transaction: {:?}", e); Err(e.into()) } } @@ -151,6 +154,7 @@ impl StarknetMessaging { hashes.retain(|&x| x != HASH_EXEC); if hashes.is_empty() { + info!(target: LOG_TARGET, "No hashes to send."); return Ok(FieldElement::ZERO); } @@ -163,6 +167,8 @@ impl StarknetMessaging { calldata, }; + info!(target: LOG_TARGET, "Sending hashes to Starknet."); + match self.send_invoke_tx(vec![call]).await { Ok(tx_hash) => { trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Hashes sending transaction."); @@ -189,11 +195,10 @@ impl Messenger for StarknetM ) -> MessengerResult<(u64, Vec)> { let chain_latest_block: u64 = match self.provider.block_number().await { Ok(n) => n, - Err(_) => { + Err(e) => { warn!( target: LOG_TARGET, - "Couldn't fetch settlement chain last block number. \nSkipped, retry at the \ - next tick." + "Couldn't fetch settlement chain last block number. Skipped, retry at the next tick. Error: {:?}", e ); return Err(Error::SendError); } @@ -213,10 +218,15 @@ impl Messenger for StarknetM let mut l1_handler_txs: Vec = vec![]; + info!(target: LOG_TARGET, "Gathering messages from block {} to block {}", from_block, to_block); + let block_to_events = self .fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) .await - .map_err(|_| Error::SendError)?; + .map_err(|e| { + error!(target: LOG_TARGET, "Error fetching events: {:?}", e); + Error::SendError + })?; for (block_number, block_events) in block_to_events.iter() { debug!( @@ -252,6 +262,7 @@ impl Messenger for StarknetM messages: &[MessageToL1], ) -> MessengerResult::MessageHash>> { if messages.is_empty() { + info!(target: LOG_TARGET, "No messages to send."); return Ok(vec![]); } @@ -259,11 +270,13 @@ impl Messenger for StarknetM for call in &calls { if !self.hooker.read().await.verify_tx_for_starknet(call.clone()).await { + warn!(target: LOG_TARGET, "Call verification failed for call: {:?}", call); continue; } } if !calls.is_empty() { + info!(target: LOG_TARGET, "Sending invoke transactions for calls."); match self.send_invoke_tx(calls.clone()).await { Ok(tx_hash) => { trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); @@ -305,6 +318,7 @@ fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec MessengerResult<(Vec MessengerResult<(Vec Result Result<(FieldElement, FieldElement, error!(target: LOG_TARGET, "Event MessageSentToAppchain is not well formatted"); } - // See contrat appchain_messaging.cairo for MessageSentToAppchain event. + // See contract appchain_messaging.cairo for MessageSentToAppchain event. let from_address = event.keys[2]; let to_address = event.keys[3]; let entry_point_selector = event.data[0]; diff --git a/messaging.local.json b/messaging.local.json index fd318e48a9..1707e867d9 100644 --- a/messaging.local.json +++ b/messaging.local.json @@ -1,7 +1,7 @@ { "chain": "starknet", "rpc_url": "http://0.0.0.0:5050", - "contract_address": "0x5776e8ba9acd1b67985b005a4fa9d2b03216e0d2df18d8131f2c42330090b37", + "contract_address": "0x3757e03517ea83d7ae5714b0bfea853114aeb4d5186ec8bf7ec73ac09033f18", "sender_address": "0x2d71e9c974539bb3ffb4b115e66a23d0f62a641ea66c4016e903454c8753bbc", "private_key": "0x33003003001800009900180300d206308b0070db00121318d17b5e6262150b", "interval": 2, From c41222bab2f4dd724bfe07cf479e8869a7af7866 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 15 May 2024 22:51:21 +0200 Subject: [PATCH 10/31] fix(solis): update header --- bin/solis/src/main.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs index 754831d379..07c48d1d14 100644 --- a/bin/solis/src/main.rs +++ b/bin/solis/src/main.rs @@ -143,16 +143,22 @@ fn print_intro(args: &KatanaArgs, genesis: &Genesis, address: SocketAddr) { } else { println!( "{}", - Style::new().red().apply_to( + Style::new().blue().apply_to( r" - -██╗ ██╗ █████╗ ████████╗ █████╗ ███╗ ██╗ █████╗ -██║ ██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗ ██║██╔══██╗ -█████╔╝ ███████║ ██║ ███████║██╔██╗ ██║███████║ -██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║ -██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║ -╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ +░█████╗░██████╗░██╗░░██╗██████╗░██████╗░░█████╗░░░░░░██╗███████╗░█████╗░████████╗ +██╔══██╗██╔══██╗██║░██╔╝██╔══██╗██╔══██╗██╔══██╗░░░░░██║██╔════╝██╔══██╗╚══██╔══╝ +███████║██████╔╝█████═╝░██████╔╝██████╔╝██║░░██║░░░░░██║█████╗░░██║░░╚═╝░░░██║░░░ +██╔══██║██╔══██╗██╔═██╗░██╔═══╝░██╔══██╗██║░░██║██╗░░██║██╔══╝░░██║░░██╗░░░██║░░░ +██║░░██║██║░░██║██║░╚██╗██║░░░░░██║░░██║╚█████╔╝╚█████╔╝███████╗╚█████╔╝░░░██║░░░ +╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░╚═╝░╚════╝░░╚════╝░╚══════╝░╚════╝░░░░╚═╝░░░ + +░██████╗░█████╗░██╗░░░░░██╗░██████╗ +██╔════╝██╔══██╗██║░░░░░██║██╔════╝ +╚█████╗░██║░░██║██║░░░░░██║╚█████╗░ +░╚═══██╗██║░░██║██║░░░░░██║░╚═══██╗ +██████╔╝╚█████╔╝███████╗██║██████╔╝ +╚═════╝░░╚════╝░╚══════╝╚═╝╚═════╝░ " ) ); From 8ba6ead22be545e0c20ea20f9ea79acfaef306ae Mon Sep 17 00:00:00 2001 From: kwiss Date: Thu, 16 May 2024 23:47:01 +0200 Subject: [PATCH 11/31] feat(hooker): update hooker default impl for debug --- crates/katana/core/src/hooker.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/katana/core/src/hooker.rs b/crates/katana/core/src/hooker.rs index 25268cad14..a7724a96bb 100644 --- a/crates/katana/core/src/hooker.rs +++ b/crates/katana/core/src/hooker.rs @@ -7,6 +7,7 @@ use katana_executor::ExecutorFactory; use starknet::accounts::Call; use starknet::core::types::{BroadcastedInvokeTransaction, FieldElement}; use std::sync::Arc; +use tracing::{error, info}; #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Copy, PartialEq, Eq)] pub struct HookerAddresses { @@ -89,34 +90,42 @@ impl DefaultKatanaHooker { impl KatanaHooker for DefaultKatanaHooker { fn set_sequencer(&mut self, sequencer: Arc>) { self.sequencer = Some(sequencer); + info!("HOOKER: Sequencer set for hooker"); } async fn verify_message_to_appchain( &self, - _from: FieldElement, - _to: FieldElement, - _selector: FieldElement, + from: FieldElement, + to: FieldElement, + selector: FieldElement, ) -> bool { + info!( + "HOOKER: verify_message_to_appchain called with from: {:?}, to: {:?}, selector: {:?}", + from, to, selector + ); true } async fn verify_invoke_tx_before_pool( &self, - _transaction: BroadcastedInvokeTransaction, + transaction: BroadcastedInvokeTransaction, ) -> bool { + info!("HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", transaction); true } - async fn verify_tx_for_starknet(&self, _call: Call) -> bool { + async fn verify_tx_for_starknet(&self, call: Call) -> bool { + info!("HOOKER: verify_tx_for_starknet called with call: {:?}", call); true } - async fn on_starknet_tx_failed(&self, _call: Call) { + async fn on_starknet_tx_failed(&self, call: Call) { // Log the failure or handle it according to your needs. No-op by default. - tracing::error!("Starknet transaction failed: {:?}", _call); + error!("HOOKER: Starknet transaction failed: {:?}", call); } fn set_addresses(&mut self, addresses: HookerAddresses) { self.addresses = Some(addresses); + info!("HOOKER: Addresses set for hooker: {:?}", addresses); } } From b219de6e1b08815a8997597d42e0e82baf318b47 Mon Sep 17 00:00:00 2001 From: bexan Date: Tue, 21 May 2024 11:33:58 +0200 Subject: [PATCH 12/31] katana: hooker update BroadcastedInvokeTransactionV3 --- crates/katana/core/src/hooker.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/katana/core/src/hooker.rs b/crates/katana/core/src/hooker.rs index a7724a96bb..48d277ae43 100644 --- a/crates/katana/core/src/hooker.rs +++ b/crates/katana/core/src/hooker.rs @@ -5,7 +5,7 @@ use crate::sequencer::KatanaSequencer; use async_trait::async_trait; use katana_executor::ExecutorFactory; use starknet::accounts::Call; -use starknet::core::types::{BroadcastedInvokeTransaction, FieldElement}; +use starknet::core::types::{BroadcastedInvokeTransactionV3, FieldElement}; use std::sync::Arc; use tracing::{error, info}; @@ -44,7 +44,7 @@ pub trait KatanaHooker { /// # Arguments /// /// * `transaction` - The invoke transaction to be verified. - async fn verify_invoke_tx_before_pool(&self, transaction: BroadcastedInvokeTransaction) + async fn verify_invoke_tx_before_pool(&self, transaction: BroadcastedInvokeTransactionV3) -> bool; /// Runs code right before a message to starknet @@ -108,7 +108,7 @@ impl KatanaHooker for DefaultKa async fn verify_invoke_tx_before_pool( &self, - transaction: BroadcastedInvokeTransaction, + transaction: BroadcastedInvokeTransactionV3, ) -> bool { info!("HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", transaction); true From 0fb79ec9c4c386805223c70451dd569a80866f92 Mon Sep 17 00:00:00 2001 From: bexan Date: Wed, 22 May 2024 14:52:43 +0200 Subject: [PATCH 13/31] starknet: add call to verify_invoke_tx_before_pool --- .../katana/rpc/rpc-types/src/error/starknet.rs | 3 +++ crates/katana/rpc/rpc/src/starknet.rs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/katana/rpc/rpc-types/src/error/starknet.rs b/crates/katana/rpc/rpc-types/src/error/starknet.rs index a4aacae381..a77b1f8e95 100644 --- a/crates/katana/rpc/rpc-types/src/error/starknet.rs +++ b/crates/katana/rpc/rpc-types/src/error/starknet.rs @@ -75,6 +75,8 @@ pub enum StarknetApiError { TooManyKeysInFilter, #[error("Failed to fetch pending transactions")] FailedToFetchPendingTransactions, + #[error("Solis: Assets are invalid on L2")] + SolisAssetFault, } impl StarknetApiError { @@ -110,6 +112,7 @@ impl StarknetApiError { StarknetApiError::UnsupportedContractClassVersion => 62, StarknetApiError::UnexpectedError { .. } => 63, StarknetApiError::ProofLimitExceeded => 10000, + StarknetApiError::SolisAssetFault => 7777, } } diff --git a/crates/katana/rpc/rpc/src/starknet.rs b/crates/katana/rpc/rpc/src/starknet.rs index a02ed30c48..797084da55 100644 --- a/crates/katana/rpc/rpc/src/starknet.rs +++ b/crates/katana/rpc/rpc/src/starknet.rs @@ -36,7 +36,7 @@ use katana_rpc_types::{ use katana_rpc_types_builder::ReceiptBuilder; use katana_tasks::{BlockingTaskPool, TokioTaskSpawner}; use starknet::core::types::{ - BlockTag, DeclareTransactionTrace, DeployAccountTransactionTrace, ExecuteInvocation, + BlockTag, BroadcastedInvokeTransaction, DeclareTransactionTrace, DeployAccountTransactionTrace, ExecuteInvocation, InvokeTransactionTrace, L1HandlerTransactionTrace, RevertedInvocation, SimulatedTransaction, TransactionExecutionStatus, TransactionStatus, TransactionTrace, }; @@ -675,6 +675,20 @@ impl StarknetApiServer for StarknetApi { &self, invoke_transaction: BroadcastedInvokeTx, ) -> RpcResult { + + if let Some(hooker) = &self.inner.sequencer.hooker { + let tx_clone = invoke_transaction.0.clone(); + match tx_clone { + BroadcastedInvokeTransaction::V3(tx) => { + if !hooker.read().await.verify_invoke_tx_before_pool(tx).await { + return Err(StarknetApiError::SolisAssetFault.into()); + } + }, + BroadcastedInvokeTransaction::V1(_tx) => { + return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + } + } + } self.on_io_blocking_task(move |this| { if invoke_transaction.is_query() { return Err(StarknetApiError::UnsupportedTransactionVersion.into()); From f6705a463212b2194b5518a04a6e4280b3e40b24 Mon Sep 17 00:00:00 2001 From: bexan Date: Wed, 22 May 2024 15:55:28 +0200 Subject: [PATCH 14/31] update transaction type --- crates/katana/core/src/hooker.rs | 6 +++--- crates/katana/rpc/rpc/src/starknet.rs | 13 +++---------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/katana/core/src/hooker.rs b/crates/katana/core/src/hooker.rs index 48d277ae43..a7724a96bb 100644 --- a/crates/katana/core/src/hooker.rs +++ b/crates/katana/core/src/hooker.rs @@ -5,7 +5,7 @@ use crate::sequencer::KatanaSequencer; use async_trait::async_trait; use katana_executor::ExecutorFactory; use starknet::accounts::Call; -use starknet::core::types::{BroadcastedInvokeTransactionV3, FieldElement}; +use starknet::core::types::{BroadcastedInvokeTransaction, FieldElement}; use std::sync::Arc; use tracing::{error, info}; @@ -44,7 +44,7 @@ pub trait KatanaHooker { /// # Arguments /// /// * `transaction` - The invoke transaction to be verified. - async fn verify_invoke_tx_before_pool(&self, transaction: BroadcastedInvokeTransactionV3) + async fn verify_invoke_tx_before_pool(&self, transaction: BroadcastedInvokeTransaction) -> bool; /// Runs code right before a message to starknet @@ -108,7 +108,7 @@ impl KatanaHooker for DefaultKa async fn verify_invoke_tx_before_pool( &self, - transaction: BroadcastedInvokeTransactionV3, + transaction: BroadcastedInvokeTransaction, ) -> bool { info!("HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", transaction); true diff --git a/crates/katana/rpc/rpc/src/starknet.rs b/crates/katana/rpc/rpc/src/starknet.rs index 797084da55..a0fde82e7e 100644 --- a/crates/katana/rpc/rpc/src/starknet.rs +++ b/crates/katana/rpc/rpc/src/starknet.rs @@ -677,16 +677,9 @@ impl StarknetApiServer for StarknetApi { ) -> RpcResult { if let Some(hooker) = &self.inner.sequencer.hooker { - let tx_clone = invoke_transaction.0.clone(); - match tx_clone { - BroadcastedInvokeTransaction::V3(tx) => { - if !hooker.read().await.verify_invoke_tx_before_pool(tx).await { - return Err(StarknetApiError::SolisAssetFault.into()); - } - }, - BroadcastedInvokeTransaction::V1(_tx) => { - return Err(StarknetApiError::UnsupportedTransactionVersion.into()); - } + let tx = invoke_transaction.0.clone(); + if !hooker.read().await.verify_invoke_tx_before_pool(tx).await { + return Err(StarknetApiError::SolisAssetFault.into()); } } self.on_io_blocking_task(move |this| { From 64538f8e3595ab9aace25512e026c7af67c47651 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 29 May 2024 00:09:28 +0200 Subject: [PATCH 15/31] feat(solis): update gather message to test only pending --- .../core/src/service/messaging/starknet.rs | 165 ++++++------------ 1 file changed, 50 insertions(+), 115 deletions(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index fc7b864975..c0b7f1745d 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -12,7 +12,7 @@ use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{AnyProvider, JsonRpcClient, Provider}; use starknet::signers::{LocalWallet, SigningKey}; -use std::collections::HashMap; +use std::collections::HashSet; use std::sync::Arc; use tokio::sync::RwLock as AsyncRwLock; use tracing::{debug, error, info, trace, warn}; @@ -20,11 +20,6 @@ use url::Url; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; -/// As messaging in starknet is only possible with EthAddress in the `to_address` -/// field, we have to set magic value to understand what the user want to do. -/// In the case of execution -> the felt 'EXE' will be passed. -/// And for normal messages, the felt 'MSG' is used. -/// Those values are very not likely a valid account address on starknet. const MSG_MAGIC: FieldElement = felt!("0x4d5347"); const EXE_MAGIC: FieldElement = felt!("0x455845"); @@ -37,6 +32,7 @@ pub struct StarknetMessaging sender_account_address: FieldElement, messaging_contract_address: FieldElement, hooker: Arc + Send + Sync>>, + event_cache: Arc>>, } impl StarknetMessaging { @@ -65,44 +61,55 @@ impl StarknetMessaging { sender_account_address, messaging_contract_address, hooker, + event_cache: Arc::new(AsyncRwLock::new(HashSet::new())), }) } - /// Fetches events for the given blocks range. - pub async fn fetch_events( - &self, - from_block: BlockId, - to_block: BlockId, - ) -> Result>> { - trace!(target: LOG_TARGET, from_block = ?from_block, to_block = ?to_block, "Fetching logs."); - - let mut block_to_events: HashMap> = HashMap::new(); - - let filter = EventFilter { - from_block: Some(from_block), - to_block: Some(to_block), - address: Some(self.messaging_contract_address), - // TODO: this might come from the configuration actually. - keys: None, - }; - - // TODO: this chunk_size may also come from configuration? - let chunk_size = 200; + async fn fetch_pending_events(&self, chain_id: ChainId) -> MessengerResult> { + let mut l1_handler_txs: Vec = vec![]; let mut continuation_token: Option = None; loop { - let event_page = - self.provider.get_events(filter.clone(), continuation_token, chunk_size).await?; - - event_page.events.into_iter().for_each(|event| { - // We ignore events without the block number - if let Some(block_number) = event.block_number { - block_to_events - .entry(block_number) - .and_modify(|v| v.push(event.clone())) - .or_insert(vec![event]); + let filter = EventFilter { + from_block: Some(BlockId::Tag(BlockTag::Pending)), + to_block: Some(BlockId::Tag(BlockTag::Pending)), + address: Some(self.messaging_contract_address), + keys: None, + }; + + let event_page = self + .provider + .get_events(filter.clone(), continuation_token.clone(), 200) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error fetching pending events: {:?}", e); + Error::SendError + })?; + + for event in event_page.events { + let event_id = event.transaction_hash.to_string(); // Assuming `transaction_hash` is the unique identifier for the event + + let mut cache = self.event_cache.write().await; + if cache.contains(&event_id) { + continue; } - }); + + if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { + if let Ok((from, to, selector)) = info_from_event(&event) { + let hooker = Arc::clone(&self.hooker); + let is_message_accepted = hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; + + if is_message_accepted { + l1_handler_txs.push(tx); + cache.insert(event_id); + } + } + } + } continuation_token = event_page.continuation_token; @@ -111,10 +118,9 @@ impl StarknetMessaging { } } - Ok(block_to_events) + Ok(l1_handler_txs) } - /// Sends an invoke TX on starknet. async fn send_invoke_tx(&self, calls: Vec) -> Result { let signer = Arc::new(&self.wallet); @@ -128,7 +134,6 @@ impl StarknetMessaging { account.set_block_id(BlockId::Tag(BlockTag::Pending)); - // TODO: we need to have maximum fee configurable. let execution = account.execute(calls).fee_estimate_multiplier(10f64); let estimated_fee = (execution.estimate_fee().await?.overall_fee) * 10u64.into(); let execution_with_fee = execution.max_fee(estimated_fee); @@ -138,8 +143,6 @@ impl StarknetMessaging { match execution_with_fee.send().await { Ok(tx) => { info!(target: LOG_TARGET, "Transaction successful: {:?}", tx); - println!("tx: {:?}", tx); - println!("tx_hash: {:?}", tx.transaction_hash); Ok(tx.transaction_hash) } Err(e) => { @@ -149,7 +152,6 @@ impl StarknetMessaging { } } - /// Sends messages hashes to settlement layer by sending a transaction. async fn send_hashes(&self, mut hashes: Vec) -> MessengerResult { hashes.retain(|&x| x != HASH_EXEC); @@ -192,7 +194,7 @@ impl Messenger for StarknetM from_block: u64, max_blocks: u64, chain_id: ChainId, - ) -> MessengerResult<(u64, Vec)> { + ) -> MessengerResult<(u64, Vec)> { let chain_latest_block: u64 = match self.provider.block_number().await { Ok(n) => n, Err(e) => { @@ -204,57 +206,13 @@ impl Messenger for StarknetM } }; - if from_block > chain_latest_block { - // Nothing to fetch, we can skip waiting the next tick. - return Ok((chain_latest_block, vec![])); - } - - // +1 as the from_block counts as 1 block fetched. - let to_block = if from_block + max_blocks + 1 < chain_latest_block { - from_block + max_blocks - } else { - chain_latest_block - }; - - let mut l1_handler_txs: Vec = vec![]; + let pending_txs = self.fetch_pending_events(chain_id).await?; - info!(target: LOG_TARGET, "Gathering messages from block {} to block {}", from_block, to_block); - - let block_to_events = self - .fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) - .await - .map_err(|e| { - error!(target: LOG_TARGET, "Error fetching events: {:?}", e); - Error::SendError - })?; - - for (block_number, block_events) in block_to_events.iter() { - debug!( - target: LOG_TARGET, - block_number = %block_number, - events_count = %block_events.len(), - "Converting events of block into L1HandlerTx." - ); - - for event in block_events.iter() { - if let Ok(tx) = l1_handler_tx_from_event(event, chain_id) { - if let Ok((from, to, selector)) = info_from_event(event) { - let hooker = Arc::clone(&self.hooker); - let is_message_accepted = hooker - .read() - .await - .verify_message_to_appchain(from, to, selector) - .await; - - if is_message_accepted { - l1_handler_txs.push(tx); - } - } - } - } + if from_block != chain_latest_block { + self.event_cache.write().await.clear(); } - Ok((to_block, l1_handler_txs)) + Ok((chain_latest_block, pending_txs)) } async fn send_messages( @@ -297,18 +255,11 @@ impl Messenger for StarknetM } } -/// Parses messages sent by cairo contracts to compute their hashes. -/// -/// Messages can also be labelled as EXE, which in this case generate a `Call` -/// additionally to the hash. fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec, Vec)> { let mut hashes: Vec = vec![]; let mut calls: Vec = vec![]; for m in messages { - // Field `to_address` is restricted to eth addresses space. So the - // `to_address` is set to 'EXE'/'MSG' to indicate that the message - // has to be executed or sent normally. let magic = m.to_address; if magic == EXE_MAGIC { @@ -325,7 +276,6 @@ fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec= 3 { calldata.extend(m.payload[2..].to_vec()); } @@ -333,15 +283,7 @@ fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec = vec![]; @@ -354,7 +296,6 @@ fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec Result Result Result<(FieldElement, FieldElement, error!(target: LOG_TARGET, "Event MessageSentToAppchain is not well formatted"); } - // See contract appchain_messaging.cairo for MessageSentToAppchain event. let from_address = event.keys[2]; let to_address = event.keys[3]; let entry_point_selector = event.data[0]; @@ -426,7 +362,6 @@ fn info_from_event(event: &EmittedEvent) -> Result<(FieldElement, FieldElement, #[cfg(test)] mod tests { - use katana_primitives::utils::transaction::compute_l1_handler_tx_hash; use starknet::macros::felt; From 44c2f0368ef7efae8330fc60ba0fd81a642d8cf3 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 29 May 2024 01:36:00 +0200 Subject: [PATCH 16/31] feat(solis): update gather message add debug logs --- .../core/src/service/messaging/service.rs | 3 ++- .../core/src/service/messaging/starknet.rs | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 52da9c474a..dd3090825c 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -102,9 +102,10 @@ impl MessagingService { let (block_num, txs) = inner.gather_messages(from_block, max_block, backend.chain_id).await?; let txs_count = txs.len(); - + info!(target: LOG_TARGET, "Gathered {} transactions for Starknet mode.", txs_count); txs.into_iter().for_each(|tx| { let hash = tx.calculate_hash(); + info!(target: LOG_TARGET, "Processing transaction with hash: {:#x}", hash); trace_l1_handler_tx_exec(hash, &tx); pool.add_transaction(ExecutableTxWithHash { hash, transaction: tx.into() }) }); diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index c0b7f1745d..95878c70a0 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -70,6 +70,8 @@ impl StarknetMessaging { let mut continuation_token: Option = None; loop { + debug!(target: LOG_TARGET, "Fetching pending events with continuation token: {:?}", continuation_token); + let filter = EventFilter { from_block: Some(BlockId::Tag(BlockTag::Pending)), to_block: Some(BlockId::Tag(BlockTag::Pending)), @@ -86,11 +88,15 @@ impl StarknetMessaging { Error::SendError })?; + debug!(target: LOG_TARGET, "Fetched {} events", event_page.events.len()); + for event in event_page.events { - let event_id = event.transaction_hash.to_string(); // Assuming `transaction_hash` is the unique identifier for the event + let event_id = event.transaction_hash.to_string(); + debug!(target: LOG_TARGET, "Processing event with ID: {}", event_id); let mut cache = self.event_cache.write().await; if cache.contains(&event_id) { + debug!(target: LOG_TARGET, "Event ID: {} already processed, skipping", event_id); continue; } @@ -104,8 +110,11 @@ impl StarknetMessaging { .await; if is_message_accepted { + debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); l1_handler_txs.push(tx); cache.insert(event_id); + } else { + debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); } } } @@ -118,6 +127,7 @@ impl StarknetMessaging { } } + debug!(target: LOG_TARGET, "Total transactions gathered: {}", l1_handler_txs.len()); Ok(l1_handler_txs) } @@ -195,8 +205,13 @@ impl Messenger for StarknetM max_blocks: u64, chain_id: ChainId, ) -> MessengerResult<(u64, Vec)> { + debug!(target: LOG_TARGET, "Gathering messages from block: {} with max blocks: {}", from_block, max_blocks); + let chain_latest_block: u64 = match self.provider.block_number().await { - Ok(n) => n, + Ok(n) => { + debug!(target: LOG_TARGET, "Latest block number on chain: {}", n); + n + } Err(e) => { warn!( target: LOG_TARGET, @@ -209,9 +224,11 @@ impl Messenger for StarknetM let pending_txs = self.fetch_pending_events(chain_id).await?; if from_block != chain_latest_block { + debug!(target: LOG_TARGET, "Block number changed from {} to {}, clearing cache", from_block, chain_latest_block); self.event_cache.write().await.clear(); } + debug!(target: LOG_TARGET, "Returning {} pending transactions", pending_txs.len()); Ok((chain_latest_block, pending_txs)) } From 85fe586a9ac55075dcc17324d69810871a27dca9 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 29 May 2024 02:19:55 +0200 Subject: [PATCH 17/31] feat(solis): comment hooker --- .../core/src/service/messaging/starknet.rs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 95878c70a0..34b1c2e506 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -102,20 +102,19 @@ impl StarknetMessaging { if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { if let Ok((from, to, selector)) = info_from_event(&event) { - let hooker = Arc::clone(&self.hooker); - let is_message_accepted = hooker - .read() - .await - .verify_message_to_appchain(from, to, selector) - .await; - - if is_message_accepted { - debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); - l1_handler_txs.push(tx); - cache.insert(event_id); - } else { - debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); - } + // let hooker = Arc::clone(&self.hooker); + // let is_message_accepted = hooker + // .read() + // .await + // .verify_message_to_appchain(from, to, selector) + // .await; + // if is_message_accepted { + debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); + l1_handler_txs.push(tx); + cache.insert(event_id); + // } else { + // debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); + // } } } } From 5d7b11accb5b6bd205ee9ae070ad49342ff27d62 Mon Sep 17 00:00:00 2001 From: kwiss Date: Wed, 29 May 2024 12:11:13 +0200 Subject: [PATCH 18/31] feat(messaging): update gather message to handle latestblock --- .../core/src/service/messaging/starknet.rs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 34b1c2e506..c439e5da83 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -13,6 +13,7 @@ use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{AnyProvider, JsonRpcClient, Provider}; use starknet::signers::{LocalWallet, SigningKey}; use std::collections::HashSet; +use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use tokio::sync::RwLock as AsyncRwLock; use tracing::{debug, error, info, trace, warn}; @@ -33,6 +34,7 @@ pub struct StarknetMessaging messaging_contract_address: FieldElement, hooker: Arc + Send + Sync>>, event_cache: Arc>>, + latest_block: Arc, } impl StarknetMessaging { @@ -47,6 +49,7 @@ impl StarknetMessaging { let private_key = FieldElement::from_hex_be(&config.private_key)?; let key = SigningKey::from_secret_scalar(private_key); let wallet = LocalWallet::from_signing_key(key); + let latest_block = Arc::new(AtomicU64::new(0)); let chain_id = provider.chain_id().await?; let sender_account_address = FieldElement::from_hex_be(&config.sender_address)?; @@ -62,6 +65,7 @@ impl StarknetMessaging { messaging_contract_address, hooker, event_cache: Arc::new(AsyncRwLock::new(HashSet::new())), + latest_block, }) } @@ -94,14 +98,16 @@ impl StarknetMessaging { let event_id = event.transaction_hash.to_string(); debug!(target: LOG_TARGET, "Processing event with ID: {}", event_id); - let mut cache = self.event_cache.write().await; - if cache.contains(&event_id) { - debug!(target: LOG_TARGET, "Event ID: {} already processed, skipping", event_id); - continue; + { + let cache = self.event_cache.read().await; + if cache.contains(&event_id) { + debug!(target: LOG_TARGET, "Event ID: {} already processed, skipping", event_id); + continue; + } } if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { - if let Ok((from, to, selector)) = info_from_event(&event) { + // if let Ok((from, to, selector)) = info_from_event(&event) { // let hooker = Arc::clone(&self.hooker); // let is_message_accepted = hooker // .read() @@ -111,11 +117,12 @@ impl StarknetMessaging { // if is_message_accepted { debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); l1_handler_txs.push(tx); + let mut cache = self.event_cache.write().await; cache.insert(event_id); // } else { // debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); // } - } + // } } } @@ -204,7 +211,7 @@ impl Messenger for StarknetM max_blocks: u64, chain_id: ChainId, ) -> MessengerResult<(u64, Vec)> { - debug!(target: LOG_TARGET, "Gathering messages from block: {} with max blocks: {}", from_block, max_blocks); + debug!(target: LOG_TARGET, "Gathering messages"); let chain_latest_block: u64 = match self.provider.block_number().await { Ok(n) => { @@ -220,14 +227,17 @@ impl Messenger for StarknetM } }; - let pending_txs = self.fetch_pending_events(chain_id).await?; - - if from_block != chain_latest_block { - debug!(target: LOG_TARGET, "Block number changed from {} to {}, clearing cache", from_block, chain_latest_block); + // Check if the block number has changed + let previous_block = self.latest_block.load(Ordering::Relaxed); + if previous_block != chain_latest_block { + debug!(target: LOG_TARGET, "Block number changed from {} to {}, clearing cache", previous_block, chain_latest_block); self.event_cache.write().await.clear(); + self.latest_block.store(chain_latest_block, Ordering::Relaxed); } + let pending_txs = self.fetch_pending_events(chain_id).await?; debug!(target: LOG_TARGET, "Returning {} pending transactions", pending_txs.len()); + Ok((chain_latest_block, pending_txs)) } From 253af536b1a6ee2a46e6db70a8ecbedf2a745fcf Mon Sep 17 00:00:00 2001 From: kwiss Date: Tue, 4 Jun 2024 00:06:45 +0200 Subject: [PATCH 19/31] fix: hooker remove hooker from send message for testing --- .../core/src/service/messaging/starknet.rs | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index c439e5da83..b1cf4457bf 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -108,20 +108,20 @@ impl StarknetMessaging { if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { // if let Ok((from, to, selector)) = info_from_event(&event) { - // let hooker = Arc::clone(&self.hooker); - // let is_message_accepted = hooker - // .read() - // .await - // .verify_message_to_appchain(from, to, selector) - // .await; - // if is_message_accepted { - debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); - l1_handler_txs.push(tx); - let mut cache = self.event_cache.write().await; - cache.insert(event_id); - // } else { - // debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); - // } + // let hooker = Arc::clone(&self.hooker); + // let is_message_accepted = hooker + // .read() + // .await + // .verify_message_to_appchain(from, to, selector) + // .await; + // if is_message_accepted { + debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); + l1_handler_txs.push(tx); + let mut cache = self.event_cache.write().await; + cache.insert(event_id); + // } else { + // debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); + // } // } } } @@ -244,39 +244,25 @@ impl Messenger for StarknetM async fn send_messages( &self, messages: &[MessageToL1], - ) -> MessengerResult::MessageHash>> { + ) -> MessengerResult> { if messages.is_empty() { - info!(target: LOG_TARGET, "No messages to send."); return Ok(vec![]); } let (hashes, calls) = parse_messages(messages)?; - for call in &calls { - if !self.hooker.read().await.verify_tx_for_starknet(call.clone()).await { - warn!(target: LOG_TARGET, "Call verification failed for call: {:?}", call); - continue; - } - } - if !calls.is_empty() { - info!(target: LOG_TARGET, "Sending invoke transactions for calls."); - match self.send_invoke_tx(calls.clone()).await { + match self.send_invoke_tx(calls).await { Ok(tx_hash) => { trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); } Err(e) => { error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); - for call in calls { - self.hooker.read().await.on_starknet_tx_failed(call).await; - } return Err(Error::SendError); } }; } - self.send_hashes(hashes.clone()).await?; - Ok(hashes) } } From b08f796d81a6fe6e19aa5d17c6c11704f906fddf Mon Sep 17 00:00:00 2001 From: bexan Date: Mon, 10 Jun 2024 07:06:58 +0200 Subject: [PATCH 20/31] hooker: generate json file with addresses --- Cargo.lock | 108 +++- artifacts/contract.abi.json | 63 ++ artifacts/orderbook.abi.json | 671 ++++++++++++++++++++++ artifacts/starknet_utils.json | 224 ++++++++ bin/solis/Cargo.toml | 7 +- bin/solis/src/contracts/account.rs | 42 ++ bin/solis/src/contracts/executor.rs | 0 bin/solis/src/contracts/mod.rs | 3 + bin/solis/src/contracts/orderbook.rs | 12 + bin/solis/src/contracts/starknet_utils.rs | 19 + bin/solis/src/hooker.rs | 578 +++++++++++++++++++ bin/solis/src/main.rs | 45 +- 12 files changed, 1750 insertions(+), 22 deletions(-) create mode 100644 artifacts/contract.abi.json create mode 100644 artifacts/orderbook.abi.json create mode 100644 artifacts/starknet_utils.json create mode 100644 bin/solis/src/contracts/account.rs create mode 100644 bin/solis/src/contracts/executor.rs create mode 100644 bin/solis/src/contracts/mod.rs create mode 100644 bin/solis/src/contracts/orderbook.rs create mode 100644 bin/solis/src/contracts/starknet_utils.rs create mode 100644 bin/solis/src/hooker.rs diff --git a/Cargo.lock b/Cargo.lock index ed8e31e9b5..9f0bcd44a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1705,10 +1705,34 @@ source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.5#54df2a4114c0c61 dependencies = [ "anyhow", "async-trait", - "cainome-cairo-serde", - "cainome-parser", - "cainome-rs", - "cainome-rs-macro", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "camino", + "clap", + "clap_complete", + "convert_case 0.6.0", + "serde", + "serde_json", + "starknet 0.9.0", + "thiserror", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "cainome" +version = "0.2.3" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "anyhow", + "async-trait", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "camino", "clap", "clap_complete", @@ -1731,6 +1755,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cainome-cairo-serde" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "starknet 0.9.0", + "thiserror", +] + [[package]] name = "cainome-parser" version = "0.1.0" @@ -1744,14 +1777,43 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cainome-parser" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "convert_case 0.6.0", + "quote", + "serde_json", + "starknet 0.9.0", + "syn 2.0.55", + "thiserror", +] + [[package]] name = "cainome-rs" version = "0.1.0" source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.5#54df2a4114c0c61359c2f1a70bc7e5fb57d9eaf2" dependencies = [ "anyhow", - "cainome-cairo-serde", - "cainome-parser", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "proc-macro2", + "quote", + "serde_json", + "starknet 0.9.0", + "syn 2.0.55", + "thiserror", +] + +[[package]] +name = "cainome-rs" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "anyhow", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "proc-macro2", "quote", "serde_json", @@ -1766,9 +1828,26 @@ version = "0.1.0" source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.5#54df2a4114c0c61359c2f1a70bc7e5fb57d9eaf2" dependencies = [ "anyhow", - "cainome-cairo-serde", - "cainome-parser", - "cainome-rs", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", + "proc-macro2", + "quote", + "serde_json", + "starknet 0.9.0", + "syn 2.0.55", + "thiserror", +] + +[[package]] +name = "cainome-rs-macro" +version = "0.1.0" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.2.6#07ba9c91baa2f6aa2bc850b6699ffc2266be9a13" +dependencies = [ + "anyhow", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "proc-macro2", "quote", "serde_json", @@ -3574,7 +3653,7 @@ name = "dojo-bindgen" version = "0.7.0-alpha.1" dependencies = [ "async-trait", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "camino", "chrono", "convert_case 0.6.0", @@ -3741,7 +3820,7 @@ dependencies = [ "assert_fs", "assert_matches", "async-trait", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "cairo-lang-filesystem", "cairo-lang-project", "cairo-lang-starknet", @@ -11316,6 +11395,8 @@ dependencies = [ "alloy-primitives", "anyhow", "assert_matches", + "async-trait", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.6)", "cfg-if", "clap", "clap_complete", @@ -11329,6 +11410,7 @@ dependencies = [ "katana-rpc-api", "serde_json", "shellexpand", + "starknet 0.9.0", "starknet_api", "tokio", "tracing", @@ -11344,7 +11426,7 @@ dependencies = [ "assert_fs", "async-trait", "bigdecimal 0.4.3", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", @@ -11399,7 +11481,7 @@ dependencies = [ "anyhow", "assert_fs", "async-trait", - "cainome", + "cainome 0.2.3 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.5)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", diff --git a/artifacts/contract.abi.json b/artifacts/contract.abi.json new file mode 100644 index 0000000000..5ac0f4f06d --- /dev/null +++ b/artifacts/contract.abi.json @@ -0,0 +1,63 @@ +[ + { + "type": "impl", + "name": "ITestImpl", + "interface_name": "package_name::ITest" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "package_name::TxCall", + "members": [ + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "package_name::ITest", + "items": [ + { + "type": "function", + "name": "ev", + "inputs": [], + "outputs": [ + { + "type": "package_name::TxCall" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "package_name::c1::Event", + "kind": "enum", + "variants": [] + } +] diff --git a/artifacts/orderbook.abi.json b/artifacts/orderbook.abi.json new file mode 100644 index 0000000000..667d00b5df --- /dev/null +++ b/artifacts/orderbook.abi.json @@ -0,0 +1,671 @@ +[ + { + "type": "impl", + "name": "ImplOrderbook", + "interface_name": "ark_orderbook::orderbook::Orderbook" + }, + { + "type": "enum", + "name": "ark_common::protocol::order_types::RouteType", + "variants": [ + { + "name": "Erc20ToErc721", + "type": "()" + }, + { + "name": "Erc721ToErc20", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::integer::u256" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "ark_orderbook::order::order_v1::OrderV1", + "members": [ + { + "name": "route", + "type": "ark_common::protocol::order_types::RouteType" + }, + { + "name": "currency_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "currency_chain_id", + "type": "core::felt252" + }, + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "offerer", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_chain_id", + "type": "core::felt252" + }, + { + "name": "token_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_id", + "type": "core::option::Option::" + }, + { + "name": "quantity", + "type": "core::integer::u256" + }, + { + "name": "start_amount", + "type": "core::integer::u256" + }, + { + "name": "end_amount", + "type": "core::integer::u256" + }, + { + "name": "start_date", + "type": "core::integer::u64" + }, + { + "name": "end_date", + "type": "core::integer::u64" + }, + { + "name": "broker_id", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "additional_data", + "type": "core::array::Span::" + } + ] + }, + { + "type": "struct", + "name": "ark_common::crypto::signer::SignInfo", + "members": [ + { + "name": "user_pubkey", + "type": "core::felt252" + }, + { + "name": "user_sig_r", + "type": "core::felt252" + }, + { + "name": "user_sig_s", + "type": "core::felt252" + } + ] + }, + { + "type": "enum", + "name": "ark_common::crypto::signer::Signer", + "variants": [ + { + "name": "WEIERSTRESS_STARKNET", + "type": "ark_common::crypto::signer::SignInfo" + } + ] + }, + { + "type": "struct", + "name": "ark_common::protocol::order_types::CancelInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "canceller", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_chain_id", + "type": "core::felt252" + }, + { + "name": "token_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_id", + "type": "core::option::Option::" + } + ] + }, + { + "type": "enum", + "name": "core::option::Option::", + "variants": [ + { + "name": "Some", + "type": "core::felt252" + }, + { + "name": "None", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "ark_common::protocol::order_types::FulfillInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "related_order_hash", + "type": "core::option::Option::" + }, + { + "name": "fulfiller", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_chain_id", + "type": "core::felt252" + }, + { + "name": "token_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token_id", + "type": "core::option::Option::" + }, + { + "name": "fulfill_broker_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "enum", + "name": "ark_common::protocol::order_types::OrderType", + "variants": [ + { + "name": "Listing", + "type": "()" + }, + { + "name": "Auction", + "type": "()" + }, + { + "name": "Offer", + "type": "()" + }, + { + "name": "CollectionOffer", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "ark_orderbook::orderbook::Orderbook", + "items": [ + { + "type": "function", + "name": "whitelist_broker", + "inputs": [ + { + "name": "broker_id", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "create_order", + "inputs": [ + { + "name": "order", + "type": "ark_orderbook::order::order_v1::OrderV1" + }, + { + "name": "signer", + "type": "ark_common::crypto::signer::Signer" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "cancel_order", + "inputs": [ + { + "name": "cancel_info", + "type": "ark_common::protocol::order_types::CancelInfo" + }, + { + "name": "signer", + "type": "ark_common::crypto::signer::Signer" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "fulfill_order", + "inputs": [ + { + "name": "fulfill_info", + "type": "ark_common::protocol::order_types::FulfillInfo" + }, + { + "name": "signer", + "type": "ark_common::crypto::signer::Signer" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get_order_type", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "ark_common::protocol::order_types::OrderType" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order_status", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_auction_expiration", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "ark_orderbook::order::order_v1::OrderV1" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order_signer", + "inputs": [ + { + "name": "order_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_order_hash", + "inputs": [ + { + "name": "token_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_starknet_executor_address", + "inputs": [ + { + "name": "value", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "admin", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "chain_id", + "type": "core::felt252" + } + ] + }, + { + "type": "struct", + "name": "ark_common::protocol::order_types::ExecutionValidationInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "transaction_hash", + "type": "core::felt252" + }, + { + "name": "starknet_block_timestamp", + "type": "core::integer::u64" + } + ] + }, + { + "type": "l1_handler", + "name": "validate_order_execution", + "inputs": [ + { + "name": "_from_address", + "type": "core::felt252" + }, + { + "name": "info", + "type": "ark_common::protocol::order_types::ExecutionValidationInfo" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderPlaced", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "order_version", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "order_type", + "type": "ark_common::protocol::order_types::OrderType", + "kind": "key" + }, + { + "name": "cancelled_order_hash", + "type": "core::option::Option::", + "kind": "data" + }, + { + "name": "order", + "type": "ark_orderbook::order::order_v1::OrderV1", + "kind": "data" + } + ] + }, + { + "type": "enum", + "name": "ark_common::protocol::order_types::OrderStatus", + "variants": [ + { + "name": "Open", + "type": "()" + }, + { + "name": "Fulfilled", + "type": "()" + }, + { + "name": "Executed", + "type": "()" + }, + { + "name": "CancelledUser", + "type": "()" + }, + { + "name": "CancelledByNewOrder", + "type": "()" + }, + { + "name": "CancelledAssetFault", + "type": "()" + }, + { + "name": "CancelledOwnership", + "type": "()" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderExecuted", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "order_status", + "type": "ark_common::protocol::order_types::OrderStatus", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderCancelled", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "reason", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::OrderFulfilled", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "fulfiller", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "related_order_hash", + "type": "core::option::Option::", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::RollbackStatus", + "kind": "struct", + "members": [ + { + "name": "order_hash", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "reason", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "ark_orderbook::orderbook::orderbook::Event", + "kind": "enum", + "variants": [ + { + "name": "OrderPlaced", + "type": "ark_orderbook::orderbook::orderbook::OrderPlaced", + "kind": "nested" + }, + { + "name": "OrderExecuted", + "type": "ark_orderbook::orderbook::orderbook::OrderExecuted", + "kind": "nested" + }, + { + "name": "OrderCancelled", + "type": "ark_orderbook::orderbook::orderbook::OrderCancelled", + "kind": "nested" + }, + { + "name": "OrderFulfilled", + "type": "ark_orderbook::orderbook::orderbook::OrderFulfilled", + "kind": "nested" + }, + { + "name": "Upgraded", + "type": "ark_orderbook::orderbook::orderbook::Upgraded", + "kind": "nested" + }, + { + "name": "RollbackStatus", + "type": "ark_orderbook::orderbook::orderbook::RollbackStatus", + "kind": "nested" + } + ] + } +] diff --git a/artifacts/starknet_utils.json b/artifacts/starknet_utils.json new file mode 100644 index 0000000000..2abf79b609 --- /dev/null +++ b/artifacts/starknet_utils.json @@ -0,0 +1,224 @@ +[ + { + "type": "struct", + "name": "package_name::ExecutionInfo", + "members": [ + { + "name": "order_hash", + "type": "core::felt252" + }, + { + "name": "nft_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "nft_from", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "nft_to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "nft_token_id", + "type": "core::integer::u256" + }, + { + "name": "payment_from", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "payment_to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "payment_amount", + "type": "core::integer::u256" + }, + { + "name": "payment_currency_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "listing_broker_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fulfill_broker_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "function", + "name": "owner_of", + "inputs": [ + { + "name": "token_id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "ownerOf", + "inputs": [ + { + "name": "token_id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "function", + "name": "is_approved_for_all", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "operator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "isApprovedForAll", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "operator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "function", + "name": "is_valid_signature", + "inputs": [ + { + "name": "hash", + "type": "core::felt252" + }, + { + "name": "signature", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "event", + "name": "solis::starknet_utils::starknet_utils::Event", + "kind": "enum", + "variants": [] + } +] diff --git a/bin/solis/Cargo.toml b/bin/solis/Cargo.toml index fb009ea6aa..5b8debb1c2 100644 --- a/bin/solis/Cargo.toml +++ b/bin/solis/Cargo.toml @@ -9,6 +9,10 @@ version.workspace = true [dependencies] alloy-primitives.workspace = true anyhow.workspace = true +async-trait = "0.1.57" +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.2.6", features = [ + "abigen-rs", +] } cfg-if = "1.0.0" clap.workspace = true clap_complete.workspace = true @@ -22,7 +26,8 @@ katana-rpc-api.workspace = true katana-rpc.workspace = true serde_json.workspace = true shellexpand = "3.1.0" -starknet_api.workspace = true +starknet = "0.9.0" +starknet_api = "0.10.0" tokio.workspace = true tracing-subscriber.workspace = true tracing.workspace = true diff --git a/bin/solis/src/contracts/account.rs b/bin/solis/src/contracts/account.rs new file mode 100644 index 0000000000..f19a7d9c6c --- /dev/null +++ b/bin/solis/src/contracts/account.rs @@ -0,0 +1,42 @@ +use starknet::{ + accounts::{ExecutionEncoding, SingleOwnerAccount}, + core::types::FieldElement, + providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient, Provider}, + signers::{LocalWallet, SigningKey}, +}; + +use url::Url; + +/// Initializes a new account to interact with Starknet. +/// +/// # Arguments +/// +/// * `provider_url` - Starknet provider's url. +/// * `account_address` - Starknet account's address. +/// * `private_key` - Private key associated to the Starknet account. +#[allow(dead_code)] +pub async fn new_account( + provider_url: &str, + account_address: FieldElement, + private_key: FieldElement, +) -> SingleOwnerAccount { + let rpc_url = Url::parse(provider_url).expect("Expecting valid Starknet RPC URL"); + let provider = + AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone()))); + + // TODO: need error instead of expect. + let chain_id = provider + .chain_id() + .await + .expect("couldn't get chain_id from provider"); + + let signer = LocalWallet::from(SigningKey::from_secret_scalar(private_key)); + + SingleOwnerAccount::new( + provider, + signer, + account_address, + chain_id, + ExecutionEncoding::Legacy, + ) +} diff --git a/bin/solis/src/contracts/executor.rs b/bin/solis/src/contracts/executor.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bin/solis/src/contracts/mod.rs b/bin/solis/src/contracts/mod.rs new file mode 100644 index 0000000000..df2f73e1e5 --- /dev/null +++ b/bin/solis/src/contracts/mod.rs @@ -0,0 +1,3 @@ +pub mod account; +pub mod orderbook; +pub mod starknet_utils; diff --git a/bin/solis/src/contracts/orderbook.rs b/bin/solis/src/contracts/orderbook.rs new file mode 100644 index 0000000000..9bc801643b --- /dev/null +++ b/bin/solis/src/contracts/orderbook.rs @@ -0,0 +1,12 @@ +use cainome::rs::abigen; +use starknet::{accounts::ConnectedAccount, core::types::FieldElement}; + +abigen!(OrderbookContract, "./artifacts/orderbook.abi.json"); + +#[allow(dead_code)] +pub fn new_orderbook( + contract_address: FieldElement, + account: A, +) -> OrderbookContract { + OrderbookContract::new(contract_address, account) +} diff --git a/bin/solis/src/contracts/starknet_utils.rs b/bin/solis/src/contracts/starknet_utils.rs new file mode 100644 index 0000000000..280c9f2e6d --- /dev/null +++ b/bin/solis/src/contracts/starknet_utils.rs @@ -0,0 +1,19 @@ +use cainome::rs::abigen; +use starknet::{ + core::types::FieldElement, + providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient}, +}; + +use url::Url; + +abigen!(StarknetUtils, "./artifacts/starknet_utils.json"); +pub fn new_starknet_utils_reader( + contract_address: FieldElement, + provider_url: &str, +) -> StarknetUtilsReader { + let rpc_url = Url::parse(provider_url).expect("Expecting valid Starknet RPC URL"); + let provider = + AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone()))); + + StarknetUtilsReader::new(contract_address, provider) +} diff --git a/bin/solis/src/hooker.rs b/bin/solis/src/hooker.rs new file mode 100644 index 0000000000..4068940884 --- /dev/null +++ b/bin/solis/src/hooker.rs @@ -0,0 +1,578 @@ +//! Solis hooker on Katana transaction lifecycle. +//! +use crate::contracts::starknet_utils::{ExecutionInfo, U256}; +use async_trait::async_trait; +use cainome::cairo_serde::CairoSerde; +use cainome::rs::abigen; +use katana_core::hooker::{HookerAddresses, KatanaHooker}; +use katana_core::sequencer::KatanaSequencer; +use katana_executor::ExecutorFactory; + +use katana_primitives::chain::ChainId; +use katana_primitives::contract::ContractAddress; +use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash, L1HandlerTx}; +use katana_primitives::utils::transaction::compute_l1_message_hash; +use starknet::accounts::Call; +use starknet::core::types::BroadcastedInvokeTransaction; +use starknet::core::types::FieldElement; +use starknet::macros::selector; +use starknet::providers::Provider; +use std::sync::Arc; +use std::fs::File; +use std::fs::OpenOptions; +use std::io::Write; +use std::io::Read; +use serde_json::Value; +use std::path::Path; +use serde_json::json; + + +const FILE_PATH_ADDRESSES: &str = "addresses.json"; + + +use crate::contracts::orderbook::{OrderV1, RouteType}; +use crate::contracts::starknet_utils::StarknetUtilsReader; +use crate::CHAIN_ID_SOLIS; +use tracing::info; + +#[allow(dead_code)] +pub enum CancelStatus { + CancelledUser, + CancelledByNewOrder, + CancelledAssetFault, + CancelledOwnership, +} + +impl CancelStatus { + fn to_u32(&self) -> u32 { + match self { + CancelStatus::CancelledUser => 1, + CancelStatus::CancelledByNewOrder => 2, + CancelStatus::CancelledAssetFault => 3, + CancelStatus::CancelledOwnership => 4, + } + } +} + +struct OwnershipVerifier { + token_address: ContractAddress, + token_id: U256, + current_owner: cainome::cairo_serde::ContractAddress, +} + +struct BalanceVerifier { + currency_address: ContractAddress, + offerer: cainome::cairo_serde::ContractAddress, + start_amount: U256, +} + +abigen!(CallContract, "./artifacts/contract.abi.json"); + +/// Hooker struct, with already instanciated contracts/readers +/// to avoid allocating them at each transaction that is being +/// verified. +pub struct SolisHooker { + // Solis interacts with the orderbook only via `L1HandlerTransaction`. Only the + // address is required. + pub orderbook_address: FieldElement, + // TODO: replace this by the Executor Contract object! + pub sn_executor_address: FieldElement, + pub sn_utils_reader: StarknetUtilsReader

, + sequencer: Option>>, +} + +impl + SolisHooker +{ + /// Verify the ownership of a token + async fn verify_ownership(&self, ownership_verifier: &OwnershipVerifier) -> bool { + let sn_utils_reader_nft_address = StarknetUtilsReader::new( + ownership_verifier.token_address.into(), + self.sn_utils_reader.provider(), + ); + + // check the current owner of the token. + let owner = sn_utils_reader_nft_address + .ownerOf(&ownership_verifier.token_id) + .call() + .await; + + if let Ok(owner_address) = owner { + if owner_address != ownership_verifier.current_owner { + tracing::trace!( + "\nOwner {:?} differs from offerer {:?} ", + owner, + ownership_verifier.current_owner + ); + + println!( + "\nOwner {:?} differs from offerer {:?} ", + owner, ownership_verifier.current_owner + ); + + return false; + } + } + + true + } + + async fn verify_balance(&self, balance_verifier: &BalanceVerifier) -> bool { + info!("HOOKER: Verify Balance"); + + let sn_utils_reader_erc20_address = StarknetUtilsReader::new( + balance_verifier.currency_address.into(), + self.sn_utils_reader.provider(), + ); + let allowance = sn_utils_reader_erc20_address + .allowance(&balance_verifier.offerer, &self.sn_executor_address.into()) + .call() + .await; + + info!( + "HOOKER: Verify Balance allowance {:?}, amount {:?}", + allowance, balance_verifier.start_amount + ); + + if let Ok(allowance) = allowance { + if allowance < balance_verifier.start_amount { + println!( + "\nAllowance {:?} is not enough {:?} for offerer {:?}", + allowance, balance_verifier.start_amount, balance_verifier.offerer + ); + return false; + } + } + + // check the balance + let balance = sn_utils_reader_erc20_address + .balanceOf(&balance_verifier.offerer) + .call() + .await; + if let Ok(balance) = balance { + if balance < balance_verifier.start_amount { + tracing::trace!( + "\nBalance {:?} is not enough {:?} ", + balance, + balance_verifier.start_amount + ); + println!( + "\nBalance {:?} is not enough {:?} ", + balance, balance_verifier.start_amount + ); + return false; + } + } + + true + } + + async fn verify_call(&self, call: &TxCall) -> bool { + let order = match OrderV1::cairo_deserialize(&call.calldata, 0) { + Ok(order) => order, + Err(e) => { + tracing::error!("Fail deserializing OrderV1: {:?}", e); + return false; + } + }; + + // ERC721 to ERC20 + if order.route == RouteType::Erc721ToErc20 { + let token_id = order.token_id.clone().unwrap(); + let n_token_id = U256 { + low: token_id.low, + high: token_id.high, + }; + + let verifier = OwnershipVerifier { + token_address: ContractAddress(order.token_address.into()), + token_id: n_token_id, + current_owner: cainome::cairo_serde::ContractAddress(order.offerer.into()), + }; + + let owner_ship_verification = self.verify_ownership(&verifier).await; + if !owner_ship_verification { + return false; + } + } + + // ERC20 to ERC721 : we check the allowance and the offerer balance. + if order.route == RouteType::Erc20ToErc721 { + if !self + .verify_balance(&BalanceVerifier { + currency_address: ContractAddress(order.currency_address.into()), + offerer: cainome::cairo_serde::ContractAddress(order.offerer.into()), + start_amount: U256 { + low: order.start_amount.low, + high: order.start_amount.high, + }, + }) + .await + { + println!("verify balance for starknet before failed"); + return false; + } + } + return true; + } +} + +impl + SolisHooker +{ + fn get_addresses_from_file() -> Result<(FieldElement, FieldElement), Box> { + let mut file = match File::open(FILE_PATH_ADDRESSES) { + Ok(file) => file, + Err(_) => return Err("File not found".into()), + }; + + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let v: Value = match serde_json::from_str(&contents) { + Ok(value) => value, + Err(_) => return Err("Error parsing JSON".into()), + }; + + let orderbook_address = match v["orderbook_address"].as_str() { + Some(address) => address, + None => return Err("orderbook_address key not found in JSON".into()), + }; + + let sn_executor_address = match v["sn_executor_address"].as_str() { + Some(address) => address, + None => return Err("sn_executor_address key not found in JSON".into()), + }; + + let orderbook_address = match FieldElement::from_hex_be(&orderbook_address[2..]) { + Ok(val) => FieldElement::from_dec_str(&val.to_string()), + Err(_) => return Err("Failed to parse orderbook_address".into()), + }; + + let sn_executor_address = match FieldElement::from_hex_be(&sn_executor_address[2..]) { + Ok(val) => FieldElement::from_dec_str(&val.to_string()), + Err(_) => return Err("Failed to parse sn_executor_address".into()), + }; + + println!("Addresses loaded from file: {:?}, {:?}", orderbook_address, sn_executor_address); + Ok((orderbook_address?, sn_executor_address?)) + } + + /// Initializes a new instance. + pub fn new( + sn_utils_reader: StarknetUtilsReader

, + orderbook_address: FieldElement, + sn_executor_address: FieldElement, + ) -> Self { + let (orderbook_address, sn_executor_address) = if orderbook_address == FieldElement::ZERO && sn_executor_address == FieldElement::ZERO { + match Self::get_addresses_from_file() { + Ok((orderbook, executor)) => (orderbook, executor), + Err(e) => { + eprintln!("Error reading addresses from file: {}", e); + (orderbook_address, sn_executor_address) + } + } + } else { + (orderbook_address, sn_executor_address) + }; + + Self { + orderbook_address, + sn_utils_reader, + sn_executor_address, + sequencer: None, + } + } + + /// Retrieves a reference to the sequencer. + #[allow(dead_code)] + pub fn sequencer_ref(&self) -> &Arc> { + // The expect is used here as it must always be set by Katana core. + // If not set, the merge on Katana may be revised. + self.sequencer + .as_ref() + .expect("Sequencer must be set to get a reference to it") + } + + /// Adds a `L1HandlerTransaction` to the transaction pool that is directed to the + /// orderbook only. + /// `L1HandlerTransaction` is a special type of transaction that can only be + /// sent by the sequencer itself. This transaction is not validated by any account. + /// + /// In the case of Solis, `L1HandlerTransaction` are sent by Solis for two purposes: + /// 1. A message was collected from the L2, and it must be executed. + /// 2. A transaction has been rejected by Solis (asset faults), and the order + /// must then be updated. + /// + /// This function is used for the scenario 2. For this reason, the `from_address` + /// field is automatically filled up by the sequencer to use the executor address + /// deployed on L2 to avoid any attack by other contracts. + /// + /// # Arguments + /// + /// * `selector` - The selector of the recipient contract to execute. + /// * `payload` - The payload of the message. + #[allow(dead_code)] + pub fn add_l1_handler_transaction_for_orderbook( + &self, + selector: FieldElement, + payload: &[FieldElement], + ) { + let to_address = self.orderbook_address; + let from_address = self.sn_executor_address; + let chain_id = ChainId::Id(CHAIN_ID_SOLIS); + + // The nonce is normally used by the messaging contract on Starknet. But in the + // case of those transaction, as they are only sent by Solis itself, we use 0. + // TODO: this value of 0 must be checked by the `l1_handler` function. + let nonce = FieldElement::ZERO; + + // The calldata always starts with the from_address. + let mut calldata: Vec = vec![from_address]; + for p in payload.into_iter() { + calldata.push(*p); + } + + let message_hash = compute_l1_message_hash(from_address, to_address, payload); + + let tx = L1HandlerTx { + nonce, + chain_id, + paid_fee_on_l1: 30000_u128, + version: FieldElement::ZERO, + message_hash, + calldata, + contract_address: ContractAddress(to_address), + entry_point_selector: selector, + }; + + if let Some(seq) = &self.sequencer { + let exe = ExecutableTxWithHash::new_query(ExecutableTx::L1Handler(tx), false); + seq.add_transaction_to_pool(exe); + } + } +} + +/// Solis hooker relies on verifiers to inspect and verify +/// the transaction and starknet state before acceptance. +#[async_trait] +impl KatanaHooker + for SolisHooker +{ + fn set_sequencer(&mut self, sequencer: Arc>) { + self.sequencer = Some(sequencer); + } + + fn set_addresses(&mut self, addresses: HookerAddresses) { + info!("HOOKER: Addresses set for hooker: {:?}", addresses); + self.orderbook_address = addresses.orderbook_arkchain; + self.sn_executor_address = addresses.executor_starknet; + + let path = Path::new(FILE_PATH_ADDRESSES); + let file = OpenOptions::new() + .write(true) + .create(true) + .open(&path); + + match file { + Ok(mut file) => { + let data = json!({ + "orderbook_address": format!("{:#x}", self.orderbook_address), + "sn_executor_address": format!("{:#x}", self.sn_executor_address) + }); + + if let Err(e) = writeln!(file, "{}", data.to_string()) { + eprintln!("Error writing file : {}", e); + } + } + Err(e) => { + eprintln!("Error opening file : {}", e); + } + } + } + + /// Verifies if the message is directed to the orderbook and comes from + /// the executor contract on L2. + /// + /// Currently, only the `from` and `to` are checked. + /// More checks may be added on the selector, and the data. + async fn verify_message_to_appchain( + &self, + from: FieldElement, + to: FieldElement, + _selector: FieldElement, + ) -> bool { + info!( + "HOOKER: verify_message_to_appchain called with from: {:?}, to: {:?}, {:?}, {:?}", + from, to, self.sn_executor_address, self.orderbook_address + ); + // For now, only the from/to are checked. + from == self.sn_executor_address && to == self.orderbook_address + } + + /// Verifies an invoke transaction that is: + /// 1. Directed to the orderbook only. + /// 2. With the selector `create_order` only as the fulfill + /// is verified by `verify_message_to_starknet_before_tx`. + async fn verify_invoke_tx_before_pool( + &self, + transaction: BroadcastedInvokeTransaction, + ) -> bool { + info!( + "HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", + transaction + ); + + let calldata = match transaction { + BroadcastedInvokeTransaction::V1(v1_transaction) => v1_transaction.calldata, + BroadcastedInvokeTransaction::V3(v3_transaction) => v3_transaction.calldata, + }; + info!( + "HOOKER: cairo_deserialize called with transaction: {:?}", + calldata + ); + + let calls = match Vec::::cairo_deserialize(&calldata, 0) { + Ok(calls) => calls, + Err(e) => { + tracing::error!("Fail deserializing OrderV1: {:?}", e); + return false; + } + }; + + for call in calls { + if call.selector != selector!("create_order") + && call.selector != selector!("create_order_from_l2") + && call.selector != selector!("fulfill_order_from_l2") + { + continue; + } + + if !self.verify_call(&call).await { + return false; + } + + // TODO: check assets on starknet. + // TODO: if not valid, in some cases we want to send L1HandlerTransaction + // to change the status of the order. (entrypoint to be written). + } + true + } + + async fn verify_tx_for_starknet(&self, call: Call) -> bool { + println!("verify message to starknet before tx: {:?}", call); + if call.selector != selector!("fulfill_order") { + return true; + } + + let execution_info = match ExecutionInfo::cairo_deserialize(&call.calldata, 0) { + Ok(execution_info) => execution_info, + Err(e) => { + tracing::error!("Fail deserializing ExecutionInfo: {:?}", e); + return false; + } + }; + + let verifier = OwnershipVerifier { + token_address: ContractAddress(execution_info.nft_address.into()), + token_id: execution_info.nft_token_id, + current_owner: cainome::cairo_serde::ContractAddress(execution_info.nft_from.into()), + }; + + let owner_ship_verification = self.verify_ownership(&verifier).await; + if !owner_ship_verification { + // rollback the status + let status = CancelStatus::CancelledOwnership; + + self.add_l1_handler_transaction_for_orderbook( + selector!("rollback_status_order"), + &[execution_info.order_hash, status.to_u32().into()], + ); + return false; + } + + if !self + .verify_balance(&BalanceVerifier { + currency_address: ContractAddress(execution_info.payment_currency_address.into()), + offerer: cainome::cairo_serde::ContractAddress(execution_info.nft_to.into()), + start_amount: U256 { + low: execution_info.payment_amount.low, + high: execution_info.payment_amount.high, + }, + }) + .await + { + // rollback the status + let status = CancelStatus::CancelledAssetFault; + + self.add_l1_handler_transaction_for_orderbook( + selector!("rollback_status_order"), + &[execution_info.order_hash, status.to_u32().into()], + ); + return false; + } + + true + } + + async fn on_starknet_tx_failed(&self, call: Call) { + println!("Starknet tx failed: {:?}", call); + + let execution_info = match ExecutionInfo::cairo_deserialize(&call.calldata, 0) { + Ok(execution_info) => execution_info, + Err(e) => { + tracing::error!("Fail deserializing ExecutionInfo: {:?}", e); + return; + } + }; + + // rollback the status + self.add_l1_handler_transaction_for_orderbook( + selector!("rollback_status_order"), + &[execution_info.order_hash], + ); + } +} + +#[cfg(test)] +mod test { + use super::*; + use starknet::macros::{felt, selector}; + + #[test] + fn test_calldata_calls_parsing_new_encoding() { + // Calldata for a transaction to starkgate: + // Tx hash Goerli: 0x78140a4777bdf508feec62485c2d49b90b8346875c19470790935bcfbb9594 + let data = vec![ + FieldElement::ONE, + // to (starkgate). + felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + // selector (transfert). + felt!("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"), + // data offset. + felt!("0x0"), + // data len. + felt!("0x0"), + // Calldata len. + FieldElement::THREE, + felt!("0x06cdcce7333a7143ad0aebbaffe54a809cc53b65c0936ecfbebaecc0de099e8e"), + felt!("0x071afd498d0000"), + felt!("0x00"), + ]; + + let calls = match Vec::::cairo_deserialize(&data, 0) { + Ok(calls) => calls, + Err(e) => { + tracing::error!("Fail deserializing OrderV1: {:?}", e); + Vec::new() + } + }; + + assert_eq!(calls.len(), 1); + assert_eq!( + calls[0].to, + felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7").into() + ); + assert_eq!(calls[0].selector, selector!("transfer")); + } +} diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs index 07c48d1d14..ce806b1e34 100644 --- a/bin/solis/src/main.rs +++ b/bin/solis/src/main.rs @@ -2,13 +2,14 @@ use std::io; use std::net::SocketAddr; use std::sync::Arc; +use crate::hooker::SolisHooker; use clap::{CommandFactory, Parser}; use clap_complete::{generate, Shell}; use console::Style; use dojo_metrics::{metrics_process, prometheus_exporter}; use katana_core::constants::MAX_RECURSION_DEPTH; use katana_core::env::get_default_vm_resource_fee_cost; -use katana_core::hooker::{DefaultKatanaHooker, KatanaHooker}; +use katana_core::hooker::KatanaHooker; use katana_core::sequencer::KatanaSequencer; use katana_executor::SimulationFlag; use katana_primitives::class::ClassHash; @@ -17,12 +18,23 @@ use katana_primitives::env::{CfgEnv, FeeTokenAddressses}; use katana_primitives::genesis::allocation::GenesisAccountAlloc; use katana_primitives::genesis::Genesis; use katana_rpc::{spawn, NodeHandle}; +use starknet::core::types::FieldElement; use tokio::signal::ctrl_c; use tokio::sync::RwLock as AsyncRwLock; use tracing::info; mod args; mod utils; +mod hooker; +mod contracts; + +// Chain ID: 'SOLIS' cairo short string. +pub const CHAIN_ID_SOLIS: FieldElement = FieldElement::from_mont([ + 18446732623703627169, + 18446744073709551615, + 18446744073709551615, + 576266102202707888, +]); use args::Commands::Completions; use args::KatanaArgs; @@ -95,18 +107,35 @@ async fn main() -> Result<(), Box> { .await?; } - // Create a default hooker instance - // Create a default hooker instance - // Create a default hooker instance - let hooker: Arc + Send + Sync>> = - Arc::new(AsyncRwLock::new(DefaultKatanaHooker::new())); + // ** SOLIS + let sn_utils_reader = contracts::starknet_utils::new_starknet_utils_reader( + FieldElement::ZERO, + &sequencer_config.messaging.clone().unwrap().rpc_url, + ); + + let executor_address = FieldElement::ZERO; + let orderbook_address = FieldElement::ZERO; + + let hooker:Arc + Send + Sync>> = + Arc::new(AsyncRwLock::new(SolisHooker::new( + sn_utils_reader, + orderbook_address, + executor_address, + ))); + // ** let sequencer = Arc::new( - KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, Some(hooker)) + KatanaSequencer::new(executor_factory, sequencer_config, starknet_config, Some(hooker.clone())) .await?, ); let NodeHandle { addr, handle, .. } = spawn(Arc::clone(&sequencer), server_config).await?; + // ** SOLIS + // Important to set the sequencer reference in the hooker, to allow the hooker + // to send `L1HandlerTransaction` to the orderbook. + hooker.write().await.set_sequencer(sequencer.clone()); + // ** + if !args.silent { let genesis = &sequencer.backend().config.genesis; print_intro(&args, genesis, addr); @@ -152,7 +181,7 @@ fn print_intro(args: &KatanaArgs, genesis: &Genesis, address: SocketAddr) { ██╔══██║██╔══██╗██╔═██╗░██╔═══╝░██╔══██╗██║░░██║██╗░░██║██╔══╝░░██║░░██╗░░░██║░░░ ██║░░██║██║░░██║██║░╚██╗██║░░░░░██║░░██║╚█████╔╝╚█████╔╝███████╗╚█████╔╝░░░██║░░░ ╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░╚═╝░╚════╝░░╚════╝░╚══════╝░╚════╝░░░░╚═╝░░░ - + ░██████╗░█████╗░██╗░░░░░██╗░██████╗ ██╔════╝██╔══██╗██║░░░░░██║██╔════╝ ╚█████╗░██║░░██║██║░░░░░██║╚█████╗░ From 3bcc9faec453be4e89e74497346e961f96eb118b Mon Sep 17 00:00:00 2001 From: bexan Date: Mon, 10 Jun 2024 12:24:39 +0200 Subject: [PATCH 21/31] katana: add service & starknet update --- .../core/src/service/messaging/service.rs | 81 ++++++++++------- .../core/src/service/messaging/starknet.rs | 90 ++++++++++++------- 2 files changed, 105 insertions(+), 66 deletions(-) diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index dd3090825c..3bed0e18ea 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -120,6 +120,7 @@ impl MessagingService { backend: Arc>, messenger: Arc>, ) -> MessengerResult> { + // Retrieve messages to be sent from the local blockchain for the given block number let Some(messages) = ReceiptProvider::receipts_by_block( backend.blockchain.provider(), BlockHashOrNumber::Num(block_num), @@ -130,23 +131,43 @@ impl MessagingService { }; if messages.is_empty() { - Ok(Some((block_num, 0))) - } else { - match messenger.as_ref() { - MessengerMode::Ethereum(inner) => { - let hashes = inner.send_messages(&messages).await.map(|hashes| { - hashes.iter().map(|h| format!("{h:#x}")).collect::>() - })?; - trace_msg_to_l1_sent(&messages, &hashes); - Ok(Some((block_num, hashes.len()))) - } + info!(target: LOG_TARGET, "No messages to send from block {}", block_num); + return Ok(Some((block_num, 0))); + } - MessengerMode::Starknet(inner) => { - let hashes = inner.send_messages(&messages).await.map(|hashes| { - hashes.iter().map(|h| format!("{h:#x}")).collect::>() - })?; - trace_msg_to_l1_sent(&messages, &hashes); - Ok(Some((block_num, hashes.len()))) + info!(target: LOG_TARGET, "Retrieved {} messages from block {}", messages.len(), block_num); + + match messenger.as_ref() { + MessengerMode::Ethereum(inner) => { + match inner.send_messages(&messages).await { + Ok(hashes) => { + let hash_strings: Vec = + hashes.iter().map(|h| format!("{:#x}", h)).collect(); + trace_msg_to_l1_sent(&messages, &hash_strings); + info!(target: LOG_TARGET, "Successfully sent {} messages from block {}", hash_strings.len(), block_num); + Ok(Some((block_num, hash_strings.len()))) + } + Err(e) => { + error!(target: LOG_TARGET, error = %e, "Error sending messages from block {}", block_num); + // Even if there's an error, we should move to the next block to avoid infinite retries + Ok(Some((block_num, 0))) // Marking as processed to avoid retries + } + } + } + MessengerMode::Starknet(inner) => { + match inner.send_messages(&messages).await { + Ok(hashes) => { + let hash_strings: Vec = + hashes.iter().map(|h| format!("{:#x}", h)).collect(); + trace_msg_to_l1_sent(&messages, &hash_strings); + info!(target: LOG_TARGET, "Successfully sent {} messages from block {}", hash_strings.len(), block_num); + Ok(Some((block_num, hash_strings.len()))) + } + Err(e) => { + error!(target: LOG_TARGET, error = %e, "Error sending messages from block {}", block_num); + // Even if there's an error, we should move to the next block to avoid infinite retries + Ok(Some((block_num, 0))) // Marking as processed to avoid retries + } } } } @@ -192,15 +213,16 @@ impl Stream for MessagingService { pin.send_from_block, pin.backend.clone(), pin.messenger.clone(), - ))) + ))); } } } - // Poll the gathering future. + // Poll the gathering future if let Some(mut gather_fut) = pin.msg_gather_fut.take() { match gather_fut.poll_unpin(cx) { Poll::Ready(Ok((last_block, msg_count))) => { + info!(target: LOG_TARGET, "Gathered {} transactions up to block {}", msg_count, last_block); pin.gather_from_block = last_block + 1; return Poll::Ready(Some(MessagingOutcome::Gather { lastest_block: last_block, @@ -208,34 +230,25 @@ impl Stream for MessagingService { })); } Poll::Ready(Err(e)) => { - error!( - target: LOG_TARGET, - block = %pin.gather_from_block, - error = %e, - "Gathering messages for block." - ); + error!(target: LOG_TARGET, block = %pin.gather_from_block, error = %e, "Error gathering messages for block."); return Poll::Pending; } Poll::Pending => pin.msg_gather_fut = Some(gather_fut), } } - // Poll the message sending future. + // Poll the message sending future if let Some(mut send_fut) = pin.msg_send_fut.take() { match send_fut.poll_unpin(cx) { Poll::Ready(Ok(Some((block_num, msg_count)))) => { - // +1 to move to the next local block to check messages to be - // sent on the settlement chain. - pin.send_from_block += 1; + info!(target: LOG_TARGET, "Sent {} messages from block {}", msg_count, block_num); + pin.send_from_block = block_num + 1; return Poll::Ready(Some(MessagingOutcome::Send { block_num, msg_count })); } Poll::Ready(Err(e)) => { - error!( - target: LOG_TARGET, - block = %pin.send_from_block, - error = %e, - "Settling messages for block." - ); + error!(target: LOG_TARGET, block = %pin.send_from_block, error = %e, "Error sending messages for block."); + // Even if there's an error, we should move to the next block to avoid infinite retries + pin.send_from_block += 1; return Poll::Pending; } Poll::Ready(_) => return Poll::Pending, diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index b1cf4457bf..786db7ecfa 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -107,22 +107,22 @@ impl StarknetMessaging { } if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { - // if let Ok((from, to, selector)) = info_from_event(&event) { - // let hooker = Arc::clone(&self.hooker); - // let is_message_accepted = hooker - // .read() - // .await - // .verify_message_to_appchain(from, to, selector) - // .await; - // if is_message_accepted { - debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); - l1_handler_txs.push(tx); - let mut cache = self.event_cache.write().await; - cache.insert(event_id); - // } else { - // debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); - // } - // } + if let Ok((from, to, selector)) = info_from_event(&event) { + let hooker = Arc::clone(&self.hooker); + let is_message_accepted = hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; + if is_message_accepted { + debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); + l1_handler_txs.push(tx); + let mut cache = self.event_cache.write().await; + cache.insert(event_id); + } else { + debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); + } + } } } @@ -139,7 +139,6 @@ impl StarknetMessaging { async fn send_invoke_tx(&self, calls: Vec) -> Result { let signer = Arc::new(&self.wallet); - let mut account = SingleOwnerAccount::new( &self.provider, signer, @@ -148,13 +147,23 @@ impl StarknetMessaging { ExecutionEncoding::New, ); + info!(target: LOG_TARGET, "Setting block ID to Pending."); account.set_block_id(BlockId::Tag(BlockTag::Pending)); let execution = account.execute(calls).fee_estimate_multiplier(10f64); - let estimated_fee = (execution.estimate_fee().await?.overall_fee) * 10u64.into(); - let execution_with_fee = execution.max_fee(estimated_fee); + let estimated_fee = match execution.estimate_fee().await { + Ok(fee) => { + info!(target: LOG_TARGET, "Estimated fee: {:?}", fee.overall_fee); + (fee.overall_fee) * 10u64.into() + } + Err(e) => { + error!(target: LOG_TARGET, "Error estimating fee: {:?}", e); + return Err(e.into()); + } + }; - info!(target: LOG_TARGET, "Sending invoke transaction."); + let execution_with_fee = execution.max_fee(estimated_fee); + info!(target: LOG_TARGET, "Sending invoke transaction with max fee: {:?}", estimated_fee); match execution_with_fee.send().await { Ok(tx) => { @@ -176,20 +185,23 @@ impl StarknetMessaging { return Ok(FieldElement::ZERO); } - let mut calldata = hashes; + info!(target: LOG_TARGET, "Preparing to send {} hashes.", hashes.len()); + + let mut calldata = hashes.clone(); calldata.insert(0, calldata.len().into()); let call = Call { selector: selector!("add_messages_hashes_from_appchain"), to: self.messaging_contract_address, - calldata, + calldata: calldata.clone(), }; - info!(target: LOG_TARGET, "Sending hashes to Starknet."); + info!(target: LOG_TARGET, "Sending hashes to Starknet: {:?}", calldata); match self.send_invoke_tx(vec![call]).await { Ok(tx_hash) => { trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Hashes sending transaction."); + info!(target: LOG_TARGET, "Successfully sent hashes with transaction hash: {:#064x}", tx_hash); Ok(tx_hash) } Err(e) => { @@ -246,23 +258,37 @@ impl Messenger for StarknetM messages: &[MessageToL1], ) -> MessengerResult> { if messages.is_empty() { + info!(target: LOG_TARGET, "No messages to send."); return Ok(vec![]); } let (hashes, calls) = parse_messages(messages)?; + for call in &calls { + if !self.hooker.read().await.verify_tx_for_starknet(call.clone()).await { + warn!(target: LOG_TARGET, "Call verification failed for call: {:?}", call); + continue; + } + } if !calls.is_empty() { - match self.send_invoke_tx(calls).await { - Ok(tx_hash) => { - trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); + info!(target: LOG_TARGET, "Sending {} calls.", calls.len()); + if let Err(e) = self.send_invoke_tx(calls.clone()).await { + error!(target: LOG_TARGET, error = %e, "Error sending invoke transaction."); + for call in calls { + self.hooker.read().await.on_starknet_tx_failed(call).await; } - Err(e) => { - error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); - return Err(Error::SendError); - } - }; + return Err(Error::SendError); + } + info!(target: LOG_TARGET, "Successfully sent invoke transaction."); } - self.send_hashes(hashes.clone()).await?; + + if let Err(e) = self.send_hashes(hashes.clone()).await { + error!(target: LOG_TARGET, error = %e, "Error sending hashes."); + return Err(Error::SendError); + } + info!(target: LOG_TARGET, "Successfully sent hashes."); + + info!(target: LOG_TARGET, "Finished sending messages."); Ok(hashes) } } From 8647b8e67b4283ac3a90c517c6f3f9196421e492 Mon Sep 17 00:00:00 2001 From: bexan Date: Mon, 10 Jun 2024 15:32:18 +0200 Subject: [PATCH 22/31] starknet messaging: update log --- crates/katana/core/src/service/messaging/starknet.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 786db7ecfa..6375f78227 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -120,7 +120,13 @@ impl StarknetMessaging { let mut cache = self.event_cache.write().await; cache.insert(event_id); } else { - debug!(target: LOG_TARGET, "Event ID: {} not accepted by hooker", event_id); + debug!( + target: LOG_TARGET, + "Event ID: {} not accepted by hooker, check the contract addresses defined in the hooker: executor address: {:?}, orderbook address: {:?}", + event_id, + from, + to + ); } } } From a8047fc5db510ae46eef0686c1247c3fde020fb0 Mon Sep 17 00:00:00 2001 From: bexan Date: Mon, 10 Jun 2024 15:48:16 +0200 Subject: [PATCH 23/31] solis: add user & password --- bin/solis/.env.example | 2 ++ bin/solis/src/args.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 bin/solis/.env.example diff --git a/bin/solis/.env.example b/bin/solis/.env.example new file mode 100644 index 0000000000..eba6147747 --- /dev/null +++ b/bin/solis/.env.example @@ -0,0 +1,2 @@ +RPC_USER=user +RPC_PASSWORD=password diff --git a/bin/solis/src/args.rs b/bin/solis/src/args.rs index f603c87b41..f2fb8b6b01 100644 --- a/bin/solis/src/args.rs +++ b/bin/solis/src/args.rs @@ -236,14 +236,18 @@ impl KatanaArgs { apis.push(ApiKind::Dev); } + let rpc_user = env::var("RPC_USER").unwrap_or_else(|_| "userDefault".to_string()); + let rpc_password = + env::var("RPC_PASSWORD").unwrap_or_else(|_| "passwordDefault".to_string()); + ServerConfig { apis, port: self.server.port, host: self.server.host.clone().unwrap_or("0.0.0.0".into()), max_connections: self.server.max_connections, allowed_origins: self.server.allowed_origins.clone(), - rpc_user: "user".into(), - rpc_password: "password".into(), + rpc_user, + rpc_password, } } From 1be79f6a3defd9ef66ec0d2c24b8eeb83dde8174 Mon Sep 17 00:00:00 2001 From: bexan Date: Mon, 10 Jun 2024 15:49:32 +0200 Subject: [PATCH 24/31] solis: update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6498958892..f65de7119d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ crates/benches/bench_results.txt **/generated .vscode bindings +./bin/solis/.env From de1c6644265019c51b6053932ab042336bd658ea Mon Sep 17 00:00:00 2001 From: bexan Date: Thu, 22 Aug 2024 11:37:15 +0200 Subject: [PATCH 25/31] fix(solis): auth solis (#19) --- Cargo.lock | 7 +++++++ bin/solis/Cargo.toml | 1 + bin/solis/src/args.rs | 2 +- bin/solis/src/main.rs | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9f0bcd44a0..ace78f9245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3863,6 +3863,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dotenvy" version = "0.15.7" @@ -11403,6 +11409,7 @@ dependencies = [ "common", "console", "dojo-metrics", + "dotenv", "katana-core", "katana-executor", "katana-primitives", diff --git a/bin/solis/Cargo.toml b/bin/solis/Cargo.toml index 5b8debb1c2..0a0c5a07d9 100644 --- a/bin/solis/Cargo.toml +++ b/bin/solis/Cargo.toml @@ -19,6 +19,7 @@ clap_complete.workspace = true common.workspace = true console.workspace = true dojo-metrics.workspace = true +dotenv = "0.15.0" katana-core.workspace = true katana-executor.workspace = true katana-primitives.workspace = true diff --git a/bin/solis/src/args.rs b/bin/solis/src/args.rs index f2fb8b6b01..9fe912a31d 100644 --- a/bin/solis/src/args.rs +++ b/bin/solis/src/args.rs @@ -9,7 +9,7 @@ //! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) //! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) //! for more info. - +use std::env; use std::net::SocketAddr; use std::path::PathBuf; diff --git a/bin/solis/src/main.rs b/bin/solis/src/main.rs index ce806b1e34..14fca64404 100644 --- a/bin/solis/src/main.rs +++ b/bin/solis/src/main.rs @@ -43,6 +43,8 @@ pub(crate) const LOG_TARGET: &str = "katana::cli"; #[tokio::main] async fn main() -> Result<(), Box> { + dotenv::dotenv().ok(); + let args = KatanaArgs::parse(); args.init_logging()?; From a4b4276ab4705240375ee8daafa07e50b8a1a720 Mon Sep 17 00:00:00 2001 From: bexan Date: Thu, 22 Aug 2024 11:37:57 +0200 Subject: [PATCH 26/31] feat(katana): prevent sequencer to treat all block from start (#22) * fix(solis): auth solis * update send_from_block * feat(katana): prevent sequencer from sending all block multiple times * starknet messenging: gather message & update gather from in config file --- .../katana/core/src/service/messaging/mod.rs | 31 ++++++- .../core/src/service/messaging/service.rs | 18 +++- .../core/src/service/messaging/starknet.rs | 90 ++++++++++++++++++- 3 files changed, 128 insertions(+), 11 deletions(-) diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index 259cb483f1..b7b7b8a193 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -41,6 +41,8 @@ mod service; mod starknet; use std::path::Path; +use std::fs::File; +use std::io::Write; use ::starknet::providers::ProviderError as StarknetProviderError; use alloy_transport::TransportError; @@ -49,7 +51,7 @@ use async_trait::async_trait; use ethereum::EthereumMessaging; 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}; @@ -90,7 +92,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, @@ -107,7 +109,11 @@ pub struct MessagingConfig { /// from/to the settlement chain. pub interval: u64, /// The block on settlement chain from where Katana will start fetching messages. - pub from_block: u64, + pub gather_from_block: u64, + /// The block from where sequencer wil start sending messages. + pub send_from_block: u64, + /// Path to the config file. + pub config_file: String, } impl MessagingConfig { @@ -119,7 +125,24 @@ impl MessagingConfig { /// This is used as the clap `value_parser` implementation pub fn parse(path: &str) -> Result { - Self::load(path).map_err(|e| e.to_string()) + let mut config = Self::load(path).map_err(|e| e.to_string())?; + config.config_file = path.to_string(); + config.save().map_err(|e| e.to_string())?; + Ok(config) + } + + /// Save the config to a JSON file. + pub fn save(&self) -> Result<(), std::io::Error> { + if self.config_file.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Config file path is empty", + )); + } + let json = serde_json::to_string_pretty(self)?; + let mut file = File::create(&self.config_file)?; + file.write_all(json.as_bytes())?; + Ok(()) } } diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 3bed0e18ea..86333cac11 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -9,7 +9,7 @@ use katana_primitives::transaction::{ExecutableTxWithHash, L1HandlerTx, TxHash}; use katana_provider::traits::block::BlockNumberProvider; use katana_provider::traits::transaction::ReceiptProvider; use std::pin::Pin; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::task::{Context, Poll}; use std::time::Duration; use tokio::time::{interval_at, Instant, Interval}; @@ -38,6 +38,8 @@ pub struct MessagingService { send_from_block: u64, /// The message sending future. msg_send_fut: Option, + /// The messaging configuration. + messaging_config: Arc>, } impl MessagingService { @@ -49,9 +51,10 @@ impl MessagingService { backend: Arc>, hooker: Arc + Send + Sync>>, ) -> anyhow::Result { - let gather_from_block = config.from_block; + let gather_from_block = config.gather_from_block; + let send_from_block = config.send_from_block; let interval = interval_from_seconds(config.interval); - let messenger = match MessengerMode::from_config(config, hooker).await { + let messenger = match MessengerMode::from_config(config.clone(), hooker).await { Ok(m) => Arc::new(m), Err(_) => { panic!( @@ -67,9 +70,10 @@ impl MessagingService { interval, messenger, gather_from_block, - send_from_block: 0, + send_from_block, msg_gather_fut: None, msg_send_fut: None, + messaging_config: Arc::new(RwLock::new(config)), }) } @@ -218,12 +222,15 @@ impl Stream for MessagingService { } } + let mut messaging_config = pin.messaging_config.write().unwrap(); // Poll the gathering future if let Some(mut gather_fut) = pin.msg_gather_fut.take() { match gather_fut.poll_unpin(cx) { Poll::Ready(Ok((last_block, msg_count))) => { info!(target: LOG_TARGET, "Gathered {} transactions up to block {}", msg_count, last_block); pin.gather_from_block = last_block + 1; + messaging_config.gather_from_block = pin.gather_from_block; + let _ = messaging_config.save(); return Poll::Ready(Some(MessagingOutcome::Gather { lastest_block: last_block, msg_count, @@ -243,6 +250,9 @@ impl Stream for MessagingService { Poll::Ready(Ok(Some((block_num, msg_count)))) => { info!(target: LOG_TARGET, "Sent {} messages from block {}", msg_count, block_num); pin.send_from_block = block_num + 1; + // update the config with the latest block number sent. + messaging_config.send_from_block = pin.send_from_block; + let _ = messaging_config.save(); return Poll::Ready(Some(MessagingOutcome::Send { block_num, msg_count })); } Poll::Ready(Err(e)) => { diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 6375f78227..4854722728 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use crate::hooker::KatanaHooker; use anyhow::Result; use async_trait::async_trait; @@ -69,6 +70,52 @@ impl StarknetMessaging { }) } + /// Fetches events for the given blocks range. + pub async fn fetch_events( + &self, + from_block: BlockId, + to_block: BlockId, + ) -> Result>> { + trace!(target: LOG_TARGET, from_block = ?from_block, to_block = ?to_block, "Fetching logs."); + + let mut block_to_events: HashMap> = HashMap::new(); + + let filter = EventFilter { + from_block: Some(from_block), + to_block: Some(to_block), + address: Some(self.messaging_contract_address), + // TODO: this might come from the configuration actually. + keys: None, + }; + + // TODO: this chunk_size may also come from configuration? + let chunk_size = 200; + let mut continuation_token: Option = None; + + loop { + let event_page = + self.provider.get_events(filter.clone(), continuation_token, chunk_size).await?; + + event_page.events.into_iter().for_each(|event| { + // We ignore events without the block number + if let Some(block_number) = event.block_number { + block_to_events + .entry(block_number) + .and_modify(|v| v.push(event.clone())) + .or_insert(vec![event]); + } + }); + + continuation_token = event_page.continuation_token; + + if continuation_token.is_none() { + break; + } + } + + Ok(block_to_events) + } + async fn fetch_pending_events(&self, chain_id: ChainId) -> MessengerResult> { let mut l1_handler_txs: Vec = vec![]; let mut continuation_token: Option = None; @@ -245,6 +292,41 @@ impl Messenger for StarknetM } }; + if from_block > chain_latest_block { + // Nothing to fetch, we can skip waiting the next tick. + return Ok((chain_latest_block, vec![])); + } + + // +1 as the from_block counts as 1 block fetched. + let to_block = if from_block + max_blocks + 1 < chain_latest_block { + from_block + max_blocks + } else { + chain_latest_block + }; + + let mut l1_handler_txs: Vec = vec![]; + + // fetch events for the given range before fetching pending events + self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) + .await + .map_err(|_| Error::SendError) + .unwrap() + .iter() + .for_each(|(block_number, block_events)| { + debug!( + target: LOG_TARGET, + block_number = %block_number, + events_count = %block_events.len(), + "Converting events of block into L1HandlerTx." + ); + + block_events.iter().for_each(|e| { + if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { + l1_handler_txs.push(tx) + } + }) + }); + // Check if the block number has changed let previous_block = self.latest_block.load(Ordering::Relaxed); if previous_block != chain_latest_block { @@ -252,11 +334,13 @@ impl Messenger for StarknetM self.event_cache.write().await.clear(); self.latest_block.store(chain_latest_block, Ordering::Relaxed); } - + // Fetch pending events let pending_txs = self.fetch_pending_events(chain_id).await?; - debug!(target: LOG_TARGET, "Returning {} pending transactions", pending_txs.len()); + // Add pending events to the list + l1_handler_txs.extend(pending_txs); - Ok((chain_latest_block, pending_txs)) + debug!(target: LOG_TARGET, "Returning {} transactions", l1_handler_txs.len()); + Ok((chain_latest_block, l1_handler_txs)) } async fn send_messages( From cd725f94e40d7d8913b138289bcb4e3de082e61c Mon Sep 17 00:00:00 2001 From: kwiss Date: Tue, 22 Oct 2024 17:56:44 +0200 Subject: [PATCH 27/31] fix: katana message gathering --- .gitignore | 4 + .../core/src/service/messaging/service.rs | 14 +- .../core/src/service/messaging/starknet.rs | 191 +++++++++++------- messaging.local.json | 9 - 4 files changed, 132 insertions(+), 86 deletions(-) delete mode 100644 messaging.local.json diff --git a/.gitignore b/.gitignore index f65de7119d..4632fee075 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ crates/benches/bench_results.txt .vscode bindings ./bin/solis/.env + +messaging.local.json +addresses.json +existing-katana-db \ No newline at end of file diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 86333cac11..b61f8001ab 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -86,7 +86,12 @@ impl MessagingService { // 200 avoids any possible rejection from RPC with possibly lots of messages. // TODO: May this be configurable? let max_block = 200; - + info!( + target: LOG_TARGET, + "Starting gather_messages from block {} with max_block {}", + from_block, + max_block + ); match messenger.as_ref() { MessengerMode::Ethereum(inner) => { let (block_num, txs) = @@ -113,7 +118,12 @@ impl MessagingService { trace_l1_handler_tx_exec(hash, &tx); pool.add_transaction(ExecutableTxWithHash { hash, transaction: tx.into() }) }); - + info!( + target: LOG_TARGET, + "gather_messages finished. Last block: {}, tx count: {}", + block_num, + txs_count + ); Ok((block_num, txs_count)) } } diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 4854722728..8e96155756 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -119,17 +119,17 @@ impl StarknetMessaging { async fn fetch_pending_events(&self, chain_id: ChainId) -> MessengerResult> { let mut l1_handler_txs: Vec = vec![]; let mut continuation_token: Option = None; - + loop { debug!(target: LOG_TARGET, "Fetching pending events with continuation token: {:?}", continuation_token); - + let filter = EventFilter { from_block: Some(BlockId::Tag(BlockTag::Pending)), to_block: Some(BlockId::Tag(BlockTag::Pending)), address: Some(self.messaging_contract_address), keys: None, }; - + let event_page = self .provider .get_events(filter.clone(), continuation_token.clone(), 200) @@ -138,55 +138,47 @@ impl StarknetMessaging { error!(target: LOG_TARGET, "Error fetching pending events: {:?}", e); Error::SendError })?; - - debug!(target: LOG_TARGET, "Fetched {} events", event_page.events.len()); - + + debug!(target: LOG_TARGET, "Fetched {} pending events", event_page.events.len()); + for event in event_page.events { let event_id = event.transaction_hash.to_string(); - debug!(target: LOG_TARGET, "Processing event with ID: {}", event_id); - - { - let cache = self.event_cache.read().await; - if cache.contains(&event_id) { - debug!(target: LOG_TARGET, "Event ID: {} already processed, skipping", event_id); - continue; - } + + // Check if we've already processed this event + let cache = self.event_cache.read().await; + if cache.contains(&event_id) { + debug!(target: LOG_TARGET, "Pending event {} already processed, skipping", event_id); + continue; } - + drop(cache); + if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { if let Ok((from, to, selector)) = info_from_event(&event) { - let hooker = Arc::clone(&self.hooker); - let is_message_accepted = hooker - .read() - .await - .verify_message_to_appchain(from, to, selector) - .await; + let hooker = Arc::clone(&self.hooker); + let is_message_accepted = hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; if is_message_accepted { - debug!(target: LOG_TARGET, "Event ID: {} accepted, adding to transactions", event_id); + debug!(target: LOG_TARGET, "Pending event {} accepted", event_id); l1_handler_txs.push(tx); + + // Add to cache after processing let mut cache = self.event_cache.write().await; cache.insert(event_id); - } else { - debug!( - target: LOG_TARGET, - "Event ID: {} not accepted by hooker, check the contract addresses defined in the hooker: executor address: {:?}, orderbook address: {:?}", - event_id, - from, - to - ); } } } } - + continuation_token = event_page.continuation_token; - if continuation_token.is_none() { break; } } - - debug!(target: LOG_TARGET, "Total transactions gathered: {}", l1_handler_txs.len()); + + debug!(target: LOG_TARGET, "Total pending transactions gathered: {}", l1_handler_txs.len()); Ok(l1_handler_txs) } @@ -276,8 +268,9 @@ impl Messenger for StarknetM max_blocks: u64, chain_id: ChainId, ) -> MessengerResult<(u64, Vec)> { - debug!(target: LOG_TARGET, "Gathering messages"); - + debug!(target: LOG_TARGET, "Starting gather_messages with from_block: {}, max_blocks: {}", from_block, max_blocks); + + // First get the latest block number let chain_latest_block: u64 = match self.provider.block_number().await { Ok(n) => { debug!(target: LOG_TARGET, "Latest block number on chain: {}", n); @@ -291,56 +284,104 @@ impl Messenger for StarknetM return Err(Error::SendError); } }; - + + let mut l1_handler_txs = Vec::new(); + + // First gather pending events + debug!(target: LOG_TARGET, "Fetching pending events first"); + let pending_txs = self.fetch_pending_events(chain_id).await?; + l1_handler_txs.extend(pending_txs); + debug!(target: LOG_TARGET, "Found {} pending transactions", l1_handler_txs.len()); + + // Then check if we need to process blocks if from_block > chain_latest_block { - // Nothing to fetch, we can skip waiting the next tick. - return Ok((chain_latest_block, vec![])); + debug!(target: LOG_TARGET, "from_block ({}) > chain_latest_block ({}), returning with only pending events", from_block, chain_latest_block); + return Ok((chain_latest_block, l1_handler_txs)); } - - // +1 as the from_block counts as 1 block fetched. - let to_block = if from_block + max_blocks + 1 < chain_latest_block { + + // Calculate the block range to process + let to_block = if from_block + max_blocks < chain_latest_block { from_block + max_blocks } else { chain_latest_block }; - - let mut l1_handler_txs: Vec = vec![]; - - // fetch events for the given range before fetching pending events - self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) - .await - .map_err(|_| Error::SendError) - .unwrap() - .iter() - .for_each(|(block_number, block_events)| { - debug!( - target: LOG_TARGET, - block_number = %block_number, - events_count = %block_events.len(), - "Converting events of block into L1HandlerTx." - ); - - block_events.iter().for_each(|e| { - if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { - l1_handler_txs.push(tx) + debug!(target: LOG_TARGET, "Fetching confirmed events from block {} to {}", from_block, to_block); + + // Now fetch events from confirmed blocks + match self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)).await { + Ok(events_by_block) => { + for (block_number, block_events) in events_by_block.iter() { + debug!( + target: LOG_TARGET, + block_number = %block_number, + events_count = %block_events.len(), + "Processing confirmed events for block" + ); + + for event in block_events { + let event_id = event.transaction_hash.to_string(); + + // Check if we've already processed this event + let cache = self.event_cache.read().await; + if cache.contains(&event_id) { + debug!(target: LOG_TARGET, "Event {} already processed, skipping", event_id); + continue; + } + drop(cache); // Release the read lock + + if let Ok(tx) = l1_handler_tx_from_event(event, chain_id) { + if let Ok((from, to, selector)) = info_from_event(event) { + let hooker = Arc::clone(&self.hooker); + let is_message_accepted = hooker + .read() + .await + .verify_message_to_appchain(from, to, selector) + .await; + + if is_message_accepted { + debug!( + target: LOG_TARGET, + "Message from block {} accepted: from {:?} to {:?} with selector {:?}", + block_number, from, to, selector + ); + l1_handler_txs.push(tx); + + // Add to cache after processing + let mut cache = self.event_cache.write().await; + cache.insert(event_id); + } else { + debug!( + target: LOG_TARGET, + "Message from block {} not accepted: from {:?} to {:?} with selector {:?}", + block_number, from, to, selector + ); + } + } + } } - }) - }); - - // Check if the block number has changed + } + } + Err(e) => { + error!(target: LOG_TARGET, "Error fetching confirmed events: {:?}", e); + return Err(Error::SendError); + } + } + + // We only clear the cache if we've moved to a new latest block to avoid reprocessing events let previous_block = self.latest_block.load(Ordering::Relaxed); - if previous_block != chain_latest_block { - debug!(target: LOG_TARGET, "Block number changed from {} to {}, clearing cache", previous_block, chain_latest_block); + if previous_block < chain_latest_block { + debug!(target: LOG_TARGET, "Moving to new latest block {} from {}, clearing cache", chain_latest_block, previous_block); self.event_cache.write().await.clear(); self.latest_block.store(chain_latest_block, Ordering::Relaxed); } - // Fetch pending events - let pending_txs = self.fetch_pending_events(chain_id).await?; - // Add pending events to the list - l1_handler_txs.extend(pending_txs); - - debug!(target: LOG_TARGET, "Returning {} transactions", l1_handler_txs.len()); - Ok((chain_latest_block, l1_handler_txs)) + + info!( + target: LOG_TARGET, + "Total messages gathered: {} (including pending)", + l1_handler_txs.len(), + ); + + Ok((to_block, l1_handler_txs)) } async fn send_messages( diff --git a/messaging.local.json b/messaging.local.json deleted file mode 100644 index 1707e867d9..0000000000 --- a/messaging.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "chain": "starknet", - "rpc_url": "http://0.0.0.0:5050", - "contract_address": "0x3757e03517ea83d7ae5714b0bfea853114aeb4d5186ec8bf7ec73ac09033f18", - "sender_address": "0x2d71e9c974539bb3ffb4b115e66a23d0f62a641ea66c4016e903454c8753bbc", - "private_key": "0x33003003001800009900180300d206308b0070db00121318d17b5e6262150b", - "interval": 2, - "from_block": 0 -} \ No newline at end of file From 702b07ff678e85c7b36a9928324a1ecbcfd3aefa Mon Sep 17 00:00:00 2001 From: Kwiss Date: Fri, 6 Dec 2024 14:39:42 +0000 Subject: [PATCH 28/31] fix: update hooker --- bin/solis/src/hooker.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/solis/src/hooker.rs b/bin/solis/src/hooker.rs index 4068940884..e94ead3c30 100644 --- a/bin/solis/src/hooker.rs +++ b/bin/solis/src/hooker.rs @@ -517,7 +517,7 @@ impl async fn on_starknet_tx_failed(&self, call: Call) { println!("Starknet tx failed: {:?}", call); - + println!("orderhash: {:?}:", execution_info.order_hash) let execution_info = match ExecutionInfo::cairo_deserialize(&call.calldata, 0) { Ok(execution_info) => execution_info, Err(e) => { @@ -527,9 +527,10 @@ impl }; // rollback the status + let status = CancelStatus::CancelledUser; self.add_l1_handler_transaction_for_orderbook( selector!("rollback_status_order"), - &[execution_info.order_hash], + &[execution_info.order_hash, status.to_u32().into()], ); } } From 25864938e7a6c8be73696dcf308b76c511e52232 Mon Sep 17 00:00:00 2001 From: kwiss Date: Fri, 6 Dec 2024 15:43:06 +0100 Subject: [PATCH 29/31] fix: katana update gather message remove pending in favor of latest only --- .../core/src/service/messaging/starknet.rs | 211 +++--------------- 1 file changed, 37 insertions(+), 174 deletions(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 8e96155756..13974b0210 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -75,10 +75,10 @@ impl StarknetMessaging { &self, from_block: BlockId, to_block: BlockId, - ) -> Result>> { + ) -> Result> { trace!(target: LOG_TARGET, from_block = ?from_block, to_block = ?to_block, "Fetching logs."); - let mut block_to_events: HashMap> = HashMap::new(); + let mut events = vec![]; let filter = EventFilter { from_block: Some(from_block), @@ -98,11 +98,10 @@ impl StarknetMessaging { event_page.events.into_iter().for_each(|event| { // We ignore events without the block number - if let Some(block_number) = event.block_number { - block_to_events - .entry(block_number) - .and_modify(|v| v.push(event.clone())) - .or_insert(vec![event]); + if event.block_number.is_some() { + // Blocks are processed in order as retrieved by `get_events`. + // This way we keep the order and ensure the messages are executed in order. + events.push(event); } }); @@ -113,73 +112,7 @@ impl StarknetMessaging { } } - Ok(block_to_events) - } - - async fn fetch_pending_events(&self, chain_id: ChainId) -> MessengerResult> { - let mut l1_handler_txs: Vec = vec![]; - let mut continuation_token: Option = None; - - loop { - debug!(target: LOG_TARGET, "Fetching pending events with continuation token: {:?}", continuation_token); - - let filter = EventFilter { - from_block: Some(BlockId::Tag(BlockTag::Pending)), - to_block: Some(BlockId::Tag(BlockTag::Pending)), - address: Some(self.messaging_contract_address), - keys: None, - }; - - let event_page = self - .provider - .get_events(filter.clone(), continuation_token.clone(), 200) - .await - .map_err(|e| { - error!(target: LOG_TARGET, "Error fetching pending events: {:?}", e); - Error::SendError - })?; - - debug!(target: LOG_TARGET, "Fetched {} pending events", event_page.events.len()); - - for event in event_page.events { - let event_id = event.transaction_hash.to_string(); - - // Check if we've already processed this event - let cache = self.event_cache.read().await; - if cache.contains(&event_id) { - debug!(target: LOG_TARGET, "Pending event {} already processed, skipping", event_id); - continue; - } - drop(cache); - - if let Ok(tx) = l1_handler_tx_from_event(&event, chain_id) { - if let Ok((from, to, selector)) = info_from_event(&event) { - let hooker = Arc::clone(&self.hooker); - let is_message_accepted = hooker - .read() - .await - .verify_message_to_appchain(from, to, selector) - .await; - if is_message_accepted { - debug!(target: LOG_TARGET, "Pending event {} accepted", event_id); - l1_handler_txs.push(tx); - - // Add to cache after processing - let mut cache = self.event_cache.write().await; - cache.insert(event_id); - } - } - } - } - - continuation_token = event_page.continuation_token; - if continuation_token.is_none() { - break; - } - } - - debug!(target: LOG_TARGET, "Total pending transactions gathered: {}", l1_handler_txs.len()); - Ok(l1_handler_txs) + Ok(events) } async fn send_invoke_tx(&self, calls: Vec) -> Result { @@ -267,120 +200,50 @@ impl Messenger for StarknetM from_block: u64, max_blocks: u64, chain_id: ChainId, - ) -> MessengerResult<(u64, Vec)> { - debug!(target: LOG_TARGET, "Starting gather_messages with from_block: {}, max_blocks: {}", from_block, max_blocks); - - // First get the latest block number + ) -> MessengerResult<(u64, Vec)> { let chain_latest_block: u64 = match self.provider.block_number().await { - Ok(n) => { - debug!(target: LOG_TARGET, "Latest block number on chain: {}", n); - n - } - Err(e) => { + Ok(n) => n, + Err(_) => { warn!( target: LOG_TARGET, - "Couldn't fetch settlement chain last block number. Skipped, retry at the next tick. Error: {:?}", e + "Couldn't fetch settlement chain last block number. \nSkipped, retry at the \ + next tick." ); return Err(Error::SendError); } }; - - let mut l1_handler_txs = Vec::new(); - - // First gather pending events - debug!(target: LOG_TARGET, "Fetching pending events first"); - let pending_txs = self.fetch_pending_events(chain_id).await?; - l1_handler_txs.extend(pending_txs); - debug!(target: LOG_TARGET, "Found {} pending transactions", l1_handler_txs.len()); - - // Then check if we need to process blocks + if from_block > chain_latest_block { - debug!(target: LOG_TARGET, "from_block ({}) > chain_latest_block ({}), returning with only pending events", from_block, chain_latest_block); - return Ok((chain_latest_block, l1_handler_txs)); + // Nothing to fetch, we can skip waiting the next tick. + return Ok((chain_latest_block, vec![])); } - - // Calculate the block range to process - let to_block = if from_block + max_blocks < chain_latest_block { + + // +1 as the from_block counts as 1 block fetched. + let to_block = if from_block + max_blocks + 1 < chain_latest_block { from_block + max_blocks } else { chain_latest_block }; - debug!(target: LOG_TARGET, "Fetching confirmed events from block {} to {}", from_block, to_block); - - // Now fetch events from confirmed blocks - match self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)).await { - Ok(events_by_block) => { - for (block_number, block_events) in events_by_block.iter() { - debug!( - target: LOG_TARGET, - block_number = %block_number, - events_count = %block_events.len(), - "Processing confirmed events for block" - ); - - for event in block_events { - let event_id = event.transaction_hash.to_string(); - - // Check if we've already processed this event - let cache = self.event_cache.read().await; - if cache.contains(&event_id) { - debug!(target: LOG_TARGET, "Event {} already processed, skipping", event_id); - continue; - } - drop(cache); // Release the read lock - - if let Ok(tx) = l1_handler_tx_from_event(event, chain_id) { - if let Ok((from, to, selector)) = info_from_event(event) { - let hooker = Arc::clone(&self.hooker); - let is_message_accepted = hooker - .read() - .await - .verify_message_to_appchain(from, to, selector) - .await; - - if is_message_accepted { - debug!( - target: LOG_TARGET, - "Message from block {} accepted: from {:?} to {:?} with selector {:?}", - block_number, from, to, selector - ); - l1_handler_txs.push(tx); - - // Add to cache after processing - let mut cache = self.event_cache.write().await; - cache.insert(event_id); - } else { - debug!( - target: LOG_TARGET, - "Message from block {} not accepted: from {:?} to {:?} with selector {:?}", - block_number, from, to, selector - ); - } - } - } - } + + let mut l1_handler_txs: Vec = vec![]; + + self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) + .await + .map_err(|_| Error::SendError) + .unwrap() + .iter() + .for_each(|e| { + debug!( + target: LOG_TARGET, + event = ?e, + "Converting event into L1HandlerTx." + ); + + if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { + l1_handler_txs.push(tx) } - } - Err(e) => { - error!(target: LOG_TARGET, "Error fetching confirmed events: {:?}", e); - return Err(Error::SendError); - } - } - - // We only clear the cache if we've moved to a new latest block to avoid reprocessing events - let previous_block = self.latest_block.load(Ordering::Relaxed); - if previous_block < chain_latest_block { - debug!(target: LOG_TARGET, "Moving to new latest block {} from {}, clearing cache", chain_latest_block, previous_block); - self.event_cache.write().await.clear(); - self.latest_block.store(chain_latest_block, Ordering::Relaxed); - } - - info!( - target: LOG_TARGET, - "Total messages gathered: {} (including pending)", - l1_handler_txs.len(), - ); - + }); + Ok((to_block, l1_handler_txs)) } From f7707b2eba79a5f8c35725176e6862c937490fb4 Mon Sep 17 00:00:00 2001 From: kwiss Date: Fri, 6 Dec 2024 15:55:08 +0100 Subject: [PATCH 30/31] fix: update hooker & unused import --- bin/solis/src/hooker.rs | 62 +++++++++------------------ crates/katana/rpc/rpc/src/starknet.rs | 2 +- 2 files changed, 21 insertions(+), 43 deletions(-) diff --git a/bin/solis/src/hooker.rs b/bin/solis/src/hooker.rs index e94ead3c30..a967b65139 100644 --- a/bin/solis/src/hooker.rs +++ b/bin/solis/src/hooker.rs @@ -12,24 +12,22 @@ use katana_primitives::chain::ChainId; use katana_primitives::contract::ContractAddress; use katana_primitives::transaction::{ExecutableTx, ExecutableTxWithHash, L1HandlerTx}; use katana_primitives::utils::transaction::compute_l1_message_hash; +use serde_json::json; +use serde_json::Value; use starknet::accounts::Call; use starknet::core::types::BroadcastedInvokeTransaction; use starknet::core::types::FieldElement; use starknet::macros::selector; use starknet::providers::Provider; -use std::sync::Arc; use std::fs::File; use std::fs::OpenOptions; -use std::io::Write; use std::io::Read; -use serde_json::Value; +use std::io::Write; use std::path::Path; -use serde_json::json; - +use std::sync::Arc; const FILE_PATH_ADDRESSES: &str = "addresses.json"; - use crate::contracts::orderbook::{OrderV1, RouteType}; use crate::contracts::starknet_utils::StarknetUtilsReader; use crate::CHAIN_ID_SOLIS; @@ -92,10 +90,7 @@ impl ); // check the current owner of the token. - let owner = sn_utils_reader_nft_address - .ownerOf(&ownership_verifier.token_id) - .call() - .await; + let owner = sn_utils_reader_nft_address.ownerOf(&ownership_verifier.token_id).call().await; if let Ok(owner_address) = owner { if owner_address != ownership_verifier.current_owner { @@ -145,10 +140,8 @@ impl } // check the balance - let balance = sn_utils_reader_erc20_address - .balanceOf(&balance_verifier.offerer) - .call() - .await; + let balance = + sn_utils_reader_erc20_address.balanceOf(&balance_verifier.offerer).call().await; if let Ok(balance) = balance { if balance < balance_verifier.start_amount { tracing::trace!( @@ -179,10 +172,7 @@ impl // ERC721 to ERC20 if order.route == RouteType::Erc721ToErc20 { let token_id = order.token_id.clone().unwrap(); - let n_token_id = U256 { - low: token_id.low, - high: token_id.high, - }; + let n_token_id = U256 { low: token_id.low, high: token_id.high }; let verifier = OwnershipVerifier { token_address: ContractAddress(order.token_address.into()), @@ -220,7 +210,8 @@ impl impl SolisHooker { - fn get_addresses_from_file() -> Result<(FieldElement, FieldElement), Box> { + fn get_addresses_from_file() -> Result<(FieldElement, FieldElement), Box> + { let mut file = match File::open(FILE_PATH_ADDRESSES) { Ok(file) => file, Err(_) => return Err("File not found".into()), @@ -264,7 +255,9 @@ impl orderbook_address: FieldElement, sn_executor_address: FieldElement, ) -> Self { - let (orderbook_address, sn_executor_address) = if orderbook_address == FieldElement::ZERO && sn_executor_address == FieldElement::ZERO { + let (orderbook_address, sn_executor_address) = if orderbook_address == FieldElement::ZERO + && sn_executor_address == FieldElement::ZERO + { match Self::get_addresses_from_file() { Ok((orderbook, executor)) => (orderbook, executor), Err(e) => { @@ -276,12 +269,7 @@ impl (orderbook_address, sn_executor_address) }; - Self { - orderbook_address, - sn_utils_reader, - sn_executor_address, - sequencer: None, - } + Self { orderbook_address, sn_utils_reader, sn_executor_address, sequencer: None } } /// Retrieves a reference to the sequencer. @@ -289,9 +277,7 @@ impl pub fn sequencer_ref(&self) -> &Arc> { // The expect is used here as it must always be set by Katana core. // If not set, the merge on Katana may be revised. - self.sequencer - .as_ref() - .expect("Sequencer must be set to get a reference to it") + self.sequencer.as_ref().expect("Sequencer must be set to get a reference to it") } /// Adds a `L1HandlerTransaction` to the transaction pool that is directed to the @@ -369,10 +355,7 @@ impl self.sn_executor_address = addresses.executor_starknet; let path = Path::new(FILE_PATH_ADDRESSES); - let file = OpenOptions::new() - .write(true) - .create(true) - .open(&path); + let file = OpenOptions::new().write(true).create(true).open(&path); match file { Ok(mut file) => { @@ -418,19 +401,13 @@ impl &self, transaction: BroadcastedInvokeTransaction, ) -> bool { - info!( - "HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", - transaction - ); + info!("HOOKER: verify_invoke_tx_before_pool called with transaction: {:?}", transaction); let calldata = match transaction { BroadcastedInvokeTransaction::V1(v1_transaction) => v1_transaction.calldata, BroadcastedInvokeTransaction::V3(v3_transaction) => v3_transaction.calldata, }; - info!( - "HOOKER: cairo_deserialize called with transaction: {:?}", - calldata - ); + info!("HOOKER: cairo_deserialize called with transaction: {:?}", calldata); let calls = match Vec::::cairo_deserialize(&calldata, 0) { Ok(calls) => calls, @@ -517,7 +494,7 @@ impl async fn on_starknet_tx_failed(&self, call: Call) { println!("Starknet tx failed: {:?}", call); - println!("orderhash: {:?}:", execution_info.order_hash) + let execution_info = match ExecutionInfo::cairo_deserialize(&call.calldata, 0) { Ok(execution_info) => execution_info, Err(e) => { @@ -526,6 +503,7 @@ impl } }; + println!("orderhash: {:?}:", execution_info.order_hash); // rollback the status let status = CancelStatus::CancelledUser; self.add_l1_handler_transaction_for_orderbook( diff --git a/crates/katana/rpc/rpc/src/starknet.rs b/crates/katana/rpc/rpc/src/starknet.rs index a0fde82e7e..03695ffbbf 100644 --- a/crates/katana/rpc/rpc/src/starknet.rs +++ b/crates/katana/rpc/rpc/src/starknet.rs @@ -36,7 +36,7 @@ use katana_rpc_types::{ use katana_rpc_types_builder::ReceiptBuilder; use katana_tasks::{BlockingTaskPool, TokioTaskSpawner}; use starknet::core::types::{ - BlockTag, BroadcastedInvokeTransaction, DeclareTransactionTrace, DeployAccountTransactionTrace, ExecuteInvocation, + BlockTag, DeclareTransactionTrace, DeployAccountTransactionTrace, ExecuteInvocation, InvokeTransactionTrace, L1HandlerTransactionTrace, RevertedInvocation, SimulatedTransaction, TransactionExecutionStatus, TransactionStatus, TransactionTrace, }; From 69a8d84215dd443eeaca5151c8ed967892e4212d Mon Sep 17 00:00:00 2001 From: christophe Date: Mon, 9 Dec 2024 22:30:11 +0100 Subject: [PATCH 31/31] fix: update gather block (#44) --- .../core/src/service/messaging/starknet.rs | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 13974b0210..6ba310dcdf 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -204,46 +204,38 @@ impl Messenger for StarknetM let chain_latest_block: u64 = match self.provider.block_number().await { Ok(n) => n, Err(_) => { - warn!( - target: LOG_TARGET, - "Couldn't fetch settlement chain last block number. \nSkipped, retry at the \ - next tick." - ); + warn!(target: LOG_TARGET, "Couldn't fetch settlement chain last block number"); return Err(Error::SendError); } }; - + if from_block > chain_latest_block { - // Nothing to fetch, we can skip waiting the next tick. return Ok((chain_latest_block, vec![])); } - - // +1 as the from_block counts as 1 block fetched. - let to_block = if from_block + max_blocks + 1 < chain_latest_block { - from_block + max_blocks - } else { - chain_latest_block - }; - + + // Instead of skipping blocks, process them sequentially + let to_block = std::cmp::min(from_block + max_blocks, chain_latest_block); + let mut l1_handler_txs: Vec = vec![]; - - self.fetch_events(BlockId::Number(from_block), BlockId::Number(to_block)) - .await - .map_err(|_| Error::SendError) - .unwrap() - .iter() - .for_each(|e| { - debug!( - target: LOG_TARGET, - event = ?e, - "Converting event into L1HandlerTx." - ); - - if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { - l1_handler_txs.push(tx) + + // Process each block individually to ensure none are missed + for block_num in from_block..=to_block { + match self.fetch_events(BlockId::Number(block_num), BlockId::Number(block_num)).await { + Ok(events) => { + events.iter().for_each(|e| { + if let Ok(tx) = l1_handler_tx_from_event(e, chain_id) { + l1_handler_txs.push(tx) + } + }); } - }); - + Err(e) => { + warn!(target: LOG_TARGET, "Error fetching block {}: {}", block_num, e); + // Return the last successfully processed block + return Ok((block_num - 1, l1_handler_txs)); + } + } + } + Ok((to_block, l1_handler_txs)) }