feat(operator): Add Snowbridge V2 Outbound Queue pallet to the runtime (#49)

This pull request integrates the snowbridge-pallet-outbound-queue-v2 and
its dependencies into the DataHaven runtime. This pallet is responsible
for handling the submission, processing, and commitment of outbound
messages destined for the Ethereum network via the Snowbridge bridge
(v2).
### Key Changes
#### Added New Pallets
- **snowbridge-pallet-outbound-queue-v2**: The core pallet for managing
the outbound message lifecycle.
- **pallet-message-queue**: Introduced to handle the queuing and
processing of messages.
 
#### Added Crates to Datahaven codebase
- snowbridge-merkle-tree
- bridge-hub-common
- snowbridge-outbound-queue-v2-runtime-api

---------

Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
This commit is contained in:
Ahmad Kaouk 2025-04-26 02:28:41 +03:00 committed by GitHub
parent 3caf276ee1
commit f4a959f342
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2521 additions and 20 deletions

86
operator/Cargo.lock generated
View file

@ -1257,6 +1257,24 @@ dependencies = [
"trie-db",
]
[[package]]
name = "bridge-hub-common"
version = "0.13.1"
dependencies = [
"cumulus-primitives-core",
"frame-support",
"pallet-message-queue",
"parity-scale-codec",
"scale-info",
"snowbridge-core",
"sp-core",
"sp-runtime",
"sp-std",
"staging-xcm",
"staging-xcm-builder",
"staging-xcm-executor",
]
[[package]]
name = "bs58"
version = "0.5.1"
@ -2349,6 +2367,7 @@ dependencies = [
name = "datahaven-runtime"
version = "0.1.0"
dependencies = [
"bridge-hub-common",
"datahaven-runtime-common",
"dhp-bridge",
"fp-account",
@ -2376,6 +2395,7 @@ dependencies = [
"pallet-grandpa",
"pallet-identity",
"pallet-im-online",
"pallet-message-queue",
"pallet-mmr",
"pallet-multisig",
"pallet-offences",
@ -2396,8 +2416,12 @@ dependencies = [
"serde_json",
"snowbridge-beacon-primitives",
"snowbridge-inbound-queue-primitives",
"snowbridge-merkle-tree",
"snowbridge-outbound-queue-primitives",
"snowbridge-outbound-queue-v2-runtime-api",
"snowbridge-pallet-ethereum-client",
"snowbridge-pallet-inbound-queue-v2",
"snowbridge-pallet-outbound-queue-v2",
"snowbridge-verification-primitives",
"sp-api",
"sp-block-builder",
@ -2429,6 +2453,7 @@ dependencies = [
"frame-support",
"polkadot-primitives",
"polkadot-runtime-common",
"staging-xcm",
]
[[package]]
@ -11755,6 +11780,21 @@ dependencies = [
"staging-xcm-executor",
]
[[package]]
name = "snowbridge-merkle-tree"
version = "0.2.0"
dependencies = [
"array-bytes",
"hex",
"hex-literal 0.3.4",
"parity-scale-codec",
"scale-info",
"sp-core",
"sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2412)",
"sp-runtime",
"sp-tracing",
]
[[package]]
name = "snowbridge-milagro-bls"
version = "1.5.4"
@ -11796,6 +11836,21 @@ dependencies = [
"staging-xcm-executor",
]
[[package]]
name = "snowbridge-outbound-queue-v2-runtime-api"
version = "0.2.0"
dependencies = [
"frame-support",
"parity-scale-codec",
"scale-info",
"snowbridge-core",
"snowbridge-merkle-tree",
"snowbridge-outbound-queue-primitives",
"sp-api",
"sp-std",
"staging-xcm",
]
[[package]]
name = "snowbridge-pallet-ethereum-client"
version = "0.2.0"
@ -11880,6 +11935,37 @@ dependencies = [
"sp-std",
]
[[package]]
name = "snowbridge-pallet-outbound-queue-v2"
version = "0.2.0"
dependencies = [
"alloy-core",
"bp-relayers",
"bridge-hub-common",
"ethabi-decode",
"frame-benchmarking",
"frame-support",
"frame-system",
"hex-literal 0.3.4",
"pallet-message-queue",
"parity-scale-codec",
"scale-info",
"serde",
"snowbridge-core",
"snowbridge-inbound-queue-primitives",
"snowbridge-merkle-tree",
"snowbridge-outbound-queue-primitives",
"sp-arithmetic",
"sp-core",
"sp-io",
"sp-keyring",
"sp-runtime",
"sp-std",
"staging-xcm",
"staging-xcm-builder",
"staging-xcm-executor",
]
[[package]]
name = "snowbridge-test-utils"
version = "0.1.0"

View file

@ -10,6 +10,7 @@ members = [
"node",
"pallets/ethereum-client",
"pallets/inbound-queue-v2",
"pallets/outbound-queue-v2",
"pallets/validator-set",
"primitives/bridge",
"runtime",
@ -28,6 +29,7 @@ dhp-bridge = { path = "./primitives/bridge", default-features = false }
alloy-core = { version = "0.8.15", default-features = false }
alloy-primitives = { version = "0.4.2", default-features = false }
alloy-sol-types = { version = "0.4.2", default-features = false }
array-bytes = { version = "6.2.2", default-features = false }
async-trait = { version = "0.1.42" }
blake2-rfc = { version = "0.2.18", default-features = false }
byte-slice-cast = { version = "1.2.1", default-features = false }
@ -63,6 +65,7 @@ tracing = { version = "0.1.37", default-features = false }
# Polkadot
bp-relayers = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
cumulus-primitives-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
frame-executive = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
@ -79,6 +82,7 @@ pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch =
pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
pallet-im-online = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
pallet-message-queue = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
pallet-parameters = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
@ -118,6 +122,7 @@ sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk", branch = "
sp-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-crypto-hashing = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
@ -130,6 +135,7 @@ sp-staking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "sta
sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-storage = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sp-version = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
@ -149,6 +155,8 @@ sc-consensus-beefy-rpc = { git = "https://github.com/paritytech/polkadot-sdk", b
sp-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
# Snowbridge
bridge-hub-common = { path = "primitives/snowbridge/bridge-hub-common", default-features = false }
snowbridge-merkle-tree = { path = "primitives/snowbridge/merkle-tree", default-features = false }
snowbridge-beacon-primitives = { path = "primitives/snowbridge/beacon", default-features = false }
snowbridge-core = { path = "primitives/snowbridge/core", default-features = false }
snowbridge-ethereum = { path = "primitives/snowbridge/ethereum", default-features = false }
@ -158,6 +166,8 @@ snowbridge-inbound-queue-primitives = { path = "primitives/snowbridge/inbound-qu
snowbridge-outbound-queue-primitives = { path = "primitives/snowbridge/outbound-queue", default-features = false }
snowbridge-pallet-inbound-queue-v2 = { path = "pallets/inbound-queue-v2", default-features = false }
snowbridge-pallet-inbound-queue-v2-fixtures = { path = "pallets/inbound-queue-v2/fixtures", default-features = false }
snowbridge-pallet-outbound-queue-v2 = { path = "pallets/outbound-queue-v2", default-features = false }
snowbridge-outbound-queue-v2-runtime-api = { path = "pallets/outbound-queue-v2/runtime-api", default-features = false }
snowbridge-test-utils = { path = "primitives/snowbridge/test-utils", default-features = false }
snowbridge-verification-primitives = { path = "primitives/snowbridge/verification", default-features = false }

View file

@ -0,0 +1,93 @@
[package]
name = "snowbridge-pallet-outbound-queue-v2"
description = "Snowbridge Outbound Queue Pallet V2"
version = "0.2.0"
authors = ["Snowfork <contact@snowfork.com>"]
edition.workspace = true
repository.workspace = true
license = "Apache-2.0"
categories = ["cryptography::cryptocurrencies"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[package.metadata.polkadot-sdk]
exclude-from-umbrella = true
[dependencies]
alloy-core = { workspace = true, features = ["sol-types"] }
codec = { features = ["derive"], workspace = true }
ethabi = { workspace = true }
hex-literal = { workspace = true, default-features = true }
scale-info = { features = ["derive"], workspace = true }
serde = { features = ["alloc", "derive"], workspace = true }
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
bp-relayers = { workspace = true }
bridge-hub-common = { workspace = true }
snowbridge-core = { workspace = true }
snowbridge-merkle-tree = { workspace = true }
snowbridge-inbound-queue-primitives = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
[dev-dependencies]
pallet-message-queue = { workspace = true }
sp-keyring = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"alloy-core/std",
"bp-relayers/std",
"bridge-hub-common/std",
"codec/std",
"ethabi/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"pallet-message-queue/std",
"scale-info/std",
"serde/std",
"snowbridge-core/std",
"snowbridge-merkle-tree/std",
"snowbridge-outbound-queue-primitives/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
]
runtime-benchmarks = [
"bridge-hub-common/runtime-benchmarks",
"frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"snowbridge-core/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-message-queue/try-runtime",
"sp-runtime/try-runtime",
]

View file

@ -0,0 +1,40 @@
[package]
name = "snowbridge-outbound-queue-v2-runtime-api"
description = "Snowbridge Outbound Queue Runtime API V2"
version = "0.2.0"
authors = ["Snowfork <contact@snowfork.com>"]
edition.workspace = true
repository.workspace = true
license = "Apache-2.0"
categories = ["cryptography::cryptocurrencies"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[package.metadata.polkadot-sdk]
exclude-from-umbrella = true
[dependencies]
codec = { features = ["derive"], workspace = true }
frame-support = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
snowbridge-core = { workspace = true }
snowbridge-merkle-tree = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
sp-api = { workspace = true }
sp-std = { workspace = true }
xcm = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"scale-info/std",
"snowbridge-core/std",
"snowbridge-merkle-tree/std",
"snowbridge-outbound-queue-primitives/std",
"sp-api/std",
"sp-std/std",
"xcm/std",
]

View file

@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Ethereum Outbound Queue V2 Runtime API
//!
//! * `prove_message`: Generate a merkle proof for a committed message
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::traits::tokens::Balance as BalanceT;
use snowbridge_merkle_tree::MerkleProof;
sp_api::decl_runtime_apis! {
pub trait OutboundQueueV2Api<Balance> where Balance: BalanceT
{
/// Generate a merkle proof for a committed message identified by `leaf_index`.
fn prove_message(leaf_index: u64) -> Option<MerkleProof>;
}
}

View file

@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Helpers for implementing runtime api
use crate::{Config, MessageLeaves};
use frame_support::storage::StorageStreamIter;
use snowbridge_merkle_tree::{merkle_proof, MerkleProof};
pub fn prove_message<T>(leaf_index: u64) -> Option<MerkleProof>
where
T: Config,
{
if !MessageLeaves::<T>::exists() {
return None;
}
let proof =
merkle_proof::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter(), leaf_index);
Some(proof)
}

View file

@ -0,0 +1,151 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use bridge_hub_common::AggregateMessageOrigin;
use codec::Encode;
use frame_benchmarking::v2::*;
use frame_support::{traits::Hooks, BoundedVec};
use snowbridge_outbound_queue_primitives::v2::{Command, Initializer, Message};
use sp_core::{H160, H256};
#[allow(unused_imports)]
use crate::Pallet as OutboundQueue;
#[benchmarks(
where
<T as Config>::MaxMessagePayloadSize: Get<u32>,
)]
mod benchmarks {
use super::*;
/// Build `Upgrade` message with `MaxMessagePayloadSize`, in the worst-case.
fn build_message<T: Config>() -> (Message, OutboundMessage) {
let commands = vec![Command::Upgrade {
impl_address: H160::zero(),
impl_code_hash: H256::zero(),
initializer: Initializer {
params: core::iter::repeat_with(|| 1_u8)
.take(<T as Config>::MaxMessagePayloadSize::get() as usize)
.collect(),
maximum_required_gas: 200_000,
},
}];
let message = Message {
origin: Default::default(),
id: H256::default(),
fee: 0,
commands: BoundedVec::try_from(commands.clone()).unwrap(),
};
let wrapped_commands: Vec<OutboundCommandWrapper> = commands
.into_iter()
.map(|command| OutboundCommandWrapper {
kind: command.index(),
gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
payload: command.abi_encode(),
})
.collect();
let outbound_message = OutboundMessage {
origin: Default::default(),
nonce: 1,
topic: H256::default(),
commands: wrapped_commands.clone().try_into().unwrap(),
};
(message, outbound_message)
}
/// Initialize `MaxMessagesPerBlock` messages need to be committed, in the worst-case.
fn initialize_worst_case<T: Config>() {
for _ in 0..T::MaxMessagesPerBlock::get() {
initialize_with_one_message::<T>();
}
}
/// Initialize with a single message
fn initialize_with_one_message<T: Config>() {
let (message, outbound_message) = build_message::<T>();
let leaf = <T as Config>::Hashing::hash(&message.encode());
MessageLeaves::<T>::append(leaf);
Messages::<T>::append(outbound_message);
}
/// Benchmark for processing a message.
#[benchmark]
fn do_process_message() -> Result<(), BenchmarkError> {
let (enqueued_message, _) = build_message::<T>();
let origin = AggregateMessageOrigin::SnowbridgeV2([1; 32].into());
let message = enqueued_message.encode();
#[block]
{
let _ = OutboundQueue::<T>::do_process_message(origin, &message).unwrap();
}
assert_eq!(MessageLeaves::<T>::decode_len().unwrap(), 1);
Ok(())
}
/// Benchmark for producing final messages commitment, in the worst-case
#[benchmark]
fn commit() -> Result<(), BenchmarkError> {
initialize_worst_case::<T>();
#[block]
{
OutboundQueue::<T>::commit();
}
Ok(())
}
/// Benchmark for producing commitment for a single message, used to estimate the delivery
/// cost. The assumption is that cost of commit a single message is even higher than the average
/// cost of commit all messages.
#[benchmark]
fn commit_single() -> Result<(), BenchmarkError> {
initialize_with_one_message::<T>();
#[block]
{
OutboundQueue::<T>::commit();
}
Ok(())
}
/// Benchmark for `on_initialize` in the worst-case
#[benchmark]
fn on_initialize() -> Result<(), BenchmarkError> {
initialize_worst_case::<T>();
#[block]
{
OutboundQueue::<T>::on_initialize(1_u32.into());
}
Ok(())
}
/// Benchmark the entire process flow in the worst-case. This can be used to determine
/// appropriate values for the configuration parameters `MaxMessagesPerBlock` and
/// `MaxMessagePayloadSize`
#[benchmark]
fn process() -> Result<(), BenchmarkError> {
initialize_worst_case::<T>();
let origin = AggregateMessageOrigin::SnowbridgeV2([1; 32].into());
let (enqueued_message, _) = build_message::<T>();
let message = enqueued_message.encode();
#[block]
{
OutboundQueue::<T>::on_initialize(1_u32.into());
for _ in 0..T::MaxMessagesPerBlock::get() {
OutboundQueue::<T>::do_process_message(origin, &message).unwrap();
}
OutboundQueue::<T>::commit();
}
Ok(())
}
impl_benchmark_test_suite!(OutboundQueue, crate::mock::new_tester(), crate::mock::Test,);
}

View file

@ -0,0 +1,414 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Pallet for committing outbound messages for delivery to Ethereum
//!
//! # Overview
//!
//! Messages come either from sibling parachains via XCM, or BridgeHub itself
//! via the `snowbridge-pallet-system-v2`:
//!
//! 1. `snowbridge_outbound_queue_primitives::v2::EthereumBlobExporter::deliver`
//! 2. `snowbridge_pallet_system_v2::Pallet::send`
//!
//! The message submission pipeline works like this:
//! 1. The message is first validated via the implementation for
//! [`snowbridge_outbound_queue_primitives::v2::SendMessage::validate`]
//! 2. The message is then enqueued for later processing via the implementation for
//! [`snowbridge_outbound_queue_primitives::v2::SendMessage::deliver`]
//! 3. The underlying message queue is implemented by [`Config::MessageQueue`]
//! 4. The message queue delivers messages to this pallet via the implementation for
//! [`frame_support::traits::ProcessMessage::process_message`]
//! 5. The message is processed in `Pallet::do_process_message`:
//! a. Convert to `OutboundMessage`, and stored into the `Messages` vector storage
//! b. ABI-encode the `OutboundMessage` and store the committed Keccak256 hash in `MessageLeaves`
//! c. Generate `PendingOrder` with assigned nonce and fee attached, stored into the
//! `PendingOrders` map storage, with nonce as the key
//! d. Increment nonce and update the `Nonce` storage
//! 6. At the end of the block, a merkle root is constructed from all the leaves in `MessageLeaves`.
//! At the beginning of the next block, both `Messages` and `MessageLeaves` are dropped so that
//! state at each block only holds the messages processed in that block.
//! 7. This merkle root is inserted into the parachain header as a digest item
//! 8. Offchain relayers are able to relay the message to Ethereum after:
//! a. Generating a merkle proof for the committed message using the `prove_message` runtime API
//! b. Reading the actual message content from the `Messages` vector in storage
//! 9. On the Ethereum side, the message root is ultimately the thing being verified by the Beefy
//! light client.
//! 10. When the message has been verified and executed, the relayer will call the extrinsic
//! `submit_delivery_receipt` to:
//! a. Verify the message with proof for a transaction receipt containing the event log,
//! same as the inbound queue verification flow
//! b. Fetch the pending order by nonce of the message, pay reward with fee attached in the order
//! c. Remove the order from `PendingOrders` map storage by nonce
//!
//!
//! # Extrinsics
//!
//! * [`Call::submit_delivery_receipt`]: Submit delivery proof
//!
//! # Runtime API
//!
//! * `prove_message`: Generate a merkle proof for a committed message
#![cfg_attr(not(feature = "std"), no_std)]
pub mod api;
pub mod process_message_impl;
pub mod send_message_impl;
pub mod types;
pub mod weights;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod test;
use alloy_core::{
primitives::{Bytes, FixedBytes},
sol_types::SolValue,
};
use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem};
use codec::Decode;
use frame_support::{
storage::StorageStreamIter,
traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError},
weights::{Weight, WeightToFee},
};
use snowbridge_core::{BasicOperatingMode, TokenId};
use snowbridge_inbound_queue_primitives::RewardLedger;
use snowbridge_merkle_tree::merkle_root;
use snowbridge_outbound_queue_primitives::{
v2::{
abi::{CommandWrapper, OutboundMessageWrapper},
DeliveryReceipt, GasMeter, Message, OutboundCommandWrapper, OutboundMessage,
},
EventProof, VerificationError, Verifier,
};
use sp_core::{H160, H256};
use sp_runtime::{
traits::{BlockNumberProvider, Hash, MaybeEquivalence},
DigestItem,
};
use sp_std::prelude::*;
pub use types::{OnNewCommitment, PendingOrder, ProcessMessageOriginOf};
pub use weights::WeightInfo;
use xcm::latest::{Location, NetworkId};
type DeliveryReceiptOf<T> = DeliveryReceipt<<T as frame_system::Config>::AccountId>;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Hashing: Hash<Output = H256>;
type MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
/// Measures the maximum gas used to execute a command on Ethereum
type GasMeter: GasMeter;
type Balance: Balance + From<u128>;
/// Max bytes in a message payload
#[pallet::constant]
type MaxMessagePayloadSize: Get<u32>;
/// Max number of messages processed per block
#[pallet::constant]
type MaxMessagesPerBlock: Get<u32>;
/// Hook that is called whenever there is a new commitment.
type OnNewCommitment: OnNewCommitment;
/// Convert a weight value into a deductible fee based.
type WeightToFee: WeightToFee<Balance = Self::Balance>;
/// Weight information for extrinsics in this pallet
type WeightInfo: WeightInfo;
/// The verifier for delivery proof from Ethereum
type Verifier: Verifier;
/// Address of the Gateway contract
#[pallet::constant]
type GatewayAddress: Get<H160>;
/// Reward discriminator type.
type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
/// The default RewardKind discriminator for rewards allocated to relayers from this pallet.
#[pallet::constant]
type DefaultRewardKind: Get<Self::RewardKind>;
/// Relayer reward payment.
type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
/// Ethereum NetworkId
type EthereumNetwork: Get<NetworkId>;
type ConvertAssetId: MaybeEquivalence<TokenId, Location>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Message has been queued and will be processed in the future
MessageQueued {
/// The message
message: Message,
},
/// Message will be committed at the end of current block. From now on, to track the
/// progress the message, use the `nonce` or the `id`.
MessageAccepted {
/// ID of the message
id: H256,
/// The nonce assigned to this message
nonce: u64,
},
/// Some messages have been committed
MessagesCommitted {
/// Merkle root of the committed messages
root: H256,
/// number of committed messages
count: u64,
},
/// Set OperatingMode
OperatingModeChanged { mode: BasicOperatingMode },
/// Delivery Proof received
MessageDeliveryProofReceived { nonce: u64 },
}
#[pallet::error]
pub enum Error<T> {
/// The message is too large
MessageTooLarge,
/// The pallet is halted
Halted,
/// Invalid Channel
InvalidChannel,
/// Invalid Envelope
InvalidEnvelope,
/// Message verification error
Verification(VerificationError),
/// Invalid Gateway
InvalidGateway,
/// Pending nonce does not exist
InvalidPendingNonce,
/// Reward payment failed
RewardPaymentFailed,
}
/// Messages to be committed in the current block. This storage value is killed in
/// `on_initialize`, so will not end up bloating state.
///
/// Is never read in the runtime, only by offchain message relayers.
/// Because of this, it will never go into the PoV of a block.
///
/// Inspired by the `frame_system::Pallet::Events` storage value
#[pallet::storage]
#[pallet::unbounded]
pub(super) type Messages<T: Config> = StorageValue<_, Vec<OutboundMessage>, ValueQuery>;
/// Hashes of the ABI-encoded messages in the [`Messages`] storage value. Used to generate a
/// merkle root during `on_finalize`. This storage value is killed in `on_initialize`, so state
/// at each block contains only root hash of messages processed in that block. This also means
/// it doesn't have to be included in PoV.
#[pallet::storage]
#[pallet::unbounded]
pub(super) type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
/// The current nonce for the messages
#[pallet::storage]
pub type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;
/// Pending orders to relay
#[pallet::storage]
pub type PendingOrders<T: Config> =
StorageMap<_, Twox64Concat, u64, PendingOrder<BlockNumberFor<T>>, OptionQuery>;
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
// Remove storage from previous block
Messages::<T>::kill();
MessageLeaves::<T>::kill();
// Reserve some weight for the `on_finalize` handler
T::WeightInfo::on_initialize() + T::WeightInfo::commit()
}
fn on_finalize(_: BlockNumberFor<T>) {
Self::commit();
}
}
#[pallet::call]
impl<T: Config> Pallet<T>
where
T::AccountId: From<[u8; 32]>,
{
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::submit_delivery_receipt())]
pub fn submit_delivery_receipt(
origin: OriginFor<T>,
event: Box<EventProof>,
) -> DispatchResult {
let relayer = ensure_signed(origin)?;
// submit message to verifier for verification
T::Verifier::verify(&event.event_log, &event.proof)
.map_err(|e| Error::<T>::Verification(e))?;
let receipt = DeliveryReceiptOf::<T>::try_from(&event.event_log)
.map_err(|_| Error::<T>::InvalidEnvelope)?;
Self::process_delivery_receipt(relayer, receipt)
}
}
impl<T: Config> Pallet<T> {
/// Generate a messages commitment and insert it into the header digest
pub(crate) fn commit() {
let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
if count == 0 {
return;
}
// Create merkle root of messages
let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
let digest_item: DigestItem = CustomDigestItem::SnowbridgeV2(root).into();
// Insert merkle root into the header digest
<frame_system::Pallet<T>>::deposit_log(digest_item);
T::OnNewCommitment::on_new_commitment(root);
Self::deposit_event(Event::MessagesCommitted { root, count });
}
/// Process a message delivered by the MessageQueue pallet
pub(crate) fn do_process_message(
_: ProcessMessageOriginOf<T>,
mut message: &[u8],
) -> Result<bool, ProcessMessageError> {
use ProcessMessageError::*;
// Yield if the maximum number of messages has been processed this block.
// This ensures that the weight of `on_finalize` has a known maximum bound.
ensure!(
MessageLeaves::<T>::decode_len().unwrap_or(0)
< T::MaxMessagesPerBlock::get() as usize,
Yield
);
let nonce = Nonce::<T>::get();
// Decode bytes into Message
let Message {
origin,
id,
fee,
commands,
} = Message::decode(&mut message).map_err(|_| Corrupt)?;
// Convert it to OutboundMessage and save into Messages storage
let commands: Vec<OutboundCommandWrapper> = commands
.into_iter()
.map(|command| OutboundCommandWrapper {
kind: command.index(),
gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
payload: command.abi_encode(),
})
.collect();
let outbound_message = OutboundMessage {
origin,
nonce,
topic: id,
commands: commands.clone().try_into().map_err(|_| Corrupt)?,
};
Messages::<T>::append(outbound_message);
// Convert it to an OutboundMessageWrapper (in ABI format), hash it using Keccak256 to
// generate a committed hash, and store it in MessageLeaves storage which can be
// verified on Ethereum later.
let abi_commands: Vec<CommandWrapper> = commands
.into_iter()
.map(|command| CommandWrapper {
kind: command.kind,
gas: command.gas,
payload: Bytes::from(command.payload),
})
.collect();
let committed_message = OutboundMessageWrapper {
origin: FixedBytes::from(origin.as_fixed_bytes()),
nonce,
topic: FixedBytes::from(id.as_fixed_bytes()),
commands: abi_commands,
};
let message_abi_encoded_hash =
<T as Config>::Hashing::hash(&committed_message.abi_encode());
MessageLeaves::<T>::append(message_abi_encoded_hash);
// Generate `PendingOrder` with fee attached in the message, stored
// into the `PendingOrders` map storage, with assigned nonce as the key.
// When the message is processed on ethereum side, the relayer will send the nonce
// back with delivery proof, only after that the order can
// be resolved and the fee will be rewarded to the relayer.
let order = PendingOrder {
nonce,
fee,
block_number: frame_system::Pallet::<T>::current_block_number(),
};
<PendingOrders<T>>::insert(nonce, order);
Nonce::<T>::set(nonce.checked_add(1).ok_or(Unsupported)?);
Self::deposit_event(Event::MessageAccepted { id, nonce });
Ok(true)
}
/// Process a delivery receipt from a relayer, to allocate the relayer reward.
pub fn process_delivery_receipt(
relayer: <T as frame_system::Config>::AccountId,
receipt: DeliveryReceiptOf<T>,
) -> DispatchResult
where
<T as frame_system::Config>::AccountId: From<[u8; 32]>,
{
// Verify that the message was submitted from the known Gateway contract
ensure!(
T::GatewayAddress::get() == receipt.gateway,
Error::<T>::InvalidGateway
);
let nonce = receipt.nonce;
let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::InvalidPendingNonce)?;
if order.fee > 0 {
// Pay relayer reward
T::RewardPayment::register_reward(&relayer, T::DefaultRewardKind::get(), order.fee);
}
<PendingOrders<T>>::remove(nonce);
Self::deposit_event(Event::MessageDeliveryProofReceived { nonce });
Ok(())
}
/// The local component of the message processing fees in native currency
pub(crate) fn calculate_local_fee() -> T::Balance {
T::WeightToFee::weight_to_fee(
&T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()),
)
}
}
}

