From 3d8a935ef2c62a5ebfd0d9f70f493b51eb886efa Mon Sep 17 00:00:00 2001 From: glihm Date: Thu, 16 Nov 2023 17:48:35 -0600 Subject: [PATCH] feat: add KatanaHooker for Solis --- crates/katana/core/src/hooker.rs | 48 +++++++++++++++++++ crates/katana/core/src/lib.rs | 1 + crates/katana/core/src/sequencer.rs | 9 ++-- .../katana/core/src/service/messaging/mod.rs | 12 ++++- .../core/src/service/messaging/service.rs | 4 +- .../core/src/service/messaging/starknet.rs | 22 +++++++-- crates/katana/rpc/src/api/starknet.rs | 3 ++ crates/katana/rpc/src/starknet.rs | 4 ++ 8 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 crates/katana/core/src/hooker.rs diff --git a/crates/katana/core/src/hooker.rs b/crates/katana/core/src/hooker.rs new file mode 100644 index 0000000000..ad610ee9b9 --- /dev/null +++ b/crates/katana/core/src/hooker.rs @@ -0,0 +1,48 @@ +//! 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 async_trait::async_trait; +use starknet::core::types::BroadcastedInvokeTransaction; +use starknet::accounts::Call; + +#[async_trait] +pub trait KatanaHooker { + /// 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_message_to_starknet_before_tx( + &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 react_on_starknet_tx_failed( + &self, + call: Call, + ); +} diff --git a/crates/katana/core/src/lib.rs b/crates/katana/core/src/lib.rs index c80c58031f..6cf7d39809 100644 --- a/crates/katana/core/src/lib.rs +++ b/crates/katana/core/src/lib.rs @@ -5,6 +5,7 @@ pub mod db; pub mod env; pub mod execution; pub mod fork; +pub mod hooker; pub mod pool; pub mod sequencer; pub mod service; diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index 16d01783d8..5471711662 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -33,7 +33,7 @@ use crate::service::messaging::MessagingConfig; use crate::service::messaging::MessagingService; use crate::service::{NodeService, TransactionMiner}; use crate::utils::event::{ContinuationToken, ContinuationTokenError}; - +use crate::hooker::KatanaHooker; type SequencerResult = Result; #[derive(Debug, Default)] @@ -49,10 +49,11 @@ pub struct KatanaSequencer { pub pool: Arc, pub backend: Arc, pub block_producer: BlockProducer, + pub hooker: Arc, } impl KatanaSequencer { - pub async fn new(config: SequencerConfig, starknet_config: StarknetConfig) -> Self { + pub async fn new(config: SequencerConfig, starknet_config: StarknetConfig, hooker: Arc) -> Self { let backend = Arc::new(Backend::new(starknet_config).await); let pool = Arc::new(TransactionPool::new()); @@ -72,7 +73,7 @@ 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), Arc::clone(&hooker)).await.ok() } else { None }; @@ -85,7 +86,7 @@ impl KatanaSequencer { messaging, }); - Self { pool, config, backend, block_producer } + Self { pool, config, backend, block_producer, hooker } } /// Returns the pending state if the sequencer is running in _interval_ mode. Otherwise `None`. diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index 86745986aa..5ae918644f 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -38,6 +38,7 @@ mod service; mod starknet; use std::path::Path; +use std::sync::Arc; use ::starknet::core::types::{FieldElement, MsgToL1}; use ::starknet::providers::ProviderError as StarknetProviderError; @@ -47,6 +48,7 @@ use ethereum::EthereumMessaging; use ethers::providers::ProviderError as EthereumProviderError; use serde::Deserialize; use tracing::{error, info}; +use crate::hooker::KatanaHooker; pub use self::service::{MessagingOutcome, MessagingService}; #[cfg(feature = "starknet-messaging")] @@ -71,6 +73,9 @@ pub enum Error { SendError, #[error(transparent)] Provider(ProviderError), + + #[error("Solis: Invalid assets before sending message transaction")] + SolisAssetFault, } #[derive(Debug, thiserror::Error)] @@ -165,7 +170,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) => { @@ -179,7 +187,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 901999e437..58f6e65fc5 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -12,6 +12,7 @@ use super::{MessagingConfig, Messenger, MessengerMode, MessengerResult, LOG_TARG use crate::backend::storage::transaction::{L1HandlerTransaction, Transaction}; use crate::backend::Backend; use crate::pool::TransactionPool; +use crate::hooker::KatanaHooker; type MessagingFuture = Pin + Send>>; type MessageGatheringFuture = MessagingFuture>; @@ -41,10 +42,11 @@ impl MessagingService { config: MessagingConfig, pool: Arc, backend: Arc, + hooker: Arc, ) -> 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 0b5d633c27..5e2ca31521 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -22,6 +22,7 @@ use url::Url; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; use crate::backend::storage::transaction::L1HandlerTransaction; use crate::utils::transaction::compute_l1_handler_transaction_hash_felts; +use crate::hooker::KatanaHooker; /// 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. @@ -39,10 +40,11 @@ pub struct StarknetMessaging { wallet: LocalWallet, sender_account_address: FieldElement, messaging_contract_address: FieldElement, + hooker: Arc, } impl StarknetMessaging { - pub async fn new(config: MessagingConfig) -> Result { + pub async fn new(config: MessagingConfig, hooker: Arc) -> Result { let provider = AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new( Url::parse(&config.rpc_url)?, ))); @@ -61,6 +63,7 @@ impl StarknetMessaging { chain_id, sender_account_address, messaging_contract_address, + hooker, }) } @@ -225,18 +228,27 @@ impl Messenger for StarknetMessaging { let (hashes, calls) = parse_messages(messages)?; - if !calls.is_empty() { - match self.send_invoke_tx(calls).await { + for call in &calls { + // 1. Verify before TX. + if !self.hooker.verify_message_to_starknet_before_tx(call.clone()).await { + continue; + } + + match self.send_invoke_tx(vec![call.clone()]).await { Ok(tx_hash) => { trace!(target: LOG_TARGET, "Invoke transaction hash {:#064x}", tx_hash); } Err(e) => { + // 2. React on TX error. + self.hooker.react_on_starknet_tx_failed(call.clone()).await; error!("Error sending invoke tx on Starknet: {:?}", e); - return Err(Error::SendError); } }; } + // We don't want to use the multicall in order to not + // have all the transaction to fail because of 1. + self.send_hashes(hashes.clone()).await?; Ok(hashes) @@ -257,6 +269,8 @@ fn parse_messages(messages: &[MsgToL1]) -> MessengerResult<(Vec, V // has to be executed or sent normally. let magic = m.to_address; + // TODO: Whitelist the orderbook contract. + if magic == EXE_MAGIC { if m.payload.len() < 2 { error!( diff --git a/crates/katana/rpc/src/api/starknet.rs b/crates/katana/rpc/src/api/starknet.rs index cf6505be69..6c46811d46 100644 --- a/crates/katana/rpc/src/api/starknet.rs +++ b/crates/katana/rpc/src/api/starknet.rs @@ -78,6 +78,9 @@ pub enum StarknetApiError { TooManyKeysInFilter = 34, #[error("Failed to fetch pending transactions")] FailedToFetchPendingTransactions = 38, + + #[error("Solis: Assets are invalid on L2")] + SolisAssetFault = 7777, } impl From for Error { diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index c0edfb61a8..941f6314c3 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -439,6 +439,10 @@ impl StarknetApiServer for StarknetApi { let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) .map_err(|_| StarknetApiError::UnexpectedError)?; + if !self.sequencer.hooker.verify_invoke_tx_before_pool(invoke_transaction.clone()).await { + return Err(StarknetApiError::SolisAssetFault.into()); + } + let transaction = broadcasted_invoke_rpc_to_api_transaction(invoke_transaction, chain_id); let transaction_hash = transaction.transaction_hash().0.into();