Skip to content

Commit

Permalink
feat: add KatanaHooker for Solis
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm committed Nov 16, 2023
1 parent 1336e80 commit 3d8a935
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 11 deletions.
48 changes: 48 additions & 0 deletions crates/katana/core/src/hooker.rs
Original file line number Diff line number Diff line change
@@ -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,
);
}
1 change: 1 addition & 0 deletions crates/katana/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions crates/katana/core/src/sequencer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = Result<T, SequencerError>;

#[derive(Debug, Default)]
Expand All @@ -49,10 +49,11 @@ pub struct KatanaSequencer {
pub pool: Arc<TransactionPool>,
pub backend: Arc<Backend>,
pub block_producer: BlockProducer,
pub hooker: Arc<dyn KatanaHooker + Send + Sync>,
}

impl KatanaSequencer {
pub async fn new(config: SequencerConfig, starknet_config: StarknetConfig) -> Self {
pub async fn new(config: SequencerConfig, starknet_config: StarknetConfig, hooker: Arc<dyn KatanaHooker + Send + Sync>) -> Self {
let backend = Arc::new(Backend::new(starknet_config).await);

let pool = Arc::new(TransactionPool::new());
Expand All @@ -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
};
Expand All @@ -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`.
Expand Down
12 changes: 10 additions & 2 deletions crates/katana/core/src/service/messaging/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")]
Expand All @@ -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)]
Expand Down Expand Up @@ -165,7 +170,10 @@ pub enum MessengerMode {
}

impl MessengerMode {
pub async fn from_config(config: MessagingConfig) -> MessengerResult<Self> {
pub async fn from_config(
config: MessagingConfig,
hooker: Arc<dyn KatanaHooker + Send + Sync>
) -> MessengerResult<Self> {
match config.chain.as_str() {
CONFIG_CHAIN_ETHEREUM => match EthereumMessaging::new(config).await {
Ok(m_eth) => {
Expand All @@ -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))
Expand Down
4 changes: 3 additions & 1 deletion crates/katana/core/src/service/messaging/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = Pin<Box<dyn Future<Output = T> + Send>>;
type MessageGatheringFuture = MessagingFuture<MessengerResult<(u64, usize)>>;
Expand Down Expand Up @@ -41,10 +42,11 @@ impl MessagingService {
config: MessagingConfig,
pool: Arc<TransactionPool>,
backend: Arc<Backend>,
hooker: Arc<dyn KatanaHooker + Send + Sync>,
) -> anyhow::Result<Self> {
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!(
Expand Down
22 changes: 18 additions & 4 deletions crates/katana/core/src/service/messaging/starknet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -39,10 +40,11 @@ pub struct StarknetMessaging {
wallet: LocalWallet,
sender_account_address: FieldElement,
messaging_contract_address: FieldElement,
hooker: Arc<dyn KatanaHooker + Send + Sync>,
}

impl StarknetMessaging {
pub async fn new(config: MessagingConfig) -> Result<StarknetMessaging> {
pub async fn new(config: MessagingConfig, hooker: Arc<dyn KatanaHooker + Send + Sync>) -> Result<StarknetMessaging> {
let provider = AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(
Url::parse(&config.rpc_url)?,
)));
Expand All @@ -61,6 +63,7 @@ impl StarknetMessaging {
chain_id,
sender_account_address,
messaging_contract_address,
hooker,
})
}

Expand Down Expand Up @@ -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)
Expand All @@ -257,6 +269,8 @@ fn parse_messages(messages: &[MsgToL1]) -> MessengerResult<(Vec<FieldElement>, 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!(
Expand Down
3 changes: 3 additions & 0 deletions crates/katana/rpc/src/api/starknet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<StarknetApiError> for Error {
Expand Down
4 changes: 4 additions & 0 deletions crates/katana/rpc/src/starknet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down

0 comments on commit 3d8a935

Please sign in to comment.