View file

@ -0,0 +1,256 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use frame_support::{
derive_impl, parameter_types,
traits::{Everything, Hooks},
weights::IdentityFee,
BoundedVec,
};
use codec::{DecodeWithMemTracking, Encode, MaxEncodedLen};
use hex_literal::hex;
use scale_info::TypeInfo;
use snowbridge_core::{
gwei, meth,
pricing::{PricingParameters, Rewards},
AgentId, AgentIdOf, ParaId,
};
use snowbridge_outbound_queue_primitives::{v2::*, Log, Proof, VerificationError, Verifier};
use sp_core::{ConstU32, H160, H256};
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup, Keccak256},
AccountId32, BuildStorage, FixedU128,
};
use sp_std::marker::PhantomData;
use xcm::prelude::Here;
use xcm_executor::traits::ConvertLocation;
type Block = frame_system::mocking::MockBlock<Test>;
type AccountId = AccountId32;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system::{Pallet, Call, Storage, Event<T>},
MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event<T>},
OutboundQueue: crate::{Pallet, Storage, Event<T>},
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = Everything;
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeTask = RuntimeTask;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type PalletInfo = PalletInfo;
type Nonce = u64;
type Block = Block;
}
parameter_types! {
pub const HeapSize: u32 = 32 * 1024;
pub const MaxStale: u32 = 32;
pub static ServiceWeight: Option<Weight> = Some(Weight::from_parts(100, 100));
}
impl pallet_message_queue::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type MessageProcessor = OutboundQueue;
type Size = u32;
type QueueChangeHandler = ();
type HeapSize = HeapSize;
type MaxStale = MaxStale;
type ServiceWeight = ServiceWeight;
type IdleMaxServiceWeight = ();
type QueuePausedQuery = ();
}
// Mock verifier
pub struct MockVerifier;
impl Verifier for MockVerifier {
fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> {
Ok(())
}
}
const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"];
const WETH: [u8; 20] = hex!["C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"];
parameter_types! {
pub const OwnParaId: ParaId = ParaId::new(1013);
pub Parameters: PricingParameters<u128> = PricingParameters {
exchange_rate: FixedU128::from_rational(1, 400),
fee_per_gas: gwei(20),
rewards: Rewards { local: DOT, remote: meth(1) },
multiplier: FixedU128::from_rational(4, 3),
};
pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS);
pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
pub DefaultMyRewardKind: BridgeReward = BridgeReward::Snowbridge;
}
pub const DOT: u128 = 10_000_000_000;
/// Showcasing that we can handle multiple different rewards with the same pallet.
#[derive(
Clone,
Copy,
Debug,
Decode,
DecodeWithMemTracking,
Encode,
Eq,
MaxEncodedLen,
PartialEq,
TypeInfo,
)]
pub enum BridgeReward {
/// Rewards for Snowbridge.
Snowbridge,
}
impl RewardLedger<<mock::Test as frame_system::Config>::AccountId, BridgeReward, u128> for () {
fn register_reward(
_relayer: &<mock::Test as frame_system::Config>::AccountId,
_reward: BridgeReward,
_reward_balance: u128,
) {
}
}
impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Verifier = MockVerifier;
type GatewayAddress = GatewayAddress;
type Hashing = Keccak256;
type MessageQueue = MessageQueue;
type MaxMessagePayloadSize = ConstU32<1024>;
type MaxMessagesPerBlock = ConstU32<20>;
type GasMeter = ConstantGasMeter;
type Balance = u128;
type WeightToFee = IdentityFee<u128>;
type WeightInfo = ();
type RewardPayment = ();
type ConvertAssetId = ();
type EthereumNetwork = EthereumNetwork;
type RewardKind = BridgeReward;
type DefaultRewardKind = DefaultMyRewardKind;
type OnNewCommitment = ();
}
fn setup() {
System::set_block_number(1);
}
pub fn new_tester() -> sp_io::TestExternalities {
let storage = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap();
let mut ext: sp_io::TestExternalities = storage.into();
ext.execute_with(setup);
ext
}
pub fn run_to_end_of_next_block() {
// finish current block
MessageQueue::on_finalize(System::block_number());
OutboundQueue::on_finalize(System::block_number());
System::on_finalize(System::block_number());
// start next block
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
OutboundQueue::on_initialize(System::block_number());
MessageQueue::on_initialize(System::block_number());
// finish next block
MessageQueue::on_finalize(System::block_number());
OutboundQueue::on_finalize(System::block_number());
System::on_finalize(System::block_number());
}
pub fn bridge_hub_root_origin() -> AgentId {
AgentIdOf::convert_location(&Here.into()).unwrap()
}
pub fn mock_governance_message<T>() -> Message
where
T: Config,
{
let _marker = PhantomData::<T>; // for clippy
Message {
origin: bridge_hub_root_origin(),
id: Default::default(),
fee: 0,
commands: BoundedVec::try_from(vec![Command::Upgrade {
impl_address: Default::default(),
impl_code_hash: Default::default(),
initializer: Initializer {
params: (0..512).map(|_| 1u8).collect::<Vec<u8>>(),
maximum_required_gas: 0,
},
}])
.unwrap(),
}
}
// Message should fail validation as it is too large
pub fn mock_invalid_governance_message<T>() -> Message
where
T: Config,
{
let _marker = PhantomData::<T>; // for clippy
Message {
origin: Default::default(),
id: Default::default(),
fee: 0,
commands: BoundedVec::try_from(vec![Command::Upgrade {
impl_address: H160::zero(),
impl_code_hash: H256::zero(),
initializer: Initializer {
params: (0..1000).map(|_| 1u8).collect::<Vec<u8>>(),
maximum_required_gas: 0,
},
}])
.unwrap(),
}
}
pub fn mock_message(sibling_para_id: u32) -> Message {
Message {
origin: H256::from_low_u64_be(sibling_para_id as u64),
id: H256::from_low_u64_be(1),
fee: 1_000,
commands: BoundedVec::try_from(vec![Command::UnlockNativeToken {
token: H160(WETH),
recipient: H160(GATEWAY_ADDRESS),
amount: 1_000_000,
}])
.unwrap(),
}
}
pub fn mock_register_token_message(sibling_para_id: u32) -> Message {
Message {
origin: H256::from_low_u64_be(sibling_para_id as u64),
id: H256::from_low_u64_be(1),
fee: 1_000,
commands: BoundedVec::try_from(vec![Command::RegisterForeignToken {
token_id: H256::from_low_u64_be(1),
name: vec![],
symbol: vec![],
decimals: 12,
}])
.unwrap(),
}
}

