refactor(operator): ♻️ Generalise further the MessageProcessor from the Inbound Pallet V2 (#46)

Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
This commit is contained in:
Ahmad Kaouk 2025-04-22 03:12:47 +03:00 committed by GitHub
parent 3fa11e8f91
commit 6ba0476e3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 160 additions and 189 deletions

View file

@ -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
test = false

View file

@ -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<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The verifier for inbound messages from Ethereum.
type Verifier: Verifier;
/// XCM message sender.
type XcmSender: SendXcm;
/// Handler for XCM fees.
type XcmExecutor: ExecuteXcm<Self::RuntimeCall>;
/// Address of the Gateway contract.
#[pallet::constant]
type GatewayAddress: Get<H160>;
/// AssetHub parachain ID.
type AssetHubParaId: Get<u32>;
/// Convert a command from Ethereum to an XCM message.
type MessageConverter: ConvertMessage;
/// Process the message that was submitted
type MessageProcessor: MessageProcessor<Self::AccountId>;
#[cfg(feature = "runtime-benchmarks")]
@ -105,8 +96,6 @@ pub mod pallet {
type DefaultRewardKind: Get<Self::RewardKind>;
/// Relayer reward payment.
type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
/// 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::<T>::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::<T>::get(nonce.into()), Error::<T>::InvalidNonce);
let xcm =
T::MessageConverter::convert(message).map_err(|error| Error::<T>::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::<T>::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::<T>::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<XcmHash, SendError> {
let (ticket, fee) = validate_send::<T::XcmSender>(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)
}
}
}

View file

@ -1,20 +1,127 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
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<T>(pub PhantomData<T>);
/// A message processor that simply returns the Blake2_256 hash of the SCALE encoded message
pub struct RemarkMessageProcessor<T>(pub PhantomData<T>);
impl<AccountId, T> MessageProcessor<AccountId> for DefaultMessageProcessor<T>
impl<AccountId, T> MessageProcessor<AccountId> for RemarkMessageProcessor<T>
where
T: crate::Config<AccountId = AccountId>,
{
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::<T>::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<T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>(
pub PhantomData<(
T,
Sender,
Executor,
Converter,
AccountToLocation,
AssetHubParaId,
)>,
);
impl<AccountId, T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>
MessageProcessor<AccountId>
for XcmMessageProcessor<T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>
where
T: crate::Config<AccountId = AccountId>,
Sender: SendXcm,
Executor: ExecuteXcm<T::RuntimeCall>,
Converter: ConvertMessage,
AccountToLocation: for<'a> TryConvert<&'a AccountId, Location>,
AssetHubParaId: Get<u32>,
{
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<T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>
XcmMessageProcessor<T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>
where
T: crate::Config,
Sender: SendXcm,
Executor: ExecuteXcm<T::RuntimeCall>,
Converter: ConvertMessage,
AccountToLocation: for<'a> TryConvert<&'a T::AccountId, Location>,
AssetHubParaId: Get<u32>,
{
/// Process a message and return the message ID
pub fn process_xcm(who: T::AccountId, message: Message) -> Result<XcmHash, DispatchError> {
// Convert the message to XCM
let xcm = Converter::convert(message).map_err(|error| Error::<T>::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::<T>::from(error)
})?;
// Return the message_id
Ok(message_id)
}
}
impl<T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>
XcmMessageProcessor<T, Sender, Executor, Converter, AccountToLocation, AssetHubParaId>
where
T: crate::Config,
Sender: SendXcm,
Executor: ExecuteXcm<T::RuntimeCall>,
Converter: ConvertMessage,
AccountToLocation: for<'a> TryConvert<&'a T::AccountId, Location>,
AssetHubParaId: Get<u32>,
{
fn send_xcm(
dest: Location,
fee_payer: &T::AccountId,
xcm: Xcm<()>,
) -> Result<XcmHash, SendError> {
let (ticket, fee) = validate_send::<Sender>(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)
}
}

View file

@ -2,9 +2,9 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
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<AccountId> 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<AccountId> 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<AccountId> 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<Test>, DummySuffix);
type MessageProcessor = (
DummyPrefix,
XcmMessageProcessor<
Test,
MockXcmSender,
MockXcmExecutor,
MessageToXcm<
CreateAssetCall,
CreateAssetDeposit,
EthereumNetwork,
InboundQueueLocation,
MockTokenIdConvert,
GatewayAddress,
UniversalLocation,
AssetHubFromEthereum,
>,
MockAccountLocationConverter<AccountId>,
ConstU32<1000>,
>,
DummySuffix,
);
#[cfg(feature = "runtime-benchmarks")]
type Helper = Test;
type WeightInfo = ();
type AccountToLocation = MockAccountLocationConverter<AccountId>;
type RewardKind = BridgeReward;
type DefaultRewardKind = SnowbridgeReward;
}

View file

@ -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)
}
}
}

View file

@ -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<AccountId> {
/// 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<AccountId> MessageProcessor<AccountId> 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 => {

View file

@ -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<XcmV5Location>,
_message: &mut Option<Xcm<()>>,
) -> Result<(Self::Ticket, Assets), SendError> {
Ok(((), Assets::new()))
}
fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
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<RuntimeCall> for DummyXcmExecutor {
type Prepared = EmptyPrepared;
fn prepare(_message: Xcm<RuntimeCall>) -> Result<Self::Prepared, Xcm<RuntimeCall>> {
Ok(EmptyPrepared)
}
fn execute(
_origin: impl Into<XcmV5Location>,
_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<XcmV5Location>, _fees: Assets) -> Result<(), XcmError> {
Ok(())
}
}
// Dummy implementation for MessageConverter
pub struct DummyMessageConverter;
impl ConvertMessage for DummyMessageConverter {
fn convert(_message: Message) -> Result<Xcm<()>, 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<AccountId, (), u128> 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<Runtime>;
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<T: snowbridge_pallet_inbound_queue_v2::Config> InboundQueueBenchmarkHelperV2<T> for Runtime {
fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) {
EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap();
}
}
// impl<T: snowbridge_pallet_outbound_queue_v2::Config> OutboundQueueBenchmarkHelperV2<T> 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<Location>,
xcm: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
Ok((xcm.clone().unwrap(), Assets::new()))
}
fn deliver(xcm: Xcm<()>) -> Result<XcmHash, SendError> {
let hash = xcm.using_encoded(sp_core::hashing::blake2_256);
Ok(hash)
}
}
// impl snowbridge_pallet_system_v2::BenchmarkHelper<RuntimeOrigin> for () {
// fn make_xcm_origin(location: Location) -> RuntimeOrigin {
// RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location))
// }
// }
}