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