View file

@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Implementation for [`frame_support::traits::ProcessMessage`]
use super::*;
use crate::weights::WeightInfo;
use frame_support::{
traits::{ProcessMessage, ProcessMessageError},
weights::WeightMeter,
};
impl<T: Config> ProcessMessage for Pallet<T> {
type Origin = AggregateMessageOrigin;
fn process_message(
message: &[u8],
origin: Self::Origin,
meter: &mut WeightMeter,
_: &mut [u8; 32],
) -> Result<bool, ProcessMessageError> {
let weight = T::WeightInfo::do_process_message();
if meter.try_consume(weight).is_err() {
return Err(ProcessMessageError::Overweight(weight));
}
Self::do_process_message(origin, message)
}
}

View file

@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Implementation for [`snowbridge_outbound_queue_primitives::v2::SendMessage`]
use super::*;
use bridge_hub_common::AggregateMessageOrigin;
use codec::Encode;
use frame_support::{
ensure,
traits::{EnqueueMessage, Get},
};
use snowbridge_outbound_queue_primitives::{
v2::{Message, SendMessage},
SendError, SendMessageFeeProvider,
};
use sp_core::H256;
use sp_runtime::BoundedVec;
impl<T> SendMessage for Pallet<T>
where
T: Config,
{
type Ticket = Message;
fn validate(message: &Message) -> Result<Self::Ticket, SendError> {
// The inner payload should not be too large
let payload = message.encode();
ensure!(
payload.len() < T::MaxMessagePayloadSize::get() as usize,
SendError::MessageTooLarge
);
Ok(message.clone())
}
fn deliver(ticket: Self::Ticket) -> Result<H256, SendError> {
let origin = AggregateMessageOrigin::SnowbridgeV2(ticket.origin);
let message =
BoundedVec::try_from(ticket.encode()).map_err(|_| SendError::MessageTooLarge)?;
T::MessageQueue::enqueue_message(message.as_bounded_slice(), origin);
Self::deposit_event(Event::MessageQueued {
message: ticket.clone(),
});
Ok(ticket.id)
}
}
impl<T: Config> SendMessageFeeProvider for Pallet<T> {
type Balance = T::Balance;
/// The local component of the message processing fees in native currency
fn local_fee() -> Self::Balance {
Self::calculate_local_fee()
}
}

View file

@ -0,0 +1,272 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{mock::*, *};
use alloy_core::primitives::FixedBytes;
use frame_support::{
assert_err, assert_noop, assert_ok,
traits::{Hooks, ProcessMessage, ProcessMessageError},
weights::WeightMeter,
BoundedVec,
};
use codec::Encode;
use hex_literal::hex;
use snowbridge_core::{ChannelId, ParaId};
use snowbridge_outbound_queue_primitives::{
v2::{abi::OutboundMessageWrapper, Command, Initializer, SendMessage},
SendError,
};
use sp_core::{hexdisplay::HexDisplay, H256};
#[test]
fn submit_messages_and_commit() {
new_tester().execute_with(|| {
for para_id in 1000..1004 {
let message = mock_message(para_id);
let ticket = OutboundQueue::validate(&message).unwrap();
assert_ok!(OutboundQueue::deliver(ticket));
}
ServiceWeight::set(Some(Weight::MAX));
run_to_end_of_next_block();
assert_eq!(Nonce::<Test>::get(), 4);
let digest = System::digest();
let digest_items = digest.logs();
assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some());
assert_eq!(Messages::<Test>::decode_len(), Some(4));
});
}
#[test]
fn submit_message_fail_too_large() {
new_tester().execute_with(|| {
let message = mock_invalid_governance_message::<Test>();
assert_err!(
OutboundQueue::validate(&message),
SendError::MessageTooLarge
);
});
}
#[test]
fn commit_exits_early_if_no_processed_messages() {
new_tester().execute_with(|| {
// on_finalize should do nothing, nor should it panic
OutboundQueue::on_finalize(System::block_number());
let digest = System::digest();
let digest_items = digest.logs();
assert_eq!(digest_items.len(), 0);
});
}
#[test]
fn process_message_yields_on_max_messages_per_block() {
new_tester().execute_with(|| {
for _ in 0..<Test as Config>::MaxMessagesPerBlock::get() {
MessageLeaves::<Test>::append(H256::zero())
}
let _channel_id: ChannelId = ParaId::from(1000).into();
let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero());
let message = Message {
origin: Default::default(),
id: Default::default(),
fee: 0,
commands: BoundedVec::try_from(vec![Command::Upgrade {
impl_address: Default::default(),
impl_code_hash: Default::default(),
initializer: Initializer {
params: (0..512).map(|_| 1u8).collect::<Vec<u8>>(),
maximum_required_gas: 0,
},
}])
.unwrap(),
};
let mut meter = WeightMeter::new();
assert_noop!(
OutboundQueue::process_message(
message.encode().as_slice(),
origin,
&mut meter,
&mut [0u8; 32]
),
ProcessMessageError::Yield
);
})
}
#[test]
fn process_message_fails_on_max_nonce_reached() {
new_tester().execute_with(|| {
let sibling_id = 1000;
let _channel_id: ChannelId = ParaId::from(sibling_id).into();
let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero());
let message: Message = mock_message(sibling_id);
let mut meter = WeightMeter::with_limit(Weight::MAX);
Nonce::<Test>::set(u64::MAX);
let result = OutboundQueue::process_message(
message.encode().as_slice(),
origin,
&mut meter,
&mut [0u8; 32],
);
assert_err!(result, ProcessMessageError::Unsupported)
})
}
#[test]
fn process_message_fails_on_overweight_message() {
new_tester().execute_with(|| {
let sibling_id = 1000;
let _channel_id: ChannelId = ParaId::from(sibling_id).into();
let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero());
let message: Message = mock_message(sibling_id);
let mut meter = WeightMeter::with_limit(Weight::from_parts(1, 1));
assert_noop!(
OutboundQueue::process_message(
message.encode().as_slice(),
origin,
&mut meter,
&mut [0u8; 32]
),
ProcessMessageError::Overweight(<Test as Config>::WeightInfo::do_process_message())
);
})
}
#[test]
fn governance_message_not_processed_in_same_block_when_queue_congested_with_low_priority_messages()
{
use AggregateMessageOrigin::*;
let sibling_id: u32 = 1000;
new_tester().execute_with(|| {
// submit a lot of low priority messages from asset_hub which will need multiple blocks to
// execute(20 messages for each block so 40 required at least 2 blocks)
let max_messages = 40;
for _ in 0..max_messages {
// submit low priority message
let message = mock_message(sibling_id);
let ticket = OutboundQueue::validate(&message).unwrap();
OutboundQueue::deliver(ticket).unwrap();
}
let footprint =
MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
assert_eq!(footprint.storage.count, (max_messages) as u64);
let message = mock_governance_message::<Test>();
let ticket = OutboundQueue::validate(&message).unwrap();
OutboundQueue::deliver(ticket).unwrap();
// move to next block
ServiceWeight::set(Some(Weight::MAX));
run_to_end_of_next_block();
// first process 20 messages from sibling channel
let footprint =
MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
assert_eq!(footprint.storage.count, 40 - 20);
// and governance message does not have the chance to execute in same block
let footprint = MessageQueue::footprint(SnowbridgeV2(bridge_hub_root_origin()));
assert_eq!(footprint.storage.count, 1);
// move to next block
ServiceWeight::set(Some(Weight::MAX));
run_to_end_of_next_block();
// now governance message get executed in this block
let footprint = MessageQueue::footprint(SnowbridgeV2(bridge_hub_root_origin()));
assert_eq!(footprint.storage.count, 0);
// and this time process 19 messages from sibling channel so we have 1 message left
let footprint =
MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
assert_eq!(footprint.storage.count, 1);
// move to the next block, the last 1 message from sibling channel get executed
ServiceWeight::set(Some(Weight::MAX));
run_to_end_of_next_block();
let footprint =
MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
assert_eq!(footprint.storage.count, 0);
});
}
#[test]
fn encode_digest_item_with_correct_index() {
new_tester().execute_with(|| {
let digest_item: DigestItem = CustomDigestItem::Snowbridge(H256::default()).into();
let enum_prefix = match digest_item {
DigestItem::Other(data) => data[0],
_ => u8::MAX,
};
assert_eq!(enum_prefix, 0);
});
}
#[test]
fn encode_digest_item() {
new_tester().execute_with(|| {
let digest_item: DigestItem = CustomDigestItem::Snowbridge([5u8; 32].into()).into();
let digest_item_raw = digest_item.encode();
assert_eq!(digest_item_raw[0], 0); // DigestItem::Other
assert_eq!(digest_item_raw[2], 0); // CustomDigestItem::Snowbridge
assert_eq!(
digest_item_raw,
[
0, 132, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5
]
);
});
}
fn encode_mock_message(message: Message) -> Vec<u8> {
let commands: Vec<CommandWrapper> = message
.commands
.into_iter()
.map(|command| CommandWrapper {
kind: command.index(),
gas: <Test as Config>::GasMeter::maximum_dispatch_gas_used_at_most(&command),
payload: Bytes::from(command.abi_encode()),
})
.collect();
// print the abi-encoded message and decode with solidity test
let committed_message = OutboundMessageWrapper {
origin: FixedBytes::from(message.origin.as_fixed_bytes()),
nonce: 1,
topic: FixedBytes::from(message.id.as_fixed_bytes()),
commands,
};
let message_abi_encoded = committed_message.abi_encode();
message_abi_encoded
}
#[test]
fn encode_unlock_message() {
let message: Message = mock_message(1000);
let message_abi_encoded = encode_mock_message(message);
println!("{}", HexDisplay::from(&message_abi_encoded));
assert_eq!(hex!("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000eda338e4dc46038493b885327842fd3e301cab3900000000000000000000000000000000000000000000000000000000000f4240").to_vec(), message_abi_encoded)
}
#[test]
fn encode_register_pna() {
let message: Message = mock_register_token_message(1000);
let message_abi_encoded = encode_mock_message(message);
println!("{}", HexDisplay::from(&message_abi_encoded));
assert_eq!(hex!("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000124f80000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec(), message_abi_encoded)
}

View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::Pallet;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::traits::ProcessMessage;
use scale_info::TypeInfo;
pub use snowbridge_merkle_tree::MerkleProof;
use sp_core::H256;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
pub type ProcessMessageOriginOf<T> = <Pallet<T> as ProcessMessage>::Origin;
/// Pending order
#[derive(Encode, Decode, TypeInfo, Clone, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)]
pub struct PendingOrder<BlockNumber> {
/// The nonce used to identify the message
pub nonce: u64,
/// The block number in which the message was committed
pub block_number: BlockNumber,
/// The fee in Ether provided by the user to incentivize message delivery
#[codec(compact)]
pub fee: u128,
}
/// Hook that will be called when a new message commitment is constructed.
pub trait OnNewCommitment {
fn on_new_commitment(commitment: H256);
}
impl OnNewCommitment for () {
fn on_new_commitment(_commitment: H256) {}
}

View file

@ -0,0 +1,104 @@
//! Autogenerated weights for `snowbridge-pallet-outbound-queue`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-10-19, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `192.168.1.7`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024`
// Executed Command:
// target/release/polkadot-parachain
// benchmark
// pallet
// --chain=bridge-hub-rococo-dev
// --pallet=snowbridge-pallet-outbound-queue
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --template
// ../parachain/templates/module-weight-template.hbs
// --output
// ../parachain/pallets/outbound-queue/src/weights.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `snowbridge-pallet-outbound-queue`.
pub trait WeightInfo {
fn do_process_message() -> Weight;
fn commit() -> Weight;
fn commit_single() -> Weight;
fn submit_delivery_receipt() -> Weight;
fn on_initialize() -> Weight;
fn process() -> Weight;
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1)
/// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured)
/// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1)
/// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
/// Storage: EthereumOutboundQueue Nonce (r:1 w:1)
/// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen)
/// Storage: EthereumOutboundQueue Messages (r:1 w:1)
/// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured)
fn do_process_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `42`
// Estimated: `3485`
// Minimum execution time: 39_000_000 picoseconds.
Weight::from_parts(39_000_000, 3485)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
/// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0)
/// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured)
/// Storage: System Digest (r:1 w:1)
/// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured)
fn commit() -> Weight {
// Proof Size summary in bytes:
// Measured: `1094`
// Estimated: `2579`
// Minimum execution time: 28_000_000 picoseconds.
Weight::from_parts(28_000_000, 2579)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn commit_single() -> Weight {
// Proof Size summary in bytes:
// Measured: `1094`
// Estimated: `2579`
// Minimum execution time: 9_000_000 picoseconds.
Weight::from_parts(9_000_000, 1586)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn submit_delivery_receipt() -> Weight {
Weight::from_parts(70_000_000, 0)
.saturating_add(Weight::from_parts(0, 3601))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(2))
}
fn on_initialize() -> Weight {
Weight::from_parts(5_000_000, 0)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(5))
}
fn process() -> Weight {
Weight::from_parts(506_000_000, 0)
.saturating_add(Weight::from_parts(0, 1493))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(35))
}
}

