diff --git a/Cargo.lock b/Cargo.lock index f2114b8a7c..536f9779af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8543,6 +8543,7 @@ dependencies = [ "serde", "serde_json", "starknet 0.12.0", + "starknet-crypto 0.7.2", "starknet-types-core", "tempfile", "thiserror 1.0.63", diff --git a/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo b/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo index 0d8897f5a5..41bd5651a4 100644 --- a/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo +++ b/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo @@ -6,17 +6,6 @@ //! listen for those events. When an event with a message is gathered //! by katana, a L1 handler transaction is then created and added to the pool. //! -//! For the appchain to send a message to starknet, the process can be done in two -//! fashions: -//! -//! 1. The appchain register messages hashes exactly as starknet does. And then -//! a transaction on starknet must be issued to consume the message. -//! -//! 2. The sequencer (katana in that case) has also the capability of directly send -//! send a transaction to "execute" the content of the message. In the appchain -//! context this is a very effective manner to have a more dynamic and real-time -//! messaging than manual consuming of a message. -//! /// Trait for Appchain messaging. For now, the messaging only whitelist one /// appchain. @@ -46,17 +35,6 @@ trait IAppchainMessaging { fn consume_message_from_appchain( ref self: T, from_address: starknet::ContractAddress, payload: Span, ) -> felt252; - - /// Executes a message sent from the appchain. A message to execute - /// does not need to be registered as consumable. It is automatically - /// consumed while executed. - fn execute_message_from_appchain( - ref self: T, - from_address: starknet::ContractAddress, - to_address: starknet::ContractAddress, - selector: felt252, - payload: Span, - ); } #[starknet::interface] @@ -76,7 +54,7 @@ mod appchain_messaging { // Owner of this contract. owner: ContractAddress, // The account on Starknet (or the chain where this contract is deployed) - // used by the appchain sequencer to register messages hashes / execute messages. + // used by the appchain sequencer to register messages hashes. appchain_account: ContractAddress, // The nonce for messages sent from Starknet. sn_to_appc_nonce: felt252, @@ -93,7 +71,6 @@ mod appchain_messaging { MessageSentToAppchain: MessageSentToAppchain, MessagesRegisteredFromAppchain: MessagesRegisteredFromAppchain, MessageConsumed: MessageConsumed, - MessageExecuted: MessageExecuted, Upgraded: Upgraded, } @@ -126,17 +103,6 @@ mod appchain_messaging { payload: Span, } - #[derive(Drop, starknet::Event)] - struct MessageExecuted { - #[key] - from_address: ContractAddress, - #[key] - to_address: ContractAddress, - #[key] - selector: felt252, - payload: Span, - } - #[derive(Drop, starknet::Event)] struct Upgraded { class_hash: ClassHash, @@ -171,8 +137,19 @@ mod appchain_messaging { hash.try_into().expect('starknet keccak overflow') } - /// Computes message hash to consume messages from appchain. - /// starknet_keccak(from_address, to_address, payload_len, payload). + /// Computes the hash of a message that is sent from the Appchain to Starknet. + /// + /// + /// + /// # Arguments + /// + /// * `from_address` - Contract address of the message sender on the Appchain. + /// * `to_address` - Contract address to send the message to on the Appchain. + /// * `payload` - The message payload. + /// + /// # Returns + /// + /// The hash of the message from the Appchain to Starknet. fn compute_hash_appc_to_sn( from_address: ContractAddress, to_address: ContractAddress, payload: Span ) -> felt252 { @@ -192,12 +169,32 @@ mod appchain_messaging { starknet_keccak(hash_data.span()) } - /// Computes message hash to send messages to appchain. - /// starknet_keccak(nonce, to_address, selector, payload). + /// Computes the hash of a message that is sent from Starknet to the Appchain. + /// + /// + /// + /// # Arguments + /// + /// * `from_address` - Contract address of the message sender on the Appchain. + /// * `to_address` - Contract address to send the message to on the Appchain. + /// * `selector` - The `l1_handler` function selector of the contract on the Appchain + /// to execute. + /// * `payload` - The message payload. + /// * `nonce` - Nonce of the message. + /// + /// # Returns + /// + /// The hash of the message from Starknet to the Appchain. fn compute_hash_sn_to_appc( - nonce: felt252, to_address: ContractAddress, selector: felt252, payload: Span + from_address: ContractAddress, + to_address: ContractAddress, + selector: felt252, + payload: Span, + nonce: felt252 ) -> felt252 { - let mut hash_data = array![nonce, to_address.into(), selector,]; + let mut hash_data = array![ + from_address.into(), to_address.into(), nonce, selector, payload.len().into(), + ]; let mut i = 0_usize; loop { @@ -208,7 +205,7 @@ mod appchain_messaging { i += 1; }; - starknet_keccak(hash_data.span()) + core::poseidon::poseidon_hash_span(hash_data.span()) } #[abi(embed_v0)] @@ -308,26 +305,5 @@ mod appchain_messaging { msg_hash } - - fn execute_message_from_appchain( - ref self: ContractState, - from_address: ContractAddress, - to_address: ContractAddress, - selector: felt252, - payload: Span, - ) { - assert( - self.appchain_account.read() == starknet::get_caller_address(), - 'Unauthorized executor', - ); - - match starknet::call_contract_syscall(to_address, selector, payload) { - Result::Ok(_) => self - .emit(MessageExecuted { from_address, to_address, selector, payload, }), - Result::Err(e) => { - panic(e) - } - } - } } } diff --git a/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo b/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo index eacffbb26c..4cc6e681bb 100644 --- a/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo +++ b/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo @@ -4,10 +4,9 @@ //! This contract can sends messages using the send message to l1 //! syscall as we normally do for messaging. //! -//! If the message contains a `to_address` that is not zero, the message -//! hash will be sent to starknet to be registered. -//! If the `to_address` is zero, then the message will then fire a transaction -//! on the starknet to directly execute the message content. +//! However, the `to_address` is set to the `MSG` magic value since +//! this field is restricted to a valid Ethereum address, too small to +//! be a valid Starknet address. use starknet::ContractAddress; #[starknet::interface] @@ -21,20 +20,6 @@ trait IContractAppchain { /// * `to_address` - Contract address on Starknet. /// * `value` - Value to be sent in the payload. fn send_message(ref self: T, to_address: ContractAddress, value: felt252); - - /// Executes a message on Starknet. When the Katana will see this message - /// with `to_address` set to 0, an invoke transaction will be fired. - /// So basically this function can invoke any contract on Starknet, the fees on starknet - /// being paid by the sequencer. We can here imagine several scenarios. :) - /// The invoke though is not directly done to the destination contract, but the - /// app messaging contract that will forward the execution. - /// - /// # Arguments - /// - /// * `to_address` - Contract address on Starknet. - /// * `selector` - Selector. - /// * `value` - Value to be sent as argument to the contract being executed on starknet. - fn execute_message(ref self: T, to_address: ContractAddress, selector: felt252, value: felt252); } #[starknet::contract] @@ -67,12 +52,5 @@ mod contract_msg_starknet { let buf: Array = array![to_address.into(), value]; starknet::send_message_to_l1_syscall('MSG', buf.span()).unwrap_syscall(); } - - fn execute_message( - ref self: ContractState, to_address: ContractAddress, selector: felt252, value: felt252, - ) { - let buf: Array = array![to_address.into(), selector, value]; - starknet::send_message_to_l1_syscall('EXE', buf.span()).unwrap_syscall(); - } } } diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index 896f619a48..66231271f1 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -28,6 +28,7 @@ reqwest.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true +starknet-crypto.workspace = true starknet-types-core.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index 2626947ad5..55174777f5 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -52,7 +52,7 @@ use katana_executor::ExecutorFactory; use katana_primitives::chain::ChainId; use katana_primitives::receipt::MessageToL1; use serde::{Deserialize, Serialize}; -use tracing::{error, info}; +use tracing::{error, info, trace}; pub use self::service::{MessagingOutcome, MessagingService}; #[cfg(feature = "starknet-messaging")] @@ -229,11 +229,19 @@ impl Future for MessagingTask { while let Poll::Ready(Some(outcome)) = this.messaging.poll_next_unpin(cx) { match outcome { MessagingOutcome::Gather { msg_count, .. } => { - info!(target: LOG_TARGET, %msg_count, "Collected messages from settlement chain."); + if msg_count > 0 { + info!(target: LOG_TARGET, %msg_count, "Collected messages from settlement chain."); + } + + trace!(target: LOG_TARGET, %msg_count, "Collected messages from settlement chain."); } MessagingOutcome::Send { msg_count, .. } => { - info!(target: LOG_TARGET, %msg_count, "Sent messages to the settlement chain."); + if msg_count > 0 { + info!(target: LOG_TARGET, %msg_count, "Sent messages to the settlement chain."); + } + + trace!(target: LOG_TARGET, %msg_count, "Sent messages to the settlement chain."); } } } diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 46d2379ce0..d290b611b5 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -265,33 +265,11 @@ 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() { let payload_str: Vec = m.payload.iter().map(|f| format!("{:#x}", *f)).collect(); let hash = &hashes[i]; - #[cfg(feature = "starknet-messaging")] - if hash == &hash_exec_str { - let to_address = &payload_str[0]; - let selector = &payload_str[1]; - let payload_str = &payload_str[2..]; - - #[rustfmt::skip] - info!( - target: LOG_TARGET, - from_address = %m.from_address, - to_address = %to_address, - selector = %selector, - payload = %payload_str.join(", "), - "Message executed on settlement layer.", - ); - - continue; - } - // We check for magic value 'MSG' used only when we are doing L3-L2 messaging. let (to_address, payload_str) = if format!("{}", m.to_address) == "0x4d5347" { (payload_str[0].clone(), &payload_str[1..]) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 7c3f2bb3db..5b379940f5 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -1,14 +1,13 @@ use std::sync::Arc; +use alloy_primitives::B256; use anyhow::Result; use async_trait::async_trait; use katana_primitives::chain::ChainId; use katana_primitives::receipt::MessageToL1; use katana_primitives::transaction::L1HandlerTx; -use katana_primitives::utils::transaction::compute_l2_to_l1_message_hash; use starknet::accounts::{Account, ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::{BlockId, BlockTag, Call, EmittedEvent, EventFilter, Felt}; -use starknet::core::utils::starknet_keccak; use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{AnyProvider, JsonRpcClient, Provider}; @@ -19,14 +18,14 @@ 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. +/// field, in teh current design we set the `to_address` to the `MSG` magic value. +/// +/// Blockifier is the one responsible for this out of range error. +/// const MSG_MAGIC: Felt = felt!("0x4d5347"); -const EXE_MAGIC: Felt = felt!("0x455845"); -pub const HASH_EXEC: Felt = felt!("0xee"); +/// TODO: This may come from the configuration. +pub const MESSAGE_SENT_EVENT_KEY: Felt = selector!("MessageSent"); #[derive(Debug)] pub struct StarknetMessaging { @@ -73,11 +72,10 @@ impl StarknetMessaging { 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, + keys: Some(vec![vec![MESSAGE_SENT_EVENT_KEY]]), }; - // TODO: this chunk_size may also come from configuration? + // TODO: This chunk_size may also come from configuration? let chunk_size = 200; let mut continuation_token: Option = None; @@ -127,9 +125,7 @@ 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); - + async fn send_hashes(&self, hashes: Vec) -> MessengerResult { if hashes.is_empty() { return Ok(Felt::ZERO); } @@ -221,94 +217,54 @@ impl Messenger for StarknetMessaging { return Ok(vec![]); } - 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); - } - }; - } - + let hashes = parse_messages(messages)?; self.send_hashes(hashes.clone()).await?; Ok(hashes) } } -/// 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)> { +/// Parses messages sent by cairo contracts on the appchain to compute their hashes. +fn parse_messages(messages: &[MessageToL1]) -> MessengerResult> { 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. + // `to_address` is set to 'MSG' to indicate that the message + // has to be sent to the L2 messaging contract. + // + // Blockifier is the one responsible for this out of range error. + // let magic = m.to_address; - if magic == EXE_MAGIC { - if m.payload.len() < 2 { - error!( - target: LOG_TARGET, - "Message execution is expecting a payload of at least length \ - 2. With [0] being the contract address, and [1] the selector.", - ); - } + if magic != MSG_MAGIC { + warn!(target: LOG_TARGET, magic = %magic, "Skipping message with non-MSG magic."); + continue; + } - let to = m.payload[0]; - let selector = m.payload[1]; + // In the case or regular message, we compute the message's hash + // which will then be sent in a transaction to be registered as being + // ready for consumption by the L2 messaging contract. - let mut calldata = vec![]; - // We must exclude the `to_address` and `selector` from the actual payload. - if m.payload.len() >= 3 { - calldata.extend(m.payload[2..].to_vec()); - } + // As to_address is used by the magic, the `to_address` we want + // is the first element of the payload. + let to_address = m.payload[0]; - calls.push(Call { to, selector, calldata }); - hashes.push(HASH_EXEC); - } else if magic == MSG_MAGIC { - // In the case or regular message, we compute the message's hash - // which will then be sent in a transaction to be registered. - - // As to_address is used by the magic, the `to_address` we want - // is the first element of the payload. - let to_address = m.payload[0]; - - // Then, the payload must be changed to only keep the rest of the - // data, without the first element that was the `to_address`. - let payload = &m.payload[1..]; - - let mut buf: Vec = vec![]; - buf.extend(m.from_address.to_bytes_be()); - buf.extend(to_address.to_bytes_be()); - buf.extend(Felt::from(payload.len()).to_bytes_be()); - for p in payload { - buf.extend(p.to_bytes_be()); - } + // Then, the payload must be changed to only keep the rest of the + // data, without the first element that was the `to_address`. + let payload = &m.payload[1..]; - hashes.push(starknet_keccak(&buf)); - } else { - // Skip the message if no valid magic number found. - warn!(target: LOG_TARGET, magic = ?magic, "Invalid message to_address magic value."); - continue; - } + let message_hash = + compute_appchain_to_starknet_message_hash(m.from_address.into(), to_address, payload); + hashes.push(message_hash); } - Ok((hashes, calls)) + Ok(hashes) } fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result { - if event.keys[0] != selector!("MessageSentToAppchain") { - debug!( + if event.keys[0] != MESSAGE_SENT_EVENT_KEY { + error!( target: LOG_TARGET, event_key = ?event.keys[0], "Event can't be converted into L1HandlerTx." @@ -331,8 +287,15 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result l2 hash computation instead. - let message_hash = compute_l2_to_l1_message_hash(from_address, to_address, &calldata); + let message_hash = compute_starknet_to_appchain_message_hash( + from_address, + to_address, + nonce, + entry_point_selector, + &calldata, + ); + + let message_hash = B256::from_slice(message_hash.to_bytes_be().as_slice()); Ok(L1HandlerTx { nonce, @@ -340,6 +303,7 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result Result +fn compute_starknet_to_appchain_message_hash( + from_address: Felt, + to_address: Felt, + nonce: Felt, + entry_point_selector: Felt, + payload: &[Felt], +) -> Felt { + let mut buf: Vec = + vec![from_address, to_address, nonce, entry_point_selector, Felt::from(payload.len())]; + for p in payload { + buf.push(*p); + } + + starknet_crypto::poseidon_hash_many(&buf) +} + +/// Computes the hash of a L3 to L2 message. +/// +/// Piltover uses poseidon hash for all hashes computation. +/// +fn compute_appchain_to_starknet_message_hash( + from_address: Felt, + to_address: Felt, + payload: &[Felt], +) -> Felt { + let mut buf: Vec = vec![from_address, to_address, Felt::from(payload.len())]; + for p in payload { + buf.push(*p); + } + + starknet_crypto::poseidon_hash_many(&buf) +} + #[cfg(test)] mod tests { @@ -359,41 +360,25 @@ mod tests { fn parse_messages_msg() { let from_address = selector!("from_address"); let to_address = selector!("to_address"); - let selector = selector!("selector"); + let _selector = selector!("selector"); let payload_msg = vec![to_address, Felt::ONE, Felt::TWO]; - let payload_exe = vec![to_address, selector, Felt::ONE, Felt::TWO]; - - let messages = vec![ - MessageToL1 { - from_address: from_address.into(), - to_address: MSG_MAGIC, - payload: payload_msg, - }, - MessageToL1 { - from_address: from_address.into(), - to_address: EXE_MAGIC, - payload: payload_exe.clone(), - }, - ]; - - let (hashes, calls) = parse_messages(&messages).unwrap(); - - assert_eq!(hashes.len(), 2); + + let messages = vec![MessageToL1 { + from_address: from_address.into(), + to_address: MSG_MAGIC, + payload: payload_msg, + }]; + + let hashes = parse_messages(&messages).unwrap(); + + assert_eq!(hashes.len(), 1); assert_eq!( hashes, vec![ - Felt::from_hex( - "0x03a1d2e131360f15e26dd4f6ff10550685611cc25f75e7950b704adb04b36162" - ) - .unwrap(), - HASH_EXEC, + Felt::from_hex("0x5063bd24379be4da83d607725d1a9f7e5571cb1be30784b4a7a22996f59ff22") + .unwrap(), ] ); - - assert_eq!(calls.len(), 1); - assert_eq!(calls[0].to, to_address); - assert_eq!(calls[0].selector, selector); - assert_eq!(calls[0].calldata, payload_exe[2..].to_vec()); } #[test] @@ -411,21 +396,6 @@ mod tests { parse_messages(&messages).unwrap(); } - #[test] - #[should_panic] - fn parse_messages_exe_bad_payload() { - let from_address = selector!("from_address"); - let payload_exe = vec![Felt::ONE]; - - let messages = vec![MessageToL1 { - from_address: from_address.into(), - to_address: EXE_MAGIC, - payload: payload_exe, - }]; - - parse_messages(&messages).unwrap(); - } - #[test] fn l1_handler_tx_from_event_parse_ok() { let from_address = selector!("from_address"); @@ -448,25 +418,26 @@ mod tests { from_address: felt!( "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" ), - keys: vec![ - selector!("MessageSentToAppchain"), - selector!("random_hash"), - from_address, - to_address, - ], + keys: vec![MESSAGE_SENT_EVENT_KEY, selector!("random_hash"), from_address, to_address], data: vec![selector, nonce, Felt::from(calldata.len() as u128), Felt::THREE], block_hash: Some(selector!("block_hash")), block_number: Some(0), transaction_hash, }; - let message_hash = compute_l2_to_l1_message_hash(from_address, to_address, &calldata); + let message_hash = compute_starknet_to_appchain_message_hash( + from_address, + to_address, + nonce, + selector, + &calldata, + ); let expected = L1HandlerTx { nonce, calldata, chain_id, - message_hash, + message_hash: B256::from_slice(message_hash.to_bytes_be().as_slice()), paid_fee_on_l1: 30000_u128, version: Felt::ZERO, entry_point_selector: selector, diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index db935cfd44..7e1c595e95 100644 Binary files a/spawn-and-move-db.tar.gz and b/spawn-and-move-db.tar.gz differ diff --git a/types-test-db.tar.gz b/types-test-db.tar.gz index 9fb1a5c0d2..55c06fae50 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