From 6ba0476e3f96758e8bcd08edcf856b4a5f70b269 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Date: Tue, 22 Apr 2025 03:12:47 +0300 Subject: [PATCH] =?UTF-8?q?refactor(operator):=20=E2=99=BB=EF=B8=8F=20Gene?= =?UTF-8?q?ralise=20further=20the=20`MessageProcessor`=20from=20the=20Inbo?= =?UTF-8?q?und=20Pallet=20V2=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com> --- operator/pallets/ethereum-client/Cargo.toml | 2 +- operator/pallets/inbound-queue-v2/src/lib.rs | 55 +------- .../src/message_processors.rs | 121 +++++++++++++++++- operator/pallets/inbound-queue-v2/src/mock.rs | 49 +++---- operator/primitives/bridge/src/lib.rs | 9 +- .../snowbridge/inbound-queue/src/v2/traits.rs | 8 +- operator/runtime/src/configs/mod.rs | 105 +-------------- 7 files changed, 160 insertions(+), 189 deletions(-) diff --git a/operator/pallets/ethereum-client/Cargo.toml b/operator/pallets/ethereum-client/Cargo.toml index 2eab65a4..89507b50 100644 --- a/operator/pallets/ethereum-client/Cargo.toml +++ b/operator/pallets/ethereum-client/Cargo.toml @@ -90,4 +90,4 @@ try-runtime = [ # THIS IS JUST TO AVOID TESTS RUNNING IN THE CI. # THIS IS A TEMPORARY PALLET HERE, AND THEN WE'LL IMPORT IT FROM REMOTE. [lib] -test = false \ No newline at end of file +test = false diff --git a/operator/pallets/inbound-queue-v2/src/lib.rs b/operator/pallets/inbound-queue-v2/src/lib.rs index 11eadcbb..702005c2 100644 --- a/operator/pallets/inbound-queue-v2/src/lib.rs +++ b/operator/pallets/inbound-queue-v2/src/lib.rs @@ -47,10 +47,9 @@ use snowbridge_inbound_queue_primitives::{ EventProof, RewardLedger, VerificationError, Verifier, }; use sp_core::H160; -use sp_runtime::traits::TryConvert; use sp_runtime::traits::Zero; use sp_std::prelude::*; -use xcm::prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *}; +use xcm::prelude::*; #[cfg(feature = "runtime-benchmarks")] use {snowbridge_beacon_primitives::BeaconHeader, sp_core::H256}; @@ -83,17 +82,9 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The verifier for inbound messages from Ethereum. type Verifier: Verifier; - /// XCM message sender. - type XcmSender: SendXcm; - /// Handler for XCM fees. - type XcmExecutor: ExecuteXcm; /// Address of the Gateway contract. #[pallet::constant] type GatewayAddress: Get; - /// AssetHub parachain ID. - type AssetHubParaId: Get; - /// Convert a command from Ethereum to an XCM message. - type MessageConverter: ConvertMessage; /// Process the message that was submitted type MessageProcessor: MessageProcessor; #[cfg(feature = "runtime-benchmarks")] @@ -105,8 +96,6 @@ pub mod pallet { type DefaultRewardKind: Get; /// Relayer reward payment. type RewardPayment: RewardLedger; - /// AccountId to Location converter - type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>; type WeightInfo: WeightInfo; } @@ -210,7 +199,7 @@ pub mod pallet { let message = Message::try_from(&event.event_log).map_err(|_| Error::::InvalidMessage)?; - T::MessageProcessor::process_message(who, message) + Self::process_message(who, message) } /// Halt or resume all pallet operations. May only be called by root. @@ -240,18 +229,10 @@ pub mod pallet { // Verify the message has not been processed ensure!(!Nonce::::get(nonce.into()), Error::::InvalidNonce); - let xcm = - T::MessageConverter::convert(message).map_err(|error| Error::::from(error))?; + // Process message + let message_id = T::MessageProcessor::process_message(relayer.clone(), message)?; - // Forward XCM to AH - let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); - let message_id = - Self::send_xcm(dest.clone(), &relayer, xcm.clone()).map_err(|error| { - tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error"); - Error::::from(error) - })?; - - // Pay relayer reward + // Pay relayer reward if needed if !relayer_fee.is_zero() { T::RewardPayment::register_reward( &relayer, @@ -263,34 +244,10 @@ pub mod pallet { // Mark message as received Nonce::::set(nonce.into()); + // Emit event with the message_id Self::deposit_event(Event::MessageReceived { nonce, message_id }); Ok(()) } - - fn send_xcm( - dest: Location, - fee_payer: &T::AccountId, - xcm: Xcm<()>, - ) -> Result { - let (ticket, fee) = validate_send::(dest, xcm)?; - let fee_payer = T::AccountToLocation::try_convert(fee_payer).map_err(|err| { - tracing::error!( - target: LOG_TARGET, - ?err, - "Failed to convert account to XCM location", - ); - SendError::NotApplicable - })?; - T::XcmExecutor::charge_fees(fee_payer.clone(), fee.clone()).map_err(|error| { - tracing::error!( - target: LOG_TARGET, - ?error, - "Charging fees failed with error", - ); - SendError::Fees - })?; - T::XcmSender::deliver(ticket) - } } } diff --git a/operator/pallets/inbound-queue-v2/src/message_processors.rs b/operator/pallets/inbound-queue-v2/src/message_processors.rs index e861f0f9..1bbec9d7 100644 --- a/operator/pallets/inbound-queue-v2/src/message_processors.rs +++ b/operator/pallets/inbound-queue-v2/src/message_processors.rs @@ -1,20 +1,127 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork use super::*; -use sp_runtime::DispatchResult; +use codec::Encode; +use frame_support::traits::Get; +use sp_runtime::traits::TryConvert; +use sp_runtime::DispatchError; use sp_std::marker::PhantomData; +use xcm::prelude::{ExecuteXcm, Location, Parachain, SendError, SendXcm, XcmHash}; -pub struct DefaultMessageProcessor(pub PhantomData); +/// A message processor that simply returns the Blake2_256 hash of the SCALE encoded message +pub struct RemarkMessageProcessor(pub PhantomData); -impl MessageProcessor for DefaultMessageProcessor +impl MessageProcessor for RemarkMessageProcessor where T: crate::Config, { - fn can_process_message(_who: &AccountId, message: &Message) -> bool { - T::MessageConverter::convert(message.clone()).is_ok() + fn can_process_message(_who: &AccountId, _message: &Message) -> bool { + true } - fn process_message(who: AccountId, message: Message) -> DispatchResult { - crate::Pallet::::process_message(who, message) + fn process_message(_who: AccountId, _message: Message) -> Result<[u8; 32], DispatchError> { + // Simply return the Blake2_256 hash of the SCALE encoded message + let hash = sp_core::hashing::blake2_256(_message.encode().as_slice()); + Ok(hash) + } +} + +/// A message processor that converts messages to XCM and forwards them to AssetHub +/// Generic parameters: T = pallet Config, Sender = XCM sender, Executor = fee handler, +/// Converter = message converter, AccountToLocation = account-to-location converter +pub struct XcmMessageProcessor( + pub PhantomData<( + T, + Sender, + Executor, + Converter, + AccountToLocation, + AssetHubParaId, + )>, +); + +impl + MessageProcessor + for XcmMessageProcessor +where + T: crate::Config, + Sender: SendXcm, + Executor: ExecuteXcm, + Converter: ConvertMessage, + AccountToLocation: for<'a> TryConvert<&'a AccountId, Location>, + AssetHubParaId: Get, +{ + fn can_process_message(_who: &AccountId, message: &Message) -> bool { + // Check if the message can be converted to XCM + Converter::convert(message.clone()).is_ok() + } + + fn process_message(who: AccountId, message: Message) -> Result<[u8; 32], DispatchError> { + // Process the message and return its ID + let id = Self::process_xcm(who, message)?; + Ok(id) + } +} + +impl + XcmMessageProcessor +where + T: crate::Config, + Sender: SendXcm, + Executor: ExecuteXcm, + Converter: ConvertMessage, + AccountToLocation: for<'a> TryConvert<&'a T::AccountId, Location>, + AssetHubParaId: Get, +{ + /// Process a message and return the message ID + pub fn process_xcm(who: T::AccountId, message: Message) -> Result { + // Convert the message to XCM + let xcm = Converter::convert(message).map_err(|error| Error::::from(error))?; + + // Forward XCM to AssetHub + let dest = Location::new(1, [Parachain(AssetHubParaId::get())]); + let message_id = Self::send_xcm(dest.clone(), &who, xcm.clone()).map_err(|error| { + tracing::error!(target: LOG_TARGET, ?error, ?dest, ?xcm, "XCM send failed with error"); + Error::::from(error) + })?; + + // Return the message_id + Ok(message_id) + } +} + +impl + XcmMessageProcessor +where + T: crate::Config, + Sender: SendXcm, + Executor: ExecuteXcm, + Converter: ConvertMessage, + AccountToLocation: for<'a> TryConvert<&'a T::AccountId, Location>, + AssetHubParaId: Get, +{ + fn send_xcm( + dest: Location, + fee_payer: &T::AccountId, + xcm: Xcm<()>, + ) -> Result { + let (ticket, fee) = validate_send::(dest, xcm)?; + let fee_payer = AccountToLocation::try_convert(fee_payer).map_err(|err| { + tracing::error!( + target: LOG_TARGET, + ?err, + "Failed to convert account to XCM location", + ); + SendError::NotApplicable + })?; + Executor::charge_fees(fee_payer.clone(), fee.clone()).map_err(|error| { + tracing::error!( + target: LOG_TARGET, + ?error, + "Charging fees failed with error", + ); + SendError::Fees + })?; + Sender::deliver(ticket) } } diff --git a/operator/pallets/inbound-queue-v2/src/mock.rs b/operator/pallets/inbound-queue-v2/src/mock.rs index 93d174b9..f216f868 100644 --- a/operator/pallets/inbound-queue-v2/src/mock.rs +++ b/operator/pallets/inbound-queue-v2/src/mock.rs @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: 2023 Snowfork use super::*; -use crate::{self as inbound_queue_v2, message_processors::DefaultMessageProcessor}; +use crate::{self as inbound_queue_v2, message_processors::XcmMessageProcessor}; use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; -use frame_support::{derive_impl, dispatch::DispatchResult, parameter_types, traits::ConstU32}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use hex_literal::hex; use scale_info::TypeInfo; use snowbridge_beacon_primitives::{ @@ -14,8 +14,8 @@ use snowbridge_core::TokenId; use snowbridge_inbound_queue_primitives::{v2::MessageToXcm, Log, Proof, VerificationError}; use sp_core::H160; use sp_runtime::{ - traits::{IdentityLookup, MaybeEquivalence}, - BuildStorage, + traits::{IdentityLookup, MaybeEquivalence, TryConvert}, + BuildStorage, DispatchError, }; use sp_std::{convert::From, default::Default, marker::PhantomData}; use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; @@ -145,7 +145,7 @@ impl MessageProcessor for DummyPrefix { false } - fn process_message(_who: AccountId, _message: Message) -> DispatchResult { + fn process_message(_who: AccountId, _message: Message) -> Result<[u8; 32], DispatchError> { panic!("DummyPrefix::process_message shouldn't be called"); } } @@ -157,7 +157,7 @@ impl MessageProcessor for DummySuffix { true } - fn process_message(_who: AccountId, _message: Message) -> DispatchResult { + fn process_message(_who: AccountId, _message: Message) -> Result<[u8; 32], DispatchError> { panic!("DummySuffix::process_message shouldn't be called"); } } @@ -165,27 +165,32 @@ impl MessageProcessor for DummySuffix { impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; - type XcmSender = MockXcmSender; - type XcmExecutor = MockXcmExecutor; type RewardPayment = (); type GatewayAddress = GatewayAddress; - type AssetHubParaId = ConstU32<1000>; - type MessageConverter = MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - EthereumNetwork, - InboundQueueLocation, - MockTokenIdConvert, - GatewayAddress, - UniversalLocation, - AssetHubFromEthereum, - >; - // Passively test that the implementation of MessageProcessor trait works correctly for tuple - type MessageProcessor = (DummyPrefix, DefaultMessageProcessor, DummySuffix); + type MessageProcessor = ( + DummyPrefix, + XcmMessageProcessor< + Test, + MockXcmSender, + MockXcmExecutor, + MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + EthereumNetwork, + InboundQueueLocation, + MockTokenIdConvert, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >, + MockAccountLocationConverter, + ConstU32<1000>, + >, + DummySuffix, + ); #[cfg(feature = "runtime-benchmarks")] type Helper = Test; type WeightInfo = (); - type AccountToLocation = MockAccountLocationConverter; type RewardKind = BridgeReward; type DefaultRewardKind = SnowbridgeReward; } diff --git a/operator/primitives/bridge/src/lib.rs b/operator/primitives/bridge/src/lib.rs index 5e3ace5a..f6559d69 100644 --- a/operator/primitives/bridge/src/lib.rs +++ b/operator/primitives/bridge/src/lib.rs @@ -56,7 +56,10 @@ where } } - fn process_message(_who: AccountId, message: SnowbridgeMessage) -> Result<(), DispatchError> { + fn process_message( + _who: AccountId, + message: SnowbridgeMessage, + ) -> Result<[u8; 32], DispatchError> { let payload = match &message.xcm { snowbridge_inbound_queue_primitives::v2::Payload::Raw(payload) => payload, snowbridge_inbound_queue_primitives::v2::Payload::CreateAsset { @@ -77,7 +80,9 @@ where RawOrigin::Root.into(), validators, )?; - Ok(()) + let mut id = [0u8; 32]; + id[..EL_MESSAGE_ID.len()].copy_from_slice(&EL_MESSAGE_ID); + Ok(id) } } } diff --git a/operator/primitives/snowbridge/inbound-queue/src/v2/traits.rs b/operator/primitives/snowbridge/inbound-queue/src/v2/traits.rs index 375064e0..84457efd 100644 --- a/operator/primitives/snowbridge/inbound-queue/src/v2/traits.rs +++ b/operator/primitives/snowbridge/inbound-queue/src/v2/traits.rs @@ -3,7 +3,7 @@ // SPDX-FileCopyrightText: 2021-2025 Parity Technologies (UK) Ltd. use super::Message; use sp_core::RuntimeDebug; -use sp_runtime::{DispatchError, DispatchResult}; +use sp_runtime::DispatchError; use xcm::latest::Xcm; /// Converts an inbound message from Ethereum to an XCM message that can be @@ -26,8 +26,8 @@ pub enum ConvertMessageError { pub trait MessageProcessor { /// Lightweight function to check if this processor can handle the message fn can_process_message(who: &AccountId, message: &Message) -> bool; - /// Process the message - fn process_message(who: AccountId, message: Message) -> DispatchResult; + /// Process the message and return the message ID + fn process_message(who: AccountId, message: Message) -> Result<[u8; 32], DispatchError>; } #[impl_trait_for_tuples::impl_for_tuples(10)] @@ -45,7 +45,7 @@ impl MessageProcessor for Tuple { false } - fn process_message(who: AccountId, message: Message) -> DispatchResult { + fn process_message(who: AccountId, message: Message) -> Result<[u8; 32], DispatchError> { for_tuples!( #( match Tuple::can_process_message(&who, &message) { true => { diff --git a/operator/runtime/src/configs/mod.rs b/operator/runtime/src/configs/mod.rs index cb990f75..1ac3bfa0 100644 --- a/operator/runtime/src/configs/mod.rs +++ b/operator/runtime/src/configs/mod.rs @@ -68,6 +68,7 @@ use pallet_transaction_payment::{ }; use polkadot_primitives::Moment; use snowbridge_beacon_primitives::{Fork, ForkVersions}; +use snowbridge_inbound_queue_primitives::RewardLedger; use sp_consensus_beefy::mmr::BeefyDataProvider; use sp_consensus_beefy::{ecdsa_crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion}; use sp_core::{crypto::KeyTypeId, H160, H256, U256}; @@ -629,81 +630,12 @@ impl snowbridge_pallet_ethereum_client::Config for Runtime { type WeightInfo = (); } -use snowbridge_inbound_queue_primitives::v2::{ConvertMessage, ConvertMessageError, Message}; -use snowbridge_inbound_queue_primitives::RewardLedger; -use xcm::v5::{ - Assets, Error as XcmError, ExecuteXcm, Location as XcmV5Location, Outcome, SendError, SendXcm, - Xcm, XcmHash, -}; - // Define the gateway address parameter // TODO: Turn this into a runtime parameter parameter_types! { pub EthereumGatewayAddress: H160 = H160::repeat_byte(0x42); } -// Dummy implementations for XCM-related traits -pub struct DummyXcmSender; -impl SendXcm for DummyXcmSender { - type Ticket = (); - fn validate( - _destination: &mut Option, - _message: &mut Option>, - ) -> Result<(Self::Ticket, Assets), SendError> { - Ok(((), Assets::new())) - } - fn deliver(_ticket: Self::Ticket) -> Result { - Ok([0; 32]) - } -} - -// Since () doesn't work, let's use an empty tuple struct for compatibility -#[derive(Debug)] -pub struct EmptyPrepared; - -// Implement the required trait for our empty struct -impl xcm::opaque::latest::PreparedMessage for EmptyPrepared { - fn weight_of(&self) -> Weight { - Weight::zero() - } -} - -pub struct DummyXcmExecutor; -impl ExecuteXcm for DummyXcmExecutor { - type Prepared = EmptyPrepared; - - fn prepare(_message: Xcm) -> Result> { - Ok(EmptyPrepared) - } - - fn execute( - _origin: impl Into, - _pre: Self::Prepared, - _id: &mut XcmHash, - _weight_credit: Weight, - ) -> Outcome { - // Just use a default empty implementation that works - let weight = Weight::zero(); - Outcome::Incomplete { - used: weight, - error: XcmError::NotWithdrawable, - } - } - - fn charge_fees(_location: impl Into, _fees: Assets) -> Result<(), XcmError> { - Ok(()) - } -} - -// Dummy implementation for MessageConverter -pub struct DummyMessageConverter; -impl ConvertMessage for DummyMessageConverter { - fn convert(_message: Message) -> Result, ConvertMessageError> { - Ok(Xcm(vec![])) - } -} - -// Dummy implementation for AccountToLocation - use the unit type to satisfy the trait bounds parameter_types! { pub DefaultRewardKind: () = (); } @@ -719,16 +651,11 @@ impl RewardLedger for DummyRewardPayment { impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = EthereumBeaconClient; - type XcmSender = DummyXcmSender; - type XcmExecutor = DummyXcmExecutor; type GatewayAddress = EthereumGatewayAddress; - type AssetHubParaId = ConstU32<1000>; - type MessageConverter = DummyMessageConverter; type MessageProcessor = EigenLayerMessageProcessor; type RewardKind = (); type DefaultRewardKind = DefaultRewardKind; type RewardPayment = DummyRewardPayment; - type AccountToLocation = (); // Use the unit type which implements all the required traits type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; @@ -737,44 +664,14 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] pub mod benchmark_helpers { use crate::{EthereumBeaconClient, Runtime}; - use codec::Encode; use snowbridge_beacon_primitives::BeaconHeader; use snowbridge_pallet_inbound_queue_v2::BenchmarkHelper as InboundQueueBenchmarkHelperV2; // use snowbridge_pallet_outbound_queue_v2::BenchmarkHelper as OutboundQueueBenchmarkHelperV2; use sp_core::H256; - use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; impl InboundQueueBenchmarkHelperV2 for Runtime { fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) { EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap(); } } - - // impl OutboundQueueBenchmarkHelperV2 for Runtime { - // fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) { - // EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap(); - // } - // } - - pub struct DoNothingRouter; - impl SendXcm for DoNothingRouter { - type Ticket = Xcm<()>; - - fn validate( - _dest: &mut Option, - xcm: &mut Option>, - ) -> SendResult { - Ok((xcm.clone().unwrap(), Assets::new())) - } - fn deliver(xcm: Xcm<()>) -> Result { - let hash = xcm.using_encoded(sp_core::hashing::blake2_256); - Ok(hash) - } - } - - // impl snowbridge_pallet_system_v2::BenchmarkHelper for () { - // fn make_xcm_origin(location: Location) -> RuntimeOrigin { - // RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) - // } - // } }