View file

@ -0,0 +1,50 @@
[package]
name = "bridge-hub-common"
version = "0.13.1"
authors.workspace = true
edition.workspace = true
description = "Bridge hub common utilities"
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
[dependencies]
codec = { features = ["derive"], workspace = true }
cumulus-primitives-core.workspace = true
frame-support.workspace = true
pallet-message-queue.workspace = true
scale-info = { features = ["derive"], workspace = true }
snowbridge-core.workspace = true
sp-core.workspace = true
sp-runtime.workspace = true
sp-std.workspace = true
xcm-builder.workspace = true
xcm-executor.workspace = true
xcm.workspace = true
[features]
default = ["std"]
std = [
"codec/std",
"cumulus-primitives-core/std",
"frame-support/std",
"pallet-message-queue/std",
"scale-info/std",
"snowbridge-core/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
]
runtime-benchmarks = [
"cumulus-primitives-core/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"snowbridge-core/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
]

View file

@ -0,0 +1,37 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Custom digest items
use codec::{Decode, Encode};
use sp_core::{RuntimeDebug, H256};
use sp_runtime::generic::DigestItem;
/// Custom header digest items, inserted as DigestItem::Other
#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug)]
pub enum CustomDigestItem {
#[codec(index = 0)]
/// Merkle root of outbound Snowbridge messages.
Snowbridge(H256),
#[codec(index = 1)]
/// Merkle root of outbound Snowbridge V2 messages.
SnowbridgeV2(H256),
}
/// Convert custom application digest item into a concrete digest item
impl From<CustomDigestItem> for DigestItem {
fn from(val: CustomDigestItem) -> Self {
DigestItem::Other(val.encode())
}
}

View file

@ -0,0 +1,24 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg_attr(not(feature = "std"), no_std)]
pub mod digest_item;
pub mod message_queue;
pub mod xcm_version;
pub use digest_item::CustomDigestItem;
pub use message_queue::{
AggregateMessageOrigin, BridgeHubDualMessageRouter, BridgeHubMessageRouter,
};

View file

