diff --git a/Cargo.lock b/Cargo.lock
index be9714f442..fa5f25f2bb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -965,6 +965,7 @@ dependencies = [
"bp-rococo",
"bp-runtime",
"bp-wococo",
+ "bridge-hub-test-utils",
"bridge-runtime-common",
"cumulus-pallet-aura-ext",
"cumulus-pallet-dmp-queue",
@@ -1008,6 +1009,7 @@ dependencies = [
"polkadot-runtime-constants",
"scale-info",
"serde",
+ "serial_test",
"smallvec",
"sp-api",
"sp-block-builder",
@@ -1027,6 +1029,27 @@ dependencies = [
"xcm-executor",
]
+[[package]]
+name = "bridge-hub-test-utils"
+version = "0.1.0"
+dependencies = [
+ "bp-messages",
+ "cumulus-pallet-parachain-system",
+ "cumulus-primitives-core",
+ "cumulus-primitives-parachain-inherent",
+ "cumulus-test-relay-sproof-builder",
+ "frame-support",
+ "frame-system",
+ "pallet-xcm",
+ "pallet-xcm-benchmarks",
+ "parachain-info",
+ "parachains-common",
+ "polkadot-parachain 0.9.31",
+ "xcm",
+ "xcm-builder",
+ "xcm-executor",
+]
+
[[package]]
name = "bridge-runtime-common"
version = "0.1.0"
@@ -2652,6 +2675,19 @@ dependencies = [
"syn",
]
+[[package]]
+name = "dashmap"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
+dependencies = [
+ "cfg-if 1.0.0",
+ "hashbrown",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core 0.9.3",
+]
+
[[package]]
name = "data-encoding"
version = "2.3.2"
@@ -11078,6 +11114,32 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serial_test"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153"
+dependencies = [
+ "dashmap",
+ "futures",
+ "lazy_static",
+ "log",
+ "parking_lot 0.12.1",
+ "serial_test_derive",
+]
+
+[[package]]
+name = "serial_test_derive"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "sha-1"
version = "0.9.8"
diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
index 25bdd1bcbe..6adb8e1c15 100644
--- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
+++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
@@ -83,6 +83,10 @@ pallet-bridge-relayers = { path = "../../../../bridges/modules/relayers", defaul
pallet-shift-session-manager = { path = "../../../../bridges/modules/shift-session-manager", default-features = false }
bridge-runtime-common = { path = "../../../../bridges/bin/runtime-common", default-features = false }
+[dev-dependencies]
+serial_test = "0.9.0"
+bridge-hub-test-utils = { path = "../test-utils"}
+
[features]
default = [
"std",
diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
new file mode 100644
index 0000000000..11d3fb9681
--- /dev/null
+++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
@@ -0,0 +1,183 @@
+// Copyright 2022 Parity Technologies (UK) Ltd.
+// This file is part of Cumulus.
+
+// Cumulus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Cumulus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Cumulus. If not, see .
+
+use bp_messages::target_chain::MessageDispatch;
+use bp_runtime::messages::MessageDispatchResult;
+use bridge_hub_rococo_runtime::bridge_common_config::XcmBlobMessageDispatchResult;
+pub use bridge_hub_rococo_runtime::{
+ runtime_api,
+ xcm_config::{XcmConfig, XcmRouter},
+ Runtime, *,
+};
+use xcm::latest::prelude::*;
+
+use bridge_hub_test_utils::*;
+
+fn execute_on_runtime(
+ with_para_id: u32,
+ open_hrmp_to_para_id: Option,
+ execute: impl FnOnce() -> R,
+) -> R {
+ new_test_ext::(with_para_id.into(), 3).execute_with(|| {
+ if let Some(open_hrmp_to_para_id) = open_hrmp_to_para_id {
+ mock_open_hrmp_channel::(
+ with_para_id.into(),
+ open_hrmp_to_para_id.into(),
+ );
+ }
+ execute()
+ })
+}
+
+#[test]
+#[serial_test::serial]
+fn test_bridge_hub_wococo_dispatch_blob_and_xcm_routing_works() {
+ let universal_source_as_senders =
+ vec![X1(GlobalConsensus(Rococo)), X2(GlobalConsensus(Rococo), Parachain(1000))];
+ let runtime_para_id = bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID;
+ let destination_network_id = Wococo;
+ let destination_para_id = 1000;
+
+ for univeral_source_as_sender in universal_source_as_senders {
+ // 1. message is sent to other global consensus - Wococo(Here)
+ let bridging_message =
+ simulate_export_message::(
+ univeral_source_as_sender,
+ destination_network_id,
+ Here,
+ dummy_xcm(),
+ );
+ let result: MessageDispatchResult = execute_on_runtime(
+ runtime_para_id,
+ None,
+ || {
+ <>::MessageDispatch as MessageDispatch<_>>::dispatch(
+ &dummy_account(),
+ wrap_as_dispatch_message(bridging_message)
+ )
+ },
+ );
+ assert_eq!(result.dispatch_level_result, XcmBlobMessageDispatchResult::Dispatched);
+
+ // 2. message is sent to other global consensus and its parachains - Wococo(Here)
+ let bridging_message =
+ simulate_export_message::(
+ univeral_source_as_sender,
+ destination_network_id,
+ X1(Parachain(destination_para_id)),
+ dummy_xcm(),
+ );
+
+ // 2.1. WITHOUT hrmp channel -> RoutingError
+ let result: MessageDispatchResult = execute_on_runtime(
+ runtime_para_id,
+ None,
+ || {
+ <>::MessageDispatch as MessageDispatch<_>>::dispatch(
+ &dummy_account(),
+ wrap_as_dispatch_message(bridging_message.clone())
+ )
+ },
+ );
+ assert_eq!(
+ result.dispatch_level_result,
+ XcmBlobMessageDispatchResult::NotDispatched("DispatchBlobError::RoutingError")
+ );
+
+ // 2.1. WITH hrmp channel -> Ok
+ let result: MessageDispatchResult = execute_on_runtime(
+ runtime_para_id,
+ Some(destination_para_id),
+ || {
+ <>::MessageDispatch as MessageDispatch<_>>::dispatch(
+ &dummy_account(),
+ wrap_as_dispatch_message(bridging_message.clone())
+ )
+ },
+ );
+ assert_eq!(result.dispatch_level_result, XcmBlobMessageDispatchResult::Dispatched);
+ }
+}
+
+#[test]
+#[serial_test::serial]
+fn test_bridge_hub_rococo_dispatch_blob_and_xcm_routing_works() {
+ let universal_source_as_senders =
+ vec![X1(GlobalConsensus(Wococo)), X2(GlobalConsensus(Wococo), Parachain(1000))];
+ let runtime_para_id = bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID;
+ let destination_network_id = Rococo;
+ let destination_para_id = 1000;
+
+ for univeral_source_as_sender in universal_source_as_senders {
+ // 1. message is sent to other global consensus - Wococo(Here)
+ let bridging_message =
+ simulate_export_message::(
+ univeral_source_as_sender,
+ destination_network_id,
+ Here,
+ dummy_xcm(),
+ );
+ let result: MessageDispatchResult = execute_on_runtime(
+ runtime_para_id,
+ None,
+ || {
+ <>::MessageDispatch as MessageDispatch<_>>::dispatch(
+ &dummy_account(),
+ wrap_as_dispatch_message(bridging_message)
+ )
+ },
+ );
+ assert_eq!(result.dispatch_level_result, XcmBlobMessageDispatchResult::Dispatched);
+
+ // 2. message is sent to other global consensus and its parachains - Wococo(Here)
+ let bridging_message =
+ simulate_export_message::(
+ univeral_source_as_sender,
+ destination_network_id,
+ X1(Parachain(destination_para_id)),
+ dummy_xcm(),
+ );
+
+ // 2.1. WITHOUT hrmp channel -> RoutingError
+ let result: MessageDispatchResult = execute_on_runtime(
+ runtime_para_id,
+ None,
+ || {
+ <>::MessageDispatch as MessageDispatch<_>>::dispatch(
+ &dummy_account(),
+ wrap_as_dispatch_message(bridging_message.clone())
+ )
+ },
+ );
+ assert_eq!(
+ result.dispatch_level_result,
+ XcmBlobMessageDispatchResult::NotDispatched("DispatchBlobError::RoutingError")
+ );
+
+ // 2.1. WITH hrmp channel -> Ok
+ let result: MessageDispatchResult = execute_on_runtime(
+ runtime_para_id,
+ Some(destination_para_id),
+ || {
+ <>::MessageDispatch as MessageDispatch<_>>::dispatch(
+ &dummy_account(),
+ wrap_as_dispatch_message(bridging_message.clone())
+ )
+ },
+ );
+ assert_eq!(result.dispatch_level_result, XcmBlobMessageDispatchResult::Dispatched);
+ }
+}
diff --git a/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml
new file mode 100644
index 0000000000..2418111ebb
--- /dev/null
+++ b/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "bridge-hub-test-utils"
+version = "0.1.0"
+authors = ["Parity Technologies "]
+edition = "2021"
+description = "Utils for BridgeHub testing"
+
+[dependencies]
+
+# Substrate
+frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
+frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
+
+# Cumulus
+parachains-common = { path = "../../../common", default-features = false }
+cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false }
+cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false }
+cumulus-primitives-parachain-inherent = { path = "../../../../primitives/parachain-inherent", default-features = false }
+cumulus-test-relay-sproof-builder = { path = "../../../../test/relay-sproof-builder", default-features = false }
+parachain-info = { path = "../../../../parachains/pallets/parachain-info", default-features = false }
+
+# Polkadot
+polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" }
+pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" }
+pallet-xcm-benchmarks = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false, optional = true }
+xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" }
+xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" }
+xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" }
+
+# Bridges
+bp-messages = { path = "../../../../bridges/primitives/messages", default-features = false }
+
+[features]
+default = [ "std" ]
+std = [
+ "frame-support/std",
+ "frame-system/std",
+ "bp-messages/std",
+ "parachains-common/std",
+ "parachain-info/std",
+ "cumulus-primitives-core/std",
+ "cumulus-pallet-parachain-system/std",
+ "cumulus-primitives-parachain-inherent/std",
+ "cumulus-test-relay-sproof-builder/std",
+ "polkadot-parachain/std",
+ "pallet-xcm/std",
+ "xcm/std",
+ "xcm-builder/std",
+ "xcm-executor/std",
+]
diff --git a/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs b/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs
new file mode 100644
index 0000000000..568ea39b57
--- /dev/null
+++ b/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs
@@ -0,0 +1,171 @@
+// Copyright 2022 Parity Technologies (UK) Ltd.
+// This file is part of Cumulus.
+
+// Cumulus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Cumulus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Cumulus. If not, see .
+
+use bp_messages::{
+ target_chain::{DispatchMessage, DispatchMessageData},
+ MessageKey,
+};
+use cumulus_primitives_core::{AbridgedHrmpChannel, ParaId, PersistedValidationData};
+use cumulus_primitives_parachain_inherent::ParachainInherentData;
+use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
+use frame_support::{
+ dispatch::{RawOrigin, UnfilteredDispatchable},
+ inherent::{InherentData, ProvideInherent},
+ sp_io,
+ traits::Get,
+};
+use parachains_common::AccountId;
+use polkadot_parachain::primitives::{HrmpChannelId, RelayChainBlockNumber};
+use xcm::{latest::prelude::*, prelude::XcmVersion};
+use xcm_builder::{HaulBlob, HaulBlobExporter};
+use xcm_executor::traits::{validate_export, ExportXcm};
+
+/// Dummy xcm
+pub fn dummy_xcm() -> Xcm<()> {
+ vec![Trap(42)].into()
+}
+
+pub fn wrap_as_dispatch_message(payload: Vec) -> DispatchMessage> {
+ DispatchMessage {
+ key: MessageKey { lane_id: [0, 0, 0, 0], nonce: 1 },
+ data: DispatchMessageData { payload: Ok(payload) },
+ }
+}
+
+/// Dummy account
+pub fn dummy_account() -> AccountId {
+ AccountId::from([0u8; 32])
+}
+
+/// Macro used for simulate_export_message and capturing bytes
+macro_rules! grab_haul_blob (
+ ($name:ident, $grabbed_payload:ident) => {
+ static mut $grabbed_payload: Option> = None;
+ struct $name;
+ impl HaulBlob for $name {
+ fn haul_blob(blob: Vec) {
+ unsafe {
+ $grabbed_payload = Some(blob);
+ }
+ }
+ }
+ }
+);
+
+/// Simulates HaulBlobExporter and all its wrapping and captures generated plain bytes
+pub fn simulate_export_message>(
+ sender: Junctions,
+ destination_network: NetworkId,
+ destination: Junctions,
+ xcm: xcm::v3::Xcm<()>,
+) -> Vec {
+ grab_haul_blob!(GrabbingHaulBlob, GRABBED_HAUL_BLOB_PAYLOAD);
+
+ let channel = 1_u32;
+ let universal_source = sender;
+
+ // simulate XCM message export
+ let (ticket, fee) = validate_export::>(
+ destination_network,
+ channel,
+ universal_source,
+ destination,
+ xcm,
+ )
+ .expect("validate_export error");
+ println!("[MessageExporter::fee] {:?}", fee);
+ let result = HaulBlobExporter::::deliver(ticket)
+ .expect("deliver error");
+ println!("[MessageExporter::deliver] {:?}", result);
+
+ unsafe { GRABBED_HAUL_BLOB_PAYLOAD.as_ref().unwrap().clone() }
+}
+
+/// Initialize runtime/externalities
+pub fn new_test_ext(
+ para_id: ParaId,
+ xcm_version: XcmVersion,
+) -> sp_io::TestExternalities {
+ let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap();
+ >::assimilate_storage(
+ &pallet_xcm::GenesisConfig { safe_xcm_version: Some(xcm_version) },
+ &mut t,
+ )
+ .unwrap();
+ >::assimilate_storage(
+ ¶chain_info::GenesisConfig { parachain_id: para_id },
+ &mut t,
+ )
+ .unwrap();
+
+ sp_io::TestExternalities::new(t)
+}
+
+/// Helper function which emulates opening HRMP channel which is needed for XcmpQueue xcm router to pass
+pub fn mock_open_hrmp_channel<
+ C: cumulus_pallet_parachain_system::Config,
+ T: ProvideInherent>,
+>(
+ sender: ParaId,
+ recipient: ParaId,
+) {
+ let n = 1_u32;
+ let mut sproof_builder = RelayStateSproofBuilder::default();
+ sproof_builder.para_id = sender;
+ sproof_builder.hrmp_channels.insert(
+ HrmpChannelId { sender, recipient },
+ AbridgedHrmpChannel {
+ max_capacity: 10,
+ max_total_size: 10_000_000_u32,
+ max_message_size: 10_000_000_u32,
+ msg_count: 10,
+ total_size: 10_000_000_u32,
+ mqc_head: None,
+ },
+ );
+ sproof_builder.hrmp_egress_channel_index = Some(vec![recipient]);
+
+ let (relay_parent_storage_root, relay_chain_state) = sproof_builder.into_state_root_and_proof();
+ let vfp = PersistedValidationData {
+ relay_parent_number: n as RelayChainBlockNumber,
+ relay_parent_storage_root,
+ ..Default::default()
+ };
+ // It is insufficient to push the validation function params
+ // to storage; they must also be included in the inherent data.
+ let inherent_data = {
+ let mut inherent_data = InherentData::default();
+ let system_inherent_data = ParachainInherentData {
+ validation_data: vfp.clone(),
+ relay_chain_state,
+ downward_messages: Default::default(),
+ horizontal_messages: Default::default(),
+ };
+ inherent_data
+ .put_data(
+ cumulus_primitives_parachain_inherent::INHERENT_IDENTIFIER,
+ &system_inherent_data,
+ )
+ .expect("failed to put VFP inherent");
+ inherent_data
+ };
+
+ // execute the block
+ T::create_inherent(&inherent_data)
+ .expect("got an inherent")
+ .dispatch_bypass_filter(RawOrigin::None.into())
+ .expect("dispatch succeeded");
+}
diff --git a/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml b/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml
index 0baba72ecd..845e165c31 100644
--- a/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml
+++ b/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml
@@ -79,3 +79,15 @@ cumulus_based = true
"--no-mdns", "--bootnodes {{'rockmine-collator1'|zombie('multiAddress')}}",
"-- --port 51433 --rpc-port 58833 --ws-port 58843 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}"
]
+
+[[hrmp_channels]]
+sender = 1000
+recipient = 1013
+max_capacity = 4
+max_message_size = 524288
+
+[[hrmp_channels]]
+sender = 1013
+recipient = 1000
+max_capacity = 4
+max_message_size = 524288
diff --git a/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml b/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml
index ba498c0a78..8c86fca58e 100644
--- a/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml
+++ b/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml
@@ -79,3 +79,15 @@ cumulus_based = true
"--no-mdns", "--bootnodes {{'wockmint-collator1'|zombie('multiAddress')}}",
"-- --port 31433 --rpc-port 38833 --ws-port 38843 --no-mdns", "--bootnodes {{'alice-validator-wo'|zombie('multiAddress')}}"
]
+
+[[hrmp_channels]]
+sender = 1000
+recipient = 1014
+max_capacity = 4
+max_message_size = 524288
+
+[[hrmp_channels]]
+sender = 1014
+recipient = 1000
+max_capacity = 4
+max_message_size = 524288