@ -0,0 +1,182 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Runtime configuration for MessageQueue pallet
use codec::{Decode, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use cumulus_primitives_core::{AggregateMessageOrigin as CumulusAggregateMessageOrigin, ParaId};
use frame_support::{
traits::{ProcessMessage, ProcessMessageError, QueueFootprint, QueuePausedQuery},
weights::WeightMeter,
};
use pallet_message_queue::OnQueueChanged;
use scale_info::TypeInfo;
use snowbridge_core::ChannelId;
use sp_core::H256;
use xcm::latest::prelude::{Junction, Location};
/// The aggregate origin of an inbound message.
/// This is specialized for BridgeHub, as the snowbridge-outbound-queue-pallet is also using
/// the shared MessageQueue pallet.
#[derive(Encode, Decode, Copy, MaxEncodedLen, Clone, Eq, PartialEq, TypeInfo, Debug)]
pub enum AggregateMessageOrigin {
/// The message came from the para-chain itself.
Here,
/// The message came from the relay-chain.
///
/// This is used by the DMP queue.
Parent,
/// The message came from a sibling para-chain.
///
/// This is used by the HRMP queue.
Sibling(ParaId),
/// The message came from a snowbridge channel.
///
/// This is used by Snowbridge inbound queue.
Snowbridge(ChannelId),
SnowbridgeV2(H256),
}
impl From<AggregateMessageOrigin> for Location {
fn from(origin: AggregateMessageOrigin) -> Self {
use AggregateMessageOrigin::*;
match origin {
Here => Location::here(),
Parent => Location::parent(),
Sibling(id) => Location::new(1, Junction::Parachain(id.into())),
// NOTE: We don't need this conversion for Snowbridge. However, we have to
// implement it anyway as xcm_builder::ProcessXcmMessage requires it.
_ => Location::default(),
}
}
}
impl From<CumulusAggregateMessageOrigin> for AggregateMessageOrigin {
fn from(origin: CumulusAggregateMessageOrigin) -> Self {
match origin {
CumulusAggregateMessageOrigin::Here => Self::Here,
CumulusAggregateMessageOrigin::Parent => Self::Parent,
CumulusAggregateMessageOrigin::Sibling(id) => Self::Sibling(id),
}
}
}
#[cfg(feature = "runtime-benchmarks")]
impl From<u32> for AggregateMessageOrigin {
fn from(x: u32) -> Self {
match x {
0 => Self::Here,
1 => Self::Parent,
p => Self::Sibling(ParaId::from(p)),
}
}
}
/// Routes messages to either the XCMP or Snowbridge processor.
pub struct BridgeHubMessageRouter<XcmpProcessor, SnowbridgeProcessor>(
PhantomData<(XcmpProcessor, SnowbridgeProcessor)>,
)
where
XcmpProcessor: ProcessMessage<Origin = AggregateMessageOrigin>,
SnowbridgeProcessor: ProcessMessage<Origin = AggregateMessageOrigin>;
impl<XcmpProcessor, SnowbridgeProcessor> ProcessMessage
for BridgeHubMessageRouter<XcmpProcessor, SnowbridgeProcessor>
where
XcmpProcessor: ProcessMessage<Origin = AggregateMessageOrigin>,
SnowbridgeProcessor: ProcessMessage<Origin = AggregateMessageOrigin>,
{
type Origin = AggregateMessageOrigin;
fn process_message(
message: &[u8],
origin: Self::Origin,
meter: &mut WeightMeter,
id: &mut [u8; 32],
) -> Result<bool, ProcessMessageError> {
use AggregateMessageOrigin::*;
match origin {
Here | Parent | Sibling(_) => {
XcmpProcessor::process_message(message, origin, meter, id)
}
Snowbridge(_) => SnowbridgeProcessor::process_message(message, origin, meter, id),
SnowbridgeV2(_) => Err(ProcessMessageError::Unsupported),
}
}
}
/// Routes messages to either the XCMP|Snowbridge V1 processor|Snowbridge V2 processor
pub struct BridgeHubDualMessageRouter<XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2>(
PhantomData<(XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2)>,
)
where
XcmpProcessor: ProcessMessage<Origin = AggregateMessageOrigin>,
SnowbridgeProcessor: ProcessMessage<Origin = AggregateMessageOrigin>;
impl<XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2> ProcessMessage
for BridgeHubDualMessageRouter<XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2>
where
XcmpProcessor: ProcessMessage<Origin = AggregateMessageOrigin>,
SnowbridgeProcessor: ProcessMessage<Origin = AggregateMessageOrigin>,
SnowbridgeProcessorV2: ProcessMessage<Origin = AggregateMessageOrigin>,
{
type Origin = AggregateMessageOrigin;
fn process_message(
message: &[u8],
origin: Self::Origin,
meter: &mut WeightMeter,
id: &mut [u8; 32],
) -> Result<bool, ProcessMessageError> {
use AggregateMessageOrigin::*;
match origin {
Here | Parent | Sibling(_) => {
XcmpProcessor::process_message(message, origin, meter, id)
}
Snowbridge(_) => SnowbridgeProcessor::process_message(message, origin, meter, id),
SnowbridgeV2(_) => SnowbridgeProcessorV2::process_message(message, origin, meter, id),
}
}
}
/// Narrow the scope of the `Inner` query from `AggregateMessageOrigin` to `ParaId`.
///
/// All non-`Sibling` variants will be ignored.
pub struct NarrowOriginToSibling<Inner>(PhantomData<Inner>);
impl<Inner: QueuePausedQuery<ParaId>> QueuePausedQuery<AggregateMessageOrigin>
for NarrowOriginToSibling<Inner>
{
fn is_paused(origin: &AggregateMessageOrigin) -> bool {
match origin {
AggregateMessageOrigin::Sibling(id) => Inner::is_paused(id),
_ => false,
}
}
}
impl<Inner: OnQueueChanged<ParaId>> OnQueueChanged<AggregateMessageOrigin>
for NarrowOriginToSibling<Inner>
{
fn on_queue_changed(origin: AggregateMessageOrigin, fp: QueueFootprint) {
if let AggregateMessageOrigin::Sibling(id) = origin {
Inner::on_queue_changed(id, fp)
}
}
}
/// Convert a sibling `ParaId` to an `AggregateMessageOrigin`.
pub struct ParaIdToSibling;
impl sp_runtime::traits::Convert<ParaId, AggregateMessageOrigin> for ParaIdToSibling {
fn convert(para_id: ParaId) -> AggregateMessageOrigin {
AggregateMessageOrigin::Sibling(para_id)
}
}

View file

@ -0,0 +1,44 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Custom XCM implementation.
use frame_support::traits::Get;
use xcm::{
latest::prelude::*,
prelude::{GetVersion, XcmVersion},
};
/// Adapter for the implementation of `GetVersion`, which attempts to find the minimal
/// configured XCM version between the destination `dest` and the bridge hub location provided as
/// `Get<Location>`.
pub struct XcmVersionOfDestAndRemoteBridge<Version, RemoteBridge>(
sp_std::marker::PhantomData<(Version, RemoteBridge)>,
);
impl<Version: GetVersion, RemoteBridge: Get<Location>> GetVersion
for XcmVersionOfDestAndRemoteBridge<Version, RemoteBridge>
{
fn get_version_for(dest: &Location) -> Option<XcmVersion> {
let dest_version = Version::get_version_for(dest);
let bridge_hub_version = Version::get_version_for(&RemoteBridge::get());
match (dest_version, bridge_hub_version) {
(Some(dv), Some(bhv)) => Some(sp_std::cmp::min(dv, bhv)),
(Some(dv), None) => Some(dv),
(None, Some(bhv)) => Some(bhv),
(None, None) => None,
}
}
}

View file

@ -0,0 +1,29 @@
[package]
name = "snowbridge-merkle-tree"
description = "Snowbridge Merkle Tree"
version = "0.2.0"
authors = ["Snowfork <contact@snowfork.com>"]
edition.workspace = true
repository.workspace = true
license = "Apache-2.0"
categories = ["cryptography::cryptocurrencies"]
[package.metadata.polkadot-sdk]
exclude-from-umbrella = true
[dependencies]
codec = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
array-bytes = { workspace = true, default-features = true }
hex = { workspace = true, default-features = true }
hex-literal = { workspace = true, default-features = true }
sp-crypto-hashing = { workspace = true, default-features = true }
sp-tracing = { workspace = true, default-features = true }
[features]
default = ["std"]
std = ["codec/std", "scale-info/std", "sp-core/std", "sp-runtime/std"]

View file

@ -0,0 +1,3 @@
# Merkle-Tree Primitives
Contains the custom merkle tree implementation optimized for Ethereum.

View file

@ -0,0 +1,466 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd.
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
//! This crate implements a simple binary Merkle Tree utilities required for inter-op with Ethereum
//! bridge & Solidity contract.
//!
//! The implementation is optimised for usage within Substrate Runtime and supports no-std
//! compilation targets.
//!
//! Merkle Tree is constructed from arbitrary-length leaves, that are initially hashed using the
//! same `\[`Hasher`\]` as the inner nodes.
//! Inner nodes are created by concatenating child hashes and hashing again. The implementation
//! does not perform any sorting of the input data (leaves) nor when inner nodes are created.
//!
//! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer.
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_core::{RuntimeDebug, H256};
use sp_runtime::traits::Hash;
/// Construct a root hash of a Binary Merkle Tree created from given leaves.
///
/// See crate-level docs for details about Merkle Tree construction.
///
/// In case an empty list of leaves is passed the function returns a 0-filled hash.
pub fn merkle_root<H, I>(leaves: I) -> H256
where
H: Hash<Output = H256>,
I: Iterator<Item = H256>,
{
merkelize::<H, _, _>(leaves, &mut ())
}
fn merkelize<H, V, I>(leaves: I, visitor: &mut V) -> H256
where
H: Hash<Output = H256>,
V: Visitor,
I: Iterator<Item = H256>,
{
let upper = Vec::with_capacity(leaves.size_hint().0);
let mut next = match merkelize_row::<H, _, _>(leaves, upper, visitor) {
Ok(root) => return root,
Err(next) if next.is_empty() => return H256::default(),
Err(next) => next,
};
let mut upper = Vec::with_capacity((next.len() + 1) / 2);
loop {
visitor.move_up();
match merkelize_row::<H, _, _>(next.drain(..), upper, visitor) {
Ok(root) => return root,
Err(t) => {
// swap collections to avoid allocations
upper = next;
next = t;
}
};
}
}
/// A generated merkle proof.
///
/// The structure contains all necessary data to later on verify the proof and the leaf itself.
#[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)]
pub struct MerkleProof {
/// Root hash of generated merkle tree.
pub root: H256,
/// Proof items (does not contain the leaf hash, nor the root obviously).
///
/// This vec contains all inner node hashes necessary to reconstruct the root hash given the
/// leaf hash.
pub proof: Vec<H256>,
/// Number of leaves in the original tree.
///
/// This is needed to detect a case where we have an odd number of leaves that "get promoted"
/// to upper layers.
pub number_of_leaves: u64,
/// Index of the leaf the proof is for (0-based).
pub leaf_index: u64,
/// Leaf content (hashed).
pub leaf: H256,
}
/// A trait of object inspecting merkle root creation.
///
/// It can be passed to [`merkelize_row`] or [`merkelize`] functions and will be notified
/// about tree traversal.
trait Visitor {
/// We are moving one level up in the tree.
fn move_up(&mut self);
/// We are creating an inner node from given `left` and `right` nodes.
///
/// Note that in case of last odd node in the row `right` might be empty.
/// The method will also visit the `root` hash (level 0).
///
/// The `index` is an index of `left` item.
fn visit(&mut self, index: u64, left: &Option<H256>, right: &Option<H256>);
}
/// No-op implementation of the visitor.
impl Visitor for () {
fn move_up(&mut self) {}
fn visit(&mut self, _index: u64, _left: &Option<H256>, _right: &Option<H256>) {}
}
/// Construct a Merkle Proof for leaves given by indices.
///
/// The function constructs a (partial) Merkle Tree first and stores all elements required
/// to prove the requested item (leaf) given the root hash.
///
/// Both the Proof and the Root Hash are returned.
///
/// # Panic
///
/// The function will panic if given `leaf_index` is greater than the number of leaves.
pub fn merkle_proof<H, I>(leaves: I, leaf_index: u64) -> MerkleProof
where
H: Hash<Output = H256>,
I: Iterator<Item = H256>,
{
let mut leaf = None;
let mut hashes = vec![];
let mut number_of_leaves = 0;
for (idx, l) in (0u64..).zip(leaves) {
// count the leaves
number_of_leaves = idx + 1;
hashes.push(l);
// find the leaf for the proof
if idx == leaf_index {
leaf = Some(l);
}
}
/// The struct collects a proof for single leaf.
struct ProofCollection {
proof: Vec<H256>,
position: u64,
}
impl ProofCollection {
fn new(position: u64) -> Self {
ProofCollection {
proof: Default::default(),
position,
}
}
}
impl Visitor for ProofCollection {
fn move_up(&mut self) {
self.position /= 2;
}
fn visit(&mut self, index: u64, left: &Option<H256>, right: &Option<H256>) {
// we are at left branch - right goes to the proof.
if self.position == index {
if let Some(right) = right {
self.proof.push(*right);
}
}
// we are at right branch - left goes to the proof.
if self.position == index + 1 {
if let Some(left) = left {
self.proof.push(*left);
}
}
}
}
let mut collect_proof = ProofCollection::new(leaf_index);
let root = merkelize::<H, _, _>(hashes.into_iter(), &mut collect_proof);
let leaf = leaf.expect("Requested `leaf_index` is greater than number of leaves.");
MerkleProof {
root,
proof: collect_proof.proof,
number_of_leaves,
leaf_index,
leaf,
}
}
/// Leaf node for proof verification.
///
/// Can be either a value that needs to be hashed first,
/// or the hash itself.
#[derive(Debug, PartialEq, Eq)]
pub enum Leaf<'a> {
/// Leaf content.
Value(&'a [u8]),
/// Hash of the leaf content.
Hash(H256),
}
impl<'a, T: AsRef<[u8]>> From<&'a T> for Leaf<'a> {
fn from(v: &'a T) -> Self {
Leaf::Value(v.as_ref())
}
}
impl<'a> From<H256> for Leaf<'a> {
fn from(v: H256) -> Self {
Leaf::Hash(v)
}
}
/// Verify Merkle Proof correctness versus given root hash.
///
/// The proof is NOT expected to contain leaf hash as the first
/// element, but only all adjacent nodes required to eventually by process of
/// concatenating and hashing end up with given root hash.
///
/// The proof must not contain the root hash.
pub fn verify_proof<'a, H, P, L>(
root: &'a H256,
proof: P,
number_of_leaves: u64,
leaf_index: u64,
leaf: L,
) -> bool
where
H: Hash<Output = H256>,
P: IntoIterator<Item = H256>,
L: Into<Leaf<'a>>,
{
if leaf_index >= number_of_leaves {
return false;
}
let leaf_hash = match leaf.into() {
Leaf::Value(content) => <H as Hash>::hash(content),
Leaf::Hash(hash) => hash,
};
let hash_len = <H as sp_core::Hasher>::LENGTH;
let mut combined = [0_u8; 64];
let computed = proof.into_iter().fold(leaf_hash, |a, b| {
if a < b {
combined[..hash_len].copy_from_slice(a.as_ref());
combined[hash_len..].copy_from_slice(b.as_ref());
} else {
combined[..hash_len].copy_from_slice(b.as_ref());
combined[hash_len..].copy_from_slice(a.as_ref());
}
<H as Hash>::hash(&combined)
});
root == &computed
}
/// Processes a single row (layer) of a tree by taking pairs of elements,
/// concatenating them, hashing and placing into resulting vector.
///
/// In case only one element is provided it is returned via `Ok` result, in any other case (also an
/// empty iterator) an `Err` with the inner nodes of upper layer is returned.
fn merkelize_row<H, V, I>(
mut iter: I,
mut next: Vec<H256>,
visitor: &mut V,
) -> Result<H256, Vec<H256>>
where
H: Hash<Output = H256>,
V: Visitor,
I: Iterator<Item = H256>,
{
next.clear();
let hash_len = <H as sp_core::Hasher>::LENGTH;
let mut index = 0;
let mut combined = vec![0_u8; hash_len * 2];
loop {
let a = iter.next();
let b = iter.next();
visitor.visit(index, &a, &b);
index += 2;
match (a, b) {
(Some(a), Some(b)) => {
if a < b {
combined[..hash_len].copy_from_slice(a.as_ref());
combined[hash_len..].copy_from_slice(b.as_ref());
} else {
combined[..hash_len].copy_from_slice(b.as_ref());
combined[hash_len..].copy_from_slice(a.as_ref());
}
next.push(<H as Hash>::hash(&combined));
}
// Odd number of items. Promote the item to the upper layer.
(Some(a), None) if !next.is_empty() => {
next.push(a);
}
// Last item = root.
(Some(a), None) => return Ok(a),
// Finish up, no more items.
_ => return Err(next),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
use sp_crypto_hashing::keccak_256;
use sp_runtime::traits::Keccak256;
fn make_leaves(count: u64) -> Vec<H256> {
(0..count)
.map(|i| keccak_256(&i.to_le_bytes()).into())
.collect()
}
#[test]
fn should_generate_empty_root() {
// given
sp_tracing::init_for_tests();
let data = vec![];
// when
let out = merkle_root::<Keccak256, _>(data.into_iter());
// then
assert_eq!(
hex::encode(out),
"0000000000000000000000000000000000000000000000000000000000000000"
);
}
#[test]
fn should_generate_single_root() {
// given
sp_tracing::init_for_tests();
let data = make_leaves(1);
// when
let out = merkle_root::<Keccak256, _>(data.into_iter());
// then
assert_eq!(
hex::encode(out),
"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce"
);
}
#[test]
fn should_generate_root_pow_2() {
// given
sp_tracing::init_for_tests();
let data = make_leaves(2);
// when
let out = merkle_root::<Keccak256, _>(data.into_iter());
// then
assert_eq!(
hex::encode(out),
"e497bd1c13b13a60af56fa0d2703517c232fde213ad20d2c3dd60735c6604512"
);
}
#[test]
fn should_generate_root_complex() {
sp_tracing::init_for_tests();
let test = |root, data: Vec<H256>| {
assert_eq!(
array_bytes::bytes2hex("", merkle_root::<Keccak256, _>(data.into_iter()).as_ref()),
root
);
};
test(
"816cc37bd8d39f7b0851838ebc875faf2afe58a03e95aca3b1333b3693f39dd3",
make_leaves(3),
);
test(
"7501ea976cb92f305cca65ab11254589ea28bb8b59d3161506350adaa237d22f",
make_leaves(4),
);
test(
"d26ba4eb398747bdd39255b1fadb99b803ce39696021b3b0bff7301ac146ee4e",
make_leaves(10),
);
}
#[test]
#[ignore]
fn should_generate_and_verify_proof() {
// given
sp_tracing::init_for_tests();
let data: Vec<H256> = make_leaves(3);
// when
let proof0 = merkle_proof::<Keccak256, _>(data.clone().into_iter(), 0);
assert!(verify_proof::<Keccak256, _, _>(
&proof0.root,
proof0.proof.clone(),
data.len() as u64,
proof0.leaf_index,
&data[0],
));
let proof1 = merkle_proof::<Keccak256, _>(data.clone().into_iter(), 1);
assert!(verify_proof::<Keccak256, _, _>(
&proof1.root,
proof1.proof,
data.len() as u64,
proof1.leaf_index,
&proof1.leaf,
));
let proof2 = merkle_proof::<Keccak256, _>(data.clone().into_iter(), 2);
assert!(verify_proof::<Keccak256, _, _>(
&proof2.root,
proof2.proof,
data.len() as u64,
proof2.leaf_index,
&proof2.leaf
));
// then
assert_eq!(hex::encode(proof0.root), hex::encode(proof1.root));
assert_eq!(hex::encode(proof2.root), hex::encode(proof1.root));
assert!(!verify_proof::<Keccak256, _, _>(
&H256::from_slice(&hex!(
"fb3b3be94be9e983ba5e094c9c51a7d96a4fa2e5d8e891df00ca89ba05bb1239"
)),
proof0.proof,
data.len() as u64,
proof0.leaf_index,
&proof0.leaf
));
assert!(!verify_proof::<Keccak256, _, _>(
&proof0.root,
vec![],
data.len() as u64,
proof0.leaf_index,
&proof0.leaf
));
}
#[test]
#[should_panic]
fn should_panic_on_invalid_leaf_index() {
sp_tracing::init_for_tests();
merkle_proof::<Keccak256, _>(make_leaves(1).into_iter(), 5);
}
}

View file

@ -13,6 +13,7 @@ publish = false
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
bridge-hub-common = { workspace = true, optional = true }
codec = { features = ["derive"], workspace = true }
dhp-bridge = { workspace = true }
datahaven-runtime-common = { workspace = true }
@ -41,6 +42,7 @@ pallet-evm-chain-id = { workspace = true }
pallet-grandpa = { workspace = true }
pallet-identity = { workspace = true }
pallet-im-online = { workspace = true }
pallet-message-queue = { workspace = true }
pallet-mmr = { workspace = true }
pallet-multisig = { workspace = true }
pallet-offences = { workspace = true }
@ -60,8 +62,12 @@ scale-info = { features = ["derive", "serde"], workspace = true }
serde_json = { workspace = true, default-features = false, features = ["alloc"] }
snowbridge-beacon-primitives = { workspace = true }
snowbridge-inbound-queue-primitives = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
snowbridge-pallet-ethereum-client = { workspace = true }
snowbridge-pallet-inbound-queue-v2 = { workspace = true }
snowbridge-pallet-outbound-queue-v2 = { workspace = true }
snowbridge-merkle-tree = { workspace = true }
snowbridge-outbound-queue-v2-runtime-api = { workspace = true }
snowbridge-verification-primitives = { workspace = true }
sp-api = { workspace = true }
sp-block-builder = { workspace = true }
@ -112,6 +118,7 @@ std = [
"pallet-grandpa/std",
"pallet-identity/std",
"pallet-im-online/std",
"pallet-message-queue/std",
"pallet-mmr/std",
"pallet-multisig/std",
"pallet-offences/std",
@ -131,11 +138,14 @@ std = [
"serde_json/std",
"snowbridge-beacon-primitives/std",
"snowbridge-inbound-queue-primitives/std",
"snowbridge-outbound-queue-primitives/std",
"snowbridge-pallet-ethereum-client/std",
"snowbridge-pallet-inbound-queue-v2/std",
"snowbridge-pallet-outbound-queue-v2/std",
"snowbridge-merkle-tree/std",
"snowbridge-outbound-queue-v2-runtime-api/std",
"dhp-bridge/std",
"snowbridge-verification-primitives/std",
"sp-api/std",
"sp-block-builder/std",
"sp-consensus-babe/std",
@ -156,6 +166,7 @@ std = [
]
runtime-benchmarks = [
"bridge-hub-common",
"datahaven-runtime-common/runtime-benchmarks",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
@ -168,6 +179,7 @@ runtime-benchmarks = [
"pallet-grandpa/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"pallet-im-online/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"pallet-mmr/runtime-benchmarks",
"pallet-multisig/runtime-benchmarks",
"pallet-offences/runtime-benchmarks",
@ -179,10 +191,11 @@ runtime-benchmarks = [
"pallet-utility/runtime-benchmarks",
"polkadot-primitives/runtime-benchmarks",
"polkadot-runtime-common/runtime-benchmarks",
"polkadot-primitives/runtime-benchmarks",
"polkadot-primitives/runtime-benchmarks",
"snowbridge-inbound-queue-primitives/runtime-benchmarks",
"snowbridge-pallet-ethereum-client/runtime-benchmarks",
"snowbridge-pallet-inbound-queue-v2/runtime-benchmarks",
"snowbridge-pallet-outbound-queue-v2/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
@ -202,6 +215,7 @@ try-runtime = [
"pallet-grandpa/try-runtime",
"pallet-identity/try-runtime",
"pallet-im-online/try-runtime",
"pallet-message-queue/try-runtime",
"pallet-mmr/try-runtime",
"pallet-multisig/try-runtime",
"pallet-offences/try-runtime",
@ -216,6 +230,7 @@ try-runtime = [
"polkadot-runtime-common/try-runtime",
"snowbridge-pallet-ethereum-client/try-runtime",
"snowbridge-pallet-inbound-queue-v2/try-runtime",
"snowbridge-pallet-outbound-queue-v2/try-runtime",
"sp-runtime/try-runtime",
]

View file

@ -8,18 +8,20 @@ edition.workspace = true
frame-support.workspace = true
polkadot-primitives.workspace = true
polkadot-runtime-common.workspace = true
xcm = { workspace = true }
[features]
default = ["std"]
std = [
"frame-support/std",
"polkadot-primitives/std",
"polkadot-runtime-common/std"
"polkadot-runtime-common/std",
"xcm/std"
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"polkadot-primitives/runtime-benchmarks",
"polkadot-runtime-common/runtime-benchmarks"
"polkadot-runtime-common/runtime-benchmarks",
]
# Set timing constants (e.g. session period) to faster versions to speed up testing.

View file

@ -17,6 +17,5 @@
#![cfg_attr(not(feature = "std"), no_std)]
pub mod constants;
pub use constants::gas;
pub use constants::time;
pub use constants::*;
pub mod impl_on_charge_evm_transaction;

View file

@ -487,6 +487,12 @@ impl_runtime_apis! {
}
}
impl snowbridge_outbound_queue_v2_runtime_api::OutboundQueueV2Api<Block, Balance> for Runtime {
fn prove_message(leaf_index: u64) -> Option<snowbridge_merkle_tree::MerkleProof> {
snowbridge_pallet_outbound_queue_v2::api::prove_message::<Runtime>(leaf_index)
}
}
#[cfg(feature = "runtime-benchmarks")]
impl frame_benchmarking::Benchmark<Block> for Runtime {
fn benchmark_metadata(extra: bool) -> (

View file

@ -42,7 +42,7 @@ use frame_support::{
},
weights::{
constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND},
IdentityFee, Weight,
IdentityFee, RuntimeDbWeight, Weight,
},
};
use frame_system::{
@ -63,11 +63,12 @@ use pallet_transaction_payment::{
use polkadot_primitives::Moment;
use snowbridge_beacon_primitives::{Fork, ForkVersions};
use snowbridge_inbound_queue_primitives::RewardLedger;
use snowbridge_outbound_queue_primitives::v2::ConstantGasMeter;
use sp_consensus_beefy::{
ecdsa_crypto::AuthorityId as BeefyId,
mmr::{BeefyDataProvider, MmrLeafVersion},
};
use sp_core::{crypto::KeyTypeId, H160, H256, U256};
use sp_core::{crypto::KeyTypeId, Get, H160, H256, U256};
use sp_runtime::{
traits::{ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto},
FixedPointNumber, Perbill,
@ -78,17 +79,21 @@ use sp_std::{
prelude::*,
};
use sp_version::RuntimeVersion;
use xcm::latest::NetworkId;
use super::{
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, Nonce, Offences, OriginCaller,
PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason,
RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys, Signature, System,
Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT, SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR,
UNIT, VERSION,
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
OriginCaller, OutboundQueueV2, PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent,
RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys,
Signature, System, Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT, SLOT_DURATION,
STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
};
use runtime_params::RuntimeParameters;
#[cfg(feature = "runtime-benchmarks")]
use bridge_hub_common::AggregateMessageOrigin;
const EVM_CHAIN_ID: u64 = 1289;
const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16;
@ -493,6 +498,34 @@ impl pallet_sudo::Config for Runtime {
type WeightInfo = pallet_sudo::weights::SubstrateWeight<Runtime>;
}
parameter_types! {
/// Amount of weight that can be spent per block to service messages.
///
/// # WARNING
///
/// This is not a good value for para-chains since the `Scheduler` already uses up to 80% block weight.
pub MessageQueueServiceWeight: Weight = Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block;
pub const MessageQueueHeapSize: u32 = 32 * 1024;
pub const MessageQueueMaxStale: u32 = 96;
}
impl pallet_message_queue::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
#[cfg(not(feature = "runtime-benchmarks"))]
type MessageProcessor = OutboundQueueV2;
#[cfg(feature = "runtime-benchmarks")]
type MessageProcessor =
pallet_message_queue::mock_helpers::NoopMessageProcessor<AggregateMessageOrigin>;
type Size = u32;
type QueueChangeHandler = ();
type QueuePausedQuery = ();
type HeapSize = MessageQueueHeapSize;
type MaxStale = MessageQueueMaxStale;
type ServiceWeight = MessageQueueServiceWeight;
type IdleMaxServiceWeight = MessageQueueServiceWeight;
type WeightInfo = ();
}
//╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
//║ FRONTIER (EVM) PALLETS ║
//╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
@ -523,7 +556,7 @@ impl FeeCalculator for TransactionPaymentAsGasPrice {
.saturating_mul_int((WEIGHT_FEE).saturating_mul(WEIGHT_PER_GAS as u128));
(
min_gas_price.into(),
<Runtime as frame_system::Config>::DbWeight::get().reads(1),
<<Runtime as frame_system::Config>::DbWeight as Get<RuntimeDbWeight>>::get().reads(1),
)
}
}
@ -691,6 +724,34 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime {
type Helper = Runtime;
}
parameter_types! {
/// Network and location for the Ethereum chain.
/// Using the Sepolia Ethereum testnet, with chain ID 11155111.
/// <https://chainlist.org/chain/11155111>
/// <https://ethereum.org/en/developers/docs/apis/json-rpc/#net_version>
pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
}
impl snowbridge_pallet_outbound_queue_v2::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Hashing = Keccak256;
type MessageQueue = MessageQueue;
type GasMeter = ConstantGasMeter;
type Balance = Balance;
type MaxMessagePayloadSize = ConstU32<2048>;
type MaxMessagesPerBlock = ConstU32<32>;
type OnNewCommitment = ();
type WeightToFee = IdentityFee<Balance>;
type Verifier = EthereumBeaconClient;
type GatewayAddress = runtime_params::dynamic_params::runtime_config::EthereumGatewayAddress;
type RewardKind = ();
type DefaultRewardKind = DefaultRewardKind;
type RewardPayment = DummyRewardPayment;
type EthereumNetwork = EthereumNetwork;
type ConvertAssetId = ();
type WeightInfo = ();
}
//╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
//║ STORAGEHUB PALLETS ║
//╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

View file

@ -307,29 +307,35 @@ mod runtime {
#[runtime::pallet_index(36)]
pub type Sudo = pallet_sudo;
#[runtime::pallet_index(37)]
pub type MessageQueue = pallet_message_queue;
// ╚═════════════════ Polkadot SDK Utility Pallets ══════════════════╝
// ╔════════════════════ Frontier (EVM) Pallets ═════════════════════╗
#[runtime::pallet_index(40)]
#[runtime::pallet_index(50)]
pub type Ethereum = pallet_ethereum;
#[runtime::pallet_index(41)]
#[runtime::pallet_index(51)]
pub type Evm = pallet_evm;
#[runtime::pallet_index(42)]
#[runtime::pallet_index(52)]
pub type EvmChainId = pallet_evm_chain_id;
// ╚════════════════════ Frontier (EVM) Pallets ═════════════════════╝
// ╔══════════════════════ Snowbridge Pallets ═══════════════════════╗
#[runtime::pallet_index(50)]
#[runtime::pallet_index(60)]
pub type EthereumBeaconClient = snowbridge_pallet_ethereum_client;
#[runtime::pallet_index(51)]
#[runtime::pallet_index(61)]
pub type InboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
#[runtime::pallet_index(62)]
pub type OutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
// ╚══════════════════════ Snowbridge Pallets ═══════════════════════╝
// ╔══════════════════════ StorageHub Pallets ═══════════════════════╗
// Start with index 60
// Start with index 70
// ╚══════════════════════ StorageHub Pallets ═══════════════════════╝
// ╔═══════════════════ DataHaven-specific Pallets ══════════════════╗