diff --git a/Cargo.lock b/Cargo.lock index 40da2844f9723..3de740d0ad30d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9803,6 +9803,7 @@ dependencies = [ name = "pallet-bridge-messages" version = "0.7.0" dependencies = [ + "bp-header-chain", "bp-messages", "bp-runtime", "bp-test-utils", @@ -9812,11 +9813,14 @@ dependencies = [ "log", "num-traits", "pallet-balances", + "pallet-bridge-grandpa", "parity-scale-codec", "scale-info", + "sp-core", "sp-io", "sp-runtime", "sp-std 14.0.0", + "sp-trie", ] [[package]] diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 74049031afe63..fe4d049df0313 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -53,6 +53,7 @@ xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/x [dev-dependencies] bp-test-utils = { path = "../../primitives/test-utils" } pallet-balances = { path = "../../../substrate/frame/balances" } +pallet-bridge-messages = { path = "../../modules/messages", features = ["std", "test-helpers"] } [features] default = ["std"] @@ -93,6 +94,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-bridge-grandpa/runtime-benchmarks", "pallet-bridge-messages/runtime-benchmarks", + "pallet-bridge-messages/test-helpers", "pallet-bridge-parachains/runtime-benchmarks", "pallet-bridge-relayers/runtime-benchmarks", "pallet-utility/runtime-benchmarks", diff --git a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs index 92810290f95e7..9f559dc13b64d 100644 --- a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs +++ b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs @@ -319,6 +319,7 @@ mod integrity_tests { pub mod per_message { use super::*; + use bp_messages::ChainWithMessages; use pallet_bridge_messages::WeightInfoExt; /// Ensures that the value of `PriorityBoostPerMessage` matches the value of @@ -339,7 +340,7 @@ mod integrity_tests { BalanceOf: Send + Sync + FixedPointOperand, { let maximal_messages_in_delivery_transaction = - Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); + Runtime::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; super::ensure_priority_boost_is_sane::>( "PriorityBoostPerMessage", maximal_messages_in_delivery_transaction, diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 252bb202649b0..6a982bd7e576f 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -22,9 +22,9 @@ use crate::messages_call_ext::{ CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType, }; -use bp_messages::{LaneId, MessageNonce}; +use bp_messages::{ChainWithMessages, LaneId, MessageNonce}; use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; -use bp_runtime::{Parachain, RangeInclusiveExt, StaticStrProvider}; +use bp_runtime::{Chain, Parachain, RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo}, @@ -293,7 +293,7 @@ pub trait RefundSignedExtension: ::Id::get(), ::Instance, - >>::BridgedChainId::get(), + >>::BridgedChain::ID, if call_info.is_receive_messages_proof_call() { RewardsAccountOwner::ThisChain } else { @@ -406,8 +406,7 @@ pub trait RefundSignedExtension: // a quick check to avoid invalid high-priority transactions let max_unconfirmed_messages_in_confirmation_tx = ::Instance, - >>::MaxUnconfirmedMessagesAtInboundLane::get( - ); + >>::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; if bundled_messages > max_unconfirmed_messages_in_confirmation_tx { return None } @@ -1163,7 +1162,7 @@ pub(crate) mod tests { RuntimeCall::BridgeMessages(MessagesCall::receive_messages_delivery_proof { proof: FromBridgedChainMessagesDeliveryProof { bridged_header_hash: Default::default(), - storage_proof: vec![], + storage_proof: Default::default(), lane: TestLaneId::get(), }, relayers_state: UnrewardedRelayersState { @@ -1326,8 +1325,10 @@ pub(crate) mod tests { best_stored_nonce: 100, }, unrewarded_relayers: UnrewardedRelayerOccupation { - free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(), - free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(), + free_relayer_slots: + BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + free_message_slots: + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, }, }), ), @@ -1396,8 +1397,10 @@ pub(crate) mod tests { best_stored_nonce: 100, }, unrewarded_relayers: UnrewardedRelayerOccupation { - free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(), - free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(), + free_relayer_slots: + BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + free_message_slots: + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, }, }), ), @@ -1458,8 +1461,10 @@ pub(crate) mod tests { best_stored_nonce: 100, }, unrewarded_relayers: UnrewardedRelayerOccupation { - free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(), - free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(), + free_relayer_slots: + BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + free_message_slots: + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, }, }), ), @@ -1498,8 +1503,10 @@ pub(crate) mod tests { best_stored_nonce: 100, }, unrewarded_relayers: UnrewardedRelayerOccupation { - free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(), - free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(), + free_relayer_slots: + BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + free_message_slots: + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, }, }, )), @@ -1734,14 +1741,16 @@ pub(crate) mod tests { let fns = [run_validate, run_grandpa_validate, run_messages_validate]; for f in fns { - let priority_of_max_messages_delivery = - f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get())) - .unwrap() - .priority; - let priority_of_more_than_max_messages_delivery = - f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1)) - .unwrap() - .priority; + let priority_of_max_messages_delivery = f(message_delivery_call( + 100 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + )) + .unwrap() + .priority; + let priority_of_more_than_max_messages_delivery = f(message_delivery_call( + 100 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1, + )) + .unwrap() + .priority; assert!( priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery, @@ -2864,7 +2873,8 @@ pub(crate) mod tests { #[test] fn does_not_panic_on_boosting_priority_of_empty_message_delivery_transaction() { run_test(|| { - let best_delivered_message = MaxUnconfirmedMessagesAtInboundLane::get(); + let best_delivered_message = + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; initialize_environment(100, 100, best_delivered_message); // register relayer so it gets priority boost diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index d3827a14dd6cc..9b41a4bf4902d 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -19,10 +19,7 @@ //! Most of the tests in this module assume that the bridge is using standard (see `crate::messages` //! module for details) configuration. -use crate::{messages, messages::MessageBridge}; - -use bp_messages::{InboundLaneData, MessageNonce}; -use bp_runtime::{Chain, ChainId}; +use bp_messages::{ChainWithMessages, InboundLaneData, MessageNonce}; use codec::Encode; use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight}; use frame_system::limits; @@ -50,23 +47,6 @@ macro_rules! assert_chain_types( } ); -/// Macro that ensures that the bridge GRANDPA pallet is configured properly to bridge with given -/// chain. -#[macro_export] -macro_rules! assert_bridge_grandpa_pallet_types( - ( runtime: $r:path, with_bridged_chain_grandpa_instance: $i:path, bridged_chain: $bridged:path ) => { - { - // if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard - // configuration is used), or something has broke existing configuration (meaning that all bridged chains - // and relays will stop functioning) - use pallet_bridge_grandpa::Config as GrandpaConfig; - use static_assertions::assert_type_eq_all; - - assert_type_eq_all!(<$r as GrandpaConfig<$i>>::BridgedChain, $bridged); - } - } -); - /// Macro that ensures that the bridge messages pallet is configured properly to bridge using given /// configuration. #[macro_export] @@ -74,32 +54,25 @@ macro_rules! assert_bridge_messages_pallet_types( ( runtime: $r:path, with_bridged_chain_messages_instance: $i:path, - bridge: $bridge:path ) => { { // if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard // configuration is used), or something has broke existing configuration (meaning that all bridged chains // and relays will stop functioning) - use $crate::messages::{ - source::{FromThisChainMessagePayload, TargetHeaderChainAdapter}, - target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter}, - AccountIdOf, BalanceOf, BridgedChain, ThisChain, - }; + use $crate::messages_xcm_extension::XcmAsPlainPayload; use pallet_bridge_messages::Config as MessagesConfig; use static_assertions::assert_type_eq_all; - assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload); + assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, XcmAsPlainPayload); - assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf>); - - assert_type_eq_all!(<$r as MessagesConfig<$i>>::TargetHeaderChain, TargetHeaderChainAdapter<$bridge>); - assert_type_eq_all!(<$r as MessagesConfig<$i>>::SourceHeaderChain, SourceHeaderChainAdapter<$bridge>); + // TODO: https://github.com/paritytech/parity-bridges-common/issues/1666: check ThisChain, BridgedChain + // and BridgedHeaderChain types } } ); /// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`, -/// `assert_bridge_grandpa_pallet_types` and `assert_bridge_messages_pallet_types`. It may be used +/// and `assert_bridge_messages_pallet_types`. It may be used /// at the chain that is implementing complete standard messages bridge (i.e. with bridge GRANDPA /// and messages pallets deployed). #[macro_export] @@ -108,20 +81,13 @@ macro_rules! assert_complete_bridge_types( runtime: $r:path, with_bridged_chain_grandpa_instance: $gi:path, with_bridged_chain_messages_instance: $mi:path, - bridge: $bridge:path, this_chain: $this:path, bridged_chain: $bridged:path, ) => { $crate::assert_chain_types!(runtime: $r, this_chain: $this); - $crate::assert_bridge_grandpa_pallet_types!( - runtime: $r, - with_bridged_chain_grandpa_instance: $gi, - bridged_chain: $bridged - ); $crate::assert_bridge_messages_pallet_types!( runtime: $r, with_bridged_chain_messages_instance: $mi, - bridge: $bridge ); } ); @@ -184,20 +150,8 @@ where ); } -/// Parameters for asserting messages pallet constants. -#[derive(Debug)] -pub struct AssertBridgeMessagesPalletConstants { - /// Maximal number of unrewarded relayer entries in a confirmation transaction at the bridged - /// chain. - pub max_unrewarded_relayers_in_bridged_confirmation_tx: MessageNonce, - /// Maximal number of unconfirmed messages in a confirmation transaction at the bridged chain. - pub max_unconfirmed_messages_in_bridged_confirmation_tx: MessageNonce, - /// Identifier of the bridged chain. - pub bridged_chain_id: ChainId, -} - /// Test that the constants, used in messages pallet configuration are valid. -pub fn assert_bridge_messages_pallet_constants(params: AssertBridgeMessagesPalletConstants) +pub fn assert_bridge_messages_pallet_constants() where R: pallet_bridge_messages::Config, MI: 'static, @@ -207,19 +161,6 @@ where "ActiveOutboundLanes ({:?}) must not be empty", R::ActiveOutboundLanes::get(), ); - assert!( - R::MaxUnrewardedRelayerEntriesAtInboundLane::get() <= params.max_unrewarded_relayers_in_bridged_confirmation_tx, - "MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}", - R::MaxUnrewardedRelayerEntriesAtInboundLane::get(), - params.max_unrewarded_relayers_in_bridged_confirmation_tx, - ); - assert!( - R::MaxUnconfirmedMessagesAtInboundLane::get() <= params.max_unconfirmed_messages_in_bridged_confirmation_tx, - "MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}", - R::MaxUnconfirmedMessagesAtInboundLane::get(), - params.max_unconfirmed_messages_in_bridged_confirmation_tx, - ); - assert_eq!(R::BridgedChainId::get(), params.bridged_chain_id); } /// Parameters for asserting bridge pallet names. @@ -238,14 +179,12 @@ pub struct AssertBridgePalletNames<'a> { /// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants /// from chain primitives crates. -pub fn assert_bridge_pallet_names(params: AssertBridgePalletNames) +pub fn assert_bridge_pallet_names(params: AssertBridgePalletNames) where - B: MessageBridge, R: pallet_bridge_grandpa::Config + pallet_bridge_messages::Config, GI: 'static, MI: 'static, { - assert_eq!(B::BRIDGED_MESSAGES_PALLET_NAME, params.with_this_chain_messages_pallet_name); assert_eq!( pallet_bridge_grandpa::PalletOwner::::storage_value_final_key().to_vec(), bp_runtime::storage_value_key(params.with_bridged_chain_grandpa_pallet_name, "PalletOwner",).0, @@ -265,32 +204,29 @@ where pub struct AssertCompleteBridgeConstants<'a> { /// Parameters to assert this chain constants. pub this_chain_constants: AssertChainConstants, - /// Parameters to assert messages pallet constants. - pub messages_pallet_constants: AssertBridgeMessagesPalletConstants, /// Parameters to assert pallet names constants. pub pallet_names: AssertBridgePalletNames<'a>, } /// All bridge-related constants tests for the complete standard messages bridge (i.e. with bridge /// GRANDPA and messages pallets deployed). -pub fn assert_complete_bridge_constants(params: AssertCompleteBridgeConstants) +pub fn assert_complete_bridge_constants(params: AssertCompleteBridgeConstants) where R: frame_system::Config + pallet_bridge_grandpa::Config + pallet_bridge_messages::Config, GI: 'static, MI: 'static, - B: MessageBridge, { assert_chain_constants::(params.this_chain_constants); assert_bridge_grandpa_pallet_constants::(); - assert_bridge_messages_pallet_constants::(params.messages_pallet_constants); - assert_bridge_pallet_names::(params.pallet_names); + assert_bridge_messages_pallet_constants::(); + assert_bridge_pallet_names::(params.pallet_names); } /// Check that the message lane weights are correct. pub fn check_message_lane_weights< - C: Chain, + C: ChainWithMessages, T: frame_system::Config + pallet_bridge_messages::Config, MessagesPalletInstance: 'static, >( @@ -309,13 +245,13 @@ pub fn check_message_lane_weights< pallet_bridge_messages::ensure_weights_are_correct::>(); // check that weights allow us to receive messages - let max_incoming_message_proof_size = bridged_chain_extra_storage_proof_size - .saturating_add(messages::target::maximal_incoming_message_size(C::max_extrinsic_size())); + let max_incoming_message_proof_size = + bridged_chain_extra_storage_proof_size.saturating_add(C::maximal_incoming_message_size()); pallet_bridge_messages::ensure_able_to_receive_message::>( C::max_extrinsic_size(), C::max_extrinsic_weight(), max_incoming_message_proof_size, - messages::target::maximal_incoming_message_dispatch_weight(C::max_extrinsic_weight()), + C::maximal_incoming_message_dispatch_weight(), ); // check that weights allow us to receive delivery confirmations diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index 5679acd6006cc..b65bb6041d561 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -20,11 +20,10 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod extensions; -pub mod messages; + pub mod messages_api; pub mod messages_benchmarking; pub mod messages_call_ext; -pub mod messages_generation; pub mod messages_xcm_extension; pub mod parachains_benchmarking; diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs deleted file mode 100644 index 9f71d542937e8..0000000000000 --- a/bridges/bin/runtime-common/src/messages.rs +++ /dev/null @@ -1,699 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Parity Bridges Common. - -// Parity Bridges Common 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. - -// Parity Bridges Common 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 Parity Bridges Common. If not, see . - -//! Types that allow runtime to act as a source/target endpoint of message lanes. -//! -//! Messages are assumed to be encoded `Call`s of the target chain. Call-dispatch -//! pallet is used to dispatch incoming messages. Message identified by a tuple -//! of to elements - message lane id and message nonce. - -use bp_header_chain::HeaderChain; -use bp_messages::{ - source_chain::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChain}, - target_chain::{ - FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, - }, - InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, - VerificationError, -}; -pub use bp_runtime::{ - Chain, RangeInclusiveExt, RawStorageProof, Size, TrustedVecDb, UnderlyingChainOf, - UnderlyingChainProvider, UntrustedVecDb, -}; -use frame_support::{traits::Get, weights::Weight}; -use sp_std::{marker::PhantomData, vec::Vec}; - -/// Bidirectional message bridge. -pub trait MessageBridge { - /// Name of the paired messages pallet instance at the Bridged chain. - /// - /// Should be the name that is used in the `construct_runtime!()` macro. - const BRIDGED_MESSAGES_PALLET_NAME: &'static str; - - /// This chain in context of message bridge. - type ThisChain: ThisChainWithMessages; - /// Bridged chain in context of message bridge. - type BridgedChain: BridgedChainWithMessages; - /// Bridged header chain. - type BridgedHeaderChain: HeaderChain>; -} - -/// This chain that has `pallet-bridge-messages` module. -pub trait ThisChainWithMessages: UnderlyingChainProvider { - /// Call origin on the chain. - type RuntimeOrigin; -} - -/// Bridged chain that has `pallet-bridge-messages` module. -pub trait BridgedChainWithMessages: UnderlyingChainProvider {} - -/// This chain in context of message bridge. -pub type ThisChain = ::ThisChain; -/// Bridged chain in context of message bridge. -pub type BridgedChain = ::BridgedChain; -/// Hash used on the chain. -pub type HashOf = bp_runtime::HashOf<::Chain>; -/// Hasher used on the chain. -pub type HasherOf = bp_runtime::HasherOf>; -/// Account id used on the chain. -pub type AccountIdOf = bp_runtime::AccountIdOf>; -/// Type of balances that is used on the chain. -pub type BalanceOf = bp_runtime::BalanceOf>; - -/// Sub-module that is declaring types required for processing This -> Bridged chain messages. -pub mod source { - use super::*; - - /// Message payload for This -> Bridged chain messages. - pub type FromThisChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload; - - /// Maximal size of outbound message payload. - pub struct FromThisChainMaximalOutboundPayloadSize(PhantomData); - - impl Get for FromThisChainMaximalOutboundPayloadSize { - fn get() -> u32 { - maximal_message_size::() - } - } - - /// 'Parsed' message delivery proof - inbound lane id and its state. - pub type ParsedMessagesDeliveryProofFromBridgedChain = - (LaneId, InboundLaneData>>); - - /// Return maximal message size of This -> Bridged chain message. - pub fn maximal_message_size() -> u32 { - super::target::maximal_incoming_message_size( - UnderlyingChainOf::>::max_extrinsic_size(), - ) - } - - /// `TargetHeaderChain` implementation that is using default types and perform default checks. - pub struct TargetHeaderChainAdapter(PhantomData); - - impl TargetHeaderChain>> - for TargetHeaderChainAdapter - { - type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof>>; - - fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), VerificationError> { - verify_chain_message::(payload) - } - - fn verify_messages_delivery_proof( - proof: Self::MessagesDeliveryProof, - ) -> Result<(LaneId, InboundLaneData>>), VerificationError> { - verify_messages_delivery_proof::(proof) - } - } - - /// Do basic Bridged-chain specific verification of This -> Bridged chain message. - /// - /// Ok result from this function means that the delivery transaction with this message - /// may be 'mined' by the target chain. - pub fn verify_chain_message( - payload: &FromThisChainMessagePayload, - ) -> Result<(), VerificationError> { - // IMPORTANT: any error that is returned here is fatal for the bridge, because - // this code is executed at the bridge hub and message sender actually lives - // at some sibling parachain. So we are failing **after** the message has been - // sent and we can't report it back to sender (unless error report mechanism is - // embedded into message and its dispatcher). - - // apart from maximal message size check (see below), we should also check the message - // dispatch weight here. But we assume that the bridged chain will just push the message - // to some queue (XCMP, UMP, DMP), so the weight is constant and fits the block. - - // The maximal size of extrinsic at Substrate-based chain depends on the - // `frame_system::Config::MaximumBlockLength` and - // `frame_system::Config::AvailableBlockRatio` constants. This check is here to be sure that - // the lane won't stuck because message is too large to fit into delivery transaction. - // - // **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not - // the message itself. The proof is always larger than the message. But unless chain state - // is enormously large, it should be several dozens/hundreds of bytes. The delivery - // transaction also contains signatures and signed extensions. Because of this, we reserve - // 1/3 of the the maximal extrinsic size for this data. - if payload.len() > maximal_message_size::() as usize { - return Err(VerificationError::MessageTooLarge) - } - - Ok(()) - } - - /// Verify proof of This -> Bridged chain messages delivery. - /// - /// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged - /// parachains, please use the `verify_messages_delivery_proof_from_parachain`. - pub fn verify_messages_delivery_proof( - proof: FromBridgedChainMessagesDeliveryProof>>, - ) -> Result, VerificationError> { - let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = - proof; - let mut storage = - B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof) - .map_err(VerificationError::HeaderChain)?; - // Messages delivery proof is just proof of single storage key read => any error - // is fatal. - let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key( - B::BRIDGED_MESSAGES_PALLET_NAME, - &lane, - ); - let inbound_lane_data = storage - .read_and_decode_mandatory_value(storage_inbound_lane_data_key.0.as_ref()) - .map_err(VerificationError::InboundLaneStorage)?; - - // check that the storage proof doesn't have any untouched trie nodes - storage.ensure_no_unused_nodes().map_err(VerificationError::StorageProof)?; - - Ok((lane, inbound_lane_data)) - } -} - -/// Sub-module that is declaring types required for processing Bridged -> This chain messages. -pub mod target { - use super::*; - - /// Decoded Bridged -> This message payload. - pub type FromBridgedChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload; - - /// Return maximal dispatch weight of the message we're able to receive. - pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight { - maximal_extrinsic_weight / 2 - } - - /// Return maximal message size given maximal extrinsic size. - pub fn maximal_incoming_message_size(maximal_extrinsic_size: u32) -> u32 { - maximal_extrinsic_size / 3 * 2 - } - - /// `SourceHeaderChain` implementation that is using default types and perform default checks. - pub struct SourceHeaderChainAdapter(PhantomData); - - impl SourceHeaderChain for SourceHeaderChainAdapter { - type MessagesProof = FromBridgedChainMessagesProof>>; - - fn verify_messages_proof( - proof: Self::MessagesProof, - messages_count: u32, - ) -> Result, VerificationError> { - verify_messages_proof::(proof, messages_count) - } - } - - /// Verify proof of Bridged -> This chain messages. - /// - /// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged - /// parachains, please use the `verify_messages_proof_from_parachain`. - /// - /// The `messages_count` argument verification (sane limits) is supposed to be made - /// outside of this function. This function only verifies that the proof declares exactly - /// `messages_count` messages. - pub fn verify_messages_proof( - proof: FromBridgedChainMessagesProof>>, - messages_count: u32, - ) -> Result, VerificationError> { - let FromBridgedChainMessagesProof { - bridged_header_hash, - storage, - lane, - nonces_start, - nonces_end, - } = proof; - let storage = B::BridgedHeaderChain::verify_vec_db_storage(bridged_header_hash, storage) - .map_err(VerificationError::HeaderChain)?; - let mut parser = StorageAdapter:: { storage, _dummy: Default::default() }; - let nonces_range = nonces_start..=nonces_end; - - // receiving proofs where end < begin is ok (if proof includes outbound lane state) - let messages_in_the_proof = nonces_range.checked_len().unwrap_or(0); - if messages_in_the_proof != MessageNonce::from(messages_count) { - return Err(VerificationError::MessagesCountMismatch) - } - - // Read messages first. All messages that are claimed to be in the proof must - // be in the proof. So any error in `read_value`, or even missing value is fatal. - // - // Mind that we allow proofs with no messages if outbound lane state is proved. - let mut messages = Vec::with_capacity(messages_in_the_proof as _); - for nonce in nonces_range { - let message_key = MessageKey { lane_id: lane, nonce }; - let message_payload = parser.read_and_decode_message_payload(&message_key)?; - messages.push(Message { key: message_key, payload: message_payload }); - } - - // Now let's check if proof contains outbound lane state proof. It is optional, so - // we simply ignore `read_value` errors and missing value. - let proved_lane_messages = ProvedLaneMessages { - lane_state: parser.read_and_decode_outbound_lane_data(&lane)?, - messages, - }; - - // Now we may actually check if the proof is empty or not. - if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() { - return Err(VerificationError::EmptyMessageProof) - } - - // Check that the `VecDb` doesn't have any untouched keys. - parser.storage.ensure_no_unused_keys().map_err(VerificationError::VecDb)?; - - // We only support single lane messages in this generated_schema - let mut proved_messages = ProvedMessages::new(); - proved_messages.insert(lane, proved_lane_messages); - - Ok(proved_messages) - } - - struct StorageAdapter { - storage: TrustedVecDb, - _dummy: sp_std::marker::PhantomData, - } - - impl StorageAdapter { - fn read_and_decode_outbound_lane_data( - &mut self, - lane_id: &LaneId, - ) -> Result, VerificationError> { - let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key( - B::BRIDGED_MESSAGES_PALLET_NAME, - lane_id, - ); - - self.storage - .get_and_decode_optional(&storage_outbound_lane_data_key) - .map_err(VerificationError::OutboundLaneStorage) - } - - fn read_and_decode_message_payload( - &mut self, - message_key: &MessageKey, - ) -> Result { - let storage_message_key = bp_messages::storage_keys::message_key( - B::BRIDGED_MESSAGES_PALLET_NAME, - &message_key.lane_id, - message_key.nonce, - ); - self.storage - .get_and_decode_mandatory(&storage_message_key) - .map_err(VerificationError::MessageStorage) - } - } -} - -/// The `BridgeMessagesCall` used by a chain. -pub type BridgeMessagesCallOf = bp_messages::BridgeMessagesCall< - bp_runtime::AccountIdOf, - FromBridgedChainMessagesProof>, - bp_messages::source_chain::FromBridgedChainMessagesDeliveryProof>, ->; - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - messages_generation::{ - encode_all_messages, encode_lane_data, prepare_messages_storage_proof, - }, - mock::*, - }; - use bp_header_chain::{HeaderChainError, StoredHeaderDataBuilder}; - use bp_runtime::{HeaderId, VecDbError}; - use codec::Encode; - use sp_core::H256; - use sp_runtime::traits::Header as _; - - #[test] - fn verify_chain_message_rejects_message_with_too_large_declared_weight() { - assert!(source::verify_chain_message::(&vec![ - 42; - BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT - - 1 - ]) - .is_err()); - } - - #[test] - fn verify_chain_message_rejects_message_too_large_message() { - assert!(source::verify_chain_message::(&vec![ - 0; - source::maximal_message_size::() - as usize + 1 - ],) - .is_err()); - } - - #[test] - fn verify_chain_message_accepts_maximal_message() { - assert_eq!( - source::verify_chain_message::(&vec![ - 0; - source::maximal_message_size::() - as _ - ],), - Ok(()), - ); - } - - fn using_messages_proof( - nonces_end: MessageNonce, - outbound_lane_data: Option, - encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option>, - encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, - add_duplicate_key: bool, - add_unused_key: bool, - test: impl Fn(FromBridgedChainMessagesProof) -> R, - ) -> R { - let (state_root, storage) = prepare_messages_storage_proof::( - TEST_LANE_ID, - 1..=nonces_end, - outbound_lane_data, - bp_runtime::StorageProofSize::Minimal(0), - vec![42], - encode_message, - encode_outbound_lane_data, - add_duplicate_key, - add_unused_key, - ); - - sp_io::TestExternalities::new(Default::default()).execute_with(move || { - let bridged_header = BridgedChainHeader::new( - 0, - Default::default(), - state_root, - Default::default(), - Default::default(), - ); - let bridged_header_hash = bridged_header.hash(); - - pallet_bridge_grandpa::BestFinalized::::put(HeaderId( - 0, - bridged_header_hash, - )); - pallet_bridge_grandpa::ImportedHeaders::::insert( - bridged_header_hash, - bridged_header.build(), - ); - test(FromBridgedChainMessagesProof { - bridged_header_hash, - storage, - lane: TEST_LANE_ID, - nonces_start: 1, - nonces_end, - }) - }) - } - - #[test] - fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() { - assert_eq!( - using_messages_proof( - 10, - None, - encode_all_messages, - encode_lane_data, - false, - false, - |proof| { target::verify_messages_proof::(proof, 5) } - ), - Err(VerificationError::MessagesCountMismatch), - ); - } - - #[test] - fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() { - assert_eq!( - using_messages_proof( - 10, - None, - encode_all_messages, - encode_lane_data, - false, - false, - |proof| { target::verify_messages_proof::(proof, 15) } - ), - Err(VerificationError::MessagesCountMismatch), - ); - } - - #[test] - fn message_proof_is_rejected_if_header_is_missing_from_the_chain() { - assert_eq!( - using_messages_proof( - 10, - None, - encode_all_messages, - encode_lane_data, - false, - false, - |proof| { - let bridged_header_hash = - pallet_bridge_grandpa::BestFinalized::::get().unwrap().1; - pallet_bridge_grandpa::ImportedHeaders::::remove( - bridged_header_hash, - ); - target::verify_messages_proof::(proof, 10) - } - ), - Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)), - ); - } - - #[test] - fn message_proof_is_rejected_if_header_state_root_mismatches() { - assert_eq!( - using_messages_proof( - 10, - None, - encode_all_messages, - encode_lane_data, - false, - false, - |proof| { - let bridged_header_hash = - pallet_bridge_grandpa::BestFinalized::::get().unwrap().1; - pallet_bridge_grandpa::ImportedHeaders::::insert( - bridged_header_hash, - BridgedChainHeader::new( - 0, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ) - .build(), - ); - target::verify_messages_proof::(proof, 10) - } - ), - Err(VerificationError::HeaderChain(HeaderChainError::VecDb(VecDbError::InvalidProof))), - ); - } - - #[test] - fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() { - assert_eq!( - using_messages_proof( - 10, - None, - encode_all_messages, - encode_lane_data, - true, - false, - |proof| { target::verify_messages_proof::(proof, 10) }, - ), - Err(VerificationError::HeaderChain(HeaderChainError::VecDb(VecDbError::InvalidProof))), - ); - } - - #[test] - fn message_proof_is_rejected_if_it_has_unused_trie_nodes() { - assert_eq!( - using_messages_proof( - 10, - None, - encode_all_messages, - encode_lane_data, - false, - true, - |proof| { target::verify_messages_proof::(proof, 10) }, - ), - Err(VerificationError::VecDb(VecDbError::UnusedKey)), - ); - } - - #[test] - fn message_proof_is_rejected_if_required_message_is_missing() { - matches!( - using_messages_proof( - 10, - None, - |n, m| if n != 5 { Some(m.encode()) } else { None }, - encode_lane_data, - false, - false, - |proof| target::verify_messages_proof::(proof, 10) - ), - Err(VerificationError::MessageStorage(VecDbError::EmptyVal)), - ); - } - - #[test] - fn message_proof_is_rejected_if_message_decode_fails() { - matches!( - using_messages_proof( - 10, - None, - |n, m| { - let mut m = m.encode(); - if n == 5 { - m = vec![42] - } - Some(m) - }, - encode_lane_data, - false, - false, - |proof| target::verify_messages_proof::(proof, 10), - ), - Err(VerificationError::MessageStorage(VecDbError::DecodeError)), - ); - } - - #[test] - fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() { - matches!( - using_messages_proof( - 10, - Some(OutboundLaneData { - oldest_unpruned_nonce: 1, - latest_received_nonce: 1, - latest_generated_nonce: 1, - }), - encode_all_messages, - |d| { - let mut d = d.encode(); - d.truncate(1); - d - }, - false, - false, - |proof| target::verify_messages_proof::(proof, 10), - ), - Err(VerificationError::OutboundLaneStorage(VecDbError::DecodeError)), - ); - } - - #[test] - fn message_proof_is_rejected_if_it_is_empty() { - assert_eq!( - using_messages_proof( - 0, - None, - encode_all_messages, - encode_lane_data, - false, - false, - |proof| { target::verify_messages_proof::(proof, 0) }, - ), - Err(VerificationError::EmptyMessageProof), - ); - } - - #[test] - fn non_empty_message_proof_without_messages_is_accepted() { - assert_eq!( - using_messages_proof( - 0, - Some(OutboundLaneData { - oldest_unpruned_nonce: 1, - latest_received_nonce: 1, - latest_generated_nonce: 1, - }), - encode_all_messages, - encode_lane_data, - false, - false, - |proof| target::verify_messages_proof::(proof, 0), - ), - Ok(vec![( - TEST_LANE_ID, - ProvedLaneMessages { - lane_state: Some(OutboundLaneData { - oldest_unpruned_nonce: 1, - latest_received_nonce: 1, - latest_generated_nonce: 1, - }), - messages: Vec::new(), - }, - )] - .into_iter() - .collect()), - ); - } - - #[test] - fn non_empty_message_proof_is_accepted() { - assert_eq!( - using_messages_proof( - 1, - Some(OutboundLaneData { - oldest_unpruned_nonce: 1, - latest_received_nonce: 1, - latest_generated_nonce: 1, - }), - encode_all_messages, - encode_lane_data, - false, - false, - |proof| target::verify_messages_proof::(proof, 1), - ), - Ok(vec![( - TEST_LANE_ID, - ProvedLaneMessages { - lane_state: Some(OutboundLaneData { - oldest_unpruned_nonce: 1, - latest_received_nonce: 1, - latest_generated_nonce: 1, - }), - messages: vec![Message { - key: MessageKey { lane_id: TEST_LANE_ID, nonce: 1 }, - payload: vec![42], - }], - }, - )] - .into_iter() - .collect()), - ); - } - - #[test] - fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() { - assert_eq!( - using_messages_proof( - 1, - None, - encode_all_messages, - encode_lane_data, - false, - false, - |mut proof| { - proof.nonces_end = u64::MAX; - target::verify_messages_proof::(proof, u32::MAX) - }, - ), - Err(VerificationError::MessagesCountMismatch), - ); - } -} diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs index 30614a21b9ebe..b95fa8f69d766 100644 --- a/bridges/bin/runtime-common/src/messages_benchmarking.rs +++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs @@ -19,25 +19,42 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{ - messages::{AccountIdOf, BridgedChain, HashOf, MessageBridge, ThisChain}, - messages_generation::{ - encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, - prepare_messages_storage_proof, - }, -}; +// use crate::{ +// messages::{BridgedChain, HashOf, ThisChain}, +// messages_generation::{ +// encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, +// prepare_messages_storage_proof, +// }, +// }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, MessagePayload, }; use bp_polkadot_core::parachains::ParaHash; -use bp_runtime::{Chain, Parachain, StorageProofSize, UnderlyingChainOf}; +use bp_runtime::{Chain, Parachain, StorageProofSize}; +// TODO: https://github.com/paritytech/parity-bridges-common/issues/1666 - merge with message +// generation methods from messages pallet and use it here + +use bp_messages::{storage_keys, ChainWithMessages}; +use bp_runtime::{ + grow_trie_leaf_value, record_all_trie_keys, AccountIdOf, HashOf, HasherOf, UntrustedVecDb, +}; use codec::Encode; use frame_support::weights::Weight; -use pallet_bridge_messages::benchmarking::{MessageDeliveryProofParams, MessageProofParams}; -use sp_runtime::traits::{Header, Zero}; +use pallet_bridge_messages::{ + benchmarking::{MessageDeliveryProofParams, MessageProofParams}, + messages_generation::{encode_all_messages, encode_lane_data, prepare_messages_storage_proof}, + BridgedChainOf, ThisChainOf, +}; +use sp_runtime::{ + traits::{Header, Zero}, + StateVersion, +}; use sp_std::prelude::*; +use sp_trie::{ + LayoutV0, LayoutV1, MemoryDB, StorageProof, TrieConfiguration, TrieDBMutBuilder, TrieMut, +}; use xcm::latest::prelude::*; /// Prepare inbound bridge message according to given message proof parameters. @@ -75,27 +92,32 @@ fn prepare_inbound_message( /// This method is intended to be used when benchmarking pallet, linked to the chain that /// uses GRANDPA finality. For parachains, please use the `prepare_message_proof_from_parachain` /// function. -pub fn prepare_message_proof_from_grandpa_chain( +pub fn prepare_message_proof_from_grandpa_chain( params: MessageProofParams, message_generator: impl Fn(usize) -> MessagePayload, -) -> (FromBridgedChainMessagesProof>>, Weight) +) -> (FromBridgedChainMessagesProof>>, Weight) where - R: pallet_bridge_grandpa::Config>>, + R: pallet_bridge_grandpa::Config> + + pallet_bridge_messages::Config< + MI, + BridgedHeaderChain = pallet_bridge_grandpa::Pallet, + >, FI: 'static, - B: MessageBridge, + MI: 'static, { // prepare storage proof - let (state_root, storage) = prepare_messages_storage_proof::( - params.lane, - params.message_nonces.clone(), - params.outbound_lane_data.clone(), - params.size, - prepare_inbound_message(¶ms, message_generator), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (state_root, storage) = + prepare_messages_storage_proof::, ThisChainOf>( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data.clone(), + params.size, + |_| prepare_inbound_message(¶ms, &message_generator), + encode_all_messages, + encode_lane_data, + false, + false, + ); // update runtime storage let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(state_root); @@ -120,32 +142,33 @@ where /// This method is intended to be used when benchmarking pallet, linked to the chain that /// uses parachain finality. For GRANDPA chains, please use the /// `prepare_message_proof_from_grandpa_chain` function. -pub fn prepare_message_proof_from_parachain( +pub fn prepare_message_proof_from_parachain( params: MessageProofParams, message_generator: impl Fn(usize) -> MessagePayload, -) -> (FromBridgedChainMessagesProof>>, Weight) +) -> (FromBridgedChainMessagesProof>>, Weight) where - R: pallet_bridge_parachains::Config, + R: pallet_bridge_parachains::Config + pallet_bridge_messages::Config, PI: 'static, - B: MessageBridge, - UnderlyingChainOf>: Chain + Parachain, + MI: 'static, + BridgedChainOf: Chain + Parachain, { // prepare storage proof - let (state_root, storage) = prepare_messages_storage_proof::( - params.lane, - params.message_nonces.clone(), - params.outbound_lane_data.clone(), - params.size, - prepare_inbound_message(¶ms, message_generator), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (state_root, storage) = + prepare_messages_storage_proof::, ThisChainOf>( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data.clone(), + params.size, + |_| prepare_inbound_message(¶ms, &message_generator), + encode_all_messages, + encode_lane_data, + false, + false, + ); // update runtime storage let (_, bridged_header_hash) = - insert_header_to_parachains_pallet::>>(state_root); + insert_header_to_parachains_pallet::>(state_root); ( FromBridgedChainMessagesProof { @@ -164,21 +187,21 @@ where /// This method is intended to be used when benchmarking pallet, linked to the chain that /// uses GRANDPA finality. For parachains, please use the /// `prepare_message_delivery_proof_from_parachain` function. -pub fn prepare_message_delivery_proof_from_grandpa_chain( - params: MessageDeliveryProofParams>>, -) -> FromBridgedChainMessagesDeliveryProof>> +pub fn prepare_message_delivery_proof_from_grandpa_chain( + params: MessageDeliveryProofParams>>, +) -> FromBridgedChainMessagesDeliveryProof>> where - R: pallet_bridge_grandpa::Config>>, + R: pallet_bridge_grandpa::Config> + + pallet_bridge_messages::Config< + MI, + BridgedHeaderChain = pallet_bridge_grandpa::Pallet, + >, FI: 'static, - B: MessageBridge, + MI: 'static, { // prepare storage proof let lane = params.lane; - let (state_root, storage_proof) = prepare_message_delivery_storage_proof::( - params.lane, - params.inbound_lane_data, - params.size, - ); + let (state_root, storage_proof) = prepare_message_delivery_proof::(params); // update runtime storage let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(state_root); @@ -195,26 +218,22 @@ where /// This method is intended to be used when benchmarking pallet, linked to the chain that /// uses parachain finality. For GRANDPA chains, please use the /// `prepare_message_delivery_proof_from_grandpa_chain` function. -pub fn prepare_message_delivery_proof_from_parachain( - params: MessageDeliveryProofParams>>, -) -> FromBridgedChainMessagesDeliveryProof>> +pub fn prepare_message_delivery_proof_from_parachain( + params: MessageDeliveryProofParams>>, +) -> FromBridgedChainMessagesDeliveryProof>> where - R: pallet_bridge_parachains::Config, + R: pallet_bridge_parachains::Config + pallet_bridge_messages::Config, PI: 'static, - B: MessageBridge, - UnderlyingChainOf>: Chain + Parachain, + MI: 'static, + BridgedChainOf: Chain + Parachain, { // prepare storage proof let lane = params.lane; - let (state_root, storage_proof) = prepare_message_delivery_storage_proof::( - params.lane, - params.inbound_lane_data, - params.size, - ); + let (state_root, storage_proof) = prepare_message_delivery_proof::(params); // update runtime storage let (_, bridged_header_hash) = - insert_header_to_parachains_pallet::>>(state_root); + insert_header_to_parachains_pallet::>(state_root); FromBridgedChainMessagesDeliveryProof { bridged_header_hash: bridged_header_hash.into(), @@ -223,6 +242,69 @@ where } } +/// Prepare in-memory message delivery proof, without inserting anything to the runtime storage. +fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams>>, +) -> (HashOf>, UntrustedVecDb) +where + R: pallet_bridge_messages::Config, + MI: 'static, +{ + match BridgedChainOf::::STATE_VERSION { + StateVersion::V0 => + do_prepare_message_delivery_proof::>>>( + params, + ), + StateVersion::V1 => + do_prepare_message_delivery_proof::>>>( + params, + ), + } +} + +/// Prepare in-memory message delivery proof, without inserting anything to the runtime storage. +fn do_prepare_message_delivery_proof< + R, + MI, + L: TrieConfiguration>>, +>( + params: MessageDeliveryProofParams>>, +) -> (HashOf>, UntrustedVecDb) +where + R: pallet_bridge_messages::Config, + MI: 'static, +{ + // prepare Bridged chain storage with inbound lane state + let storage_key = storage_keys::inbound_lane_data_key( + R::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + ¶ms.lane, + ) + .0; + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = TrieDBMutBuilder::::new(&mut mdb, &mut root).build(); + let inbound_lane_data = + grow_trie_leaf_value(params.inbound_lane_data.encode(), params.size); + trie.insert(&storage_key, &inbound_lane_data) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + } + + // generate storage proof to be delivered to This chain + let read_proof = record_all_trie_keys::(&mdb, &root) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + let storage_proof = UntrustedVecDb::try_new::>>( + StorageProof::new(read_proof), + root, + vec![storage_key], + ) + .unwrap(); + + (root, storage_proof) +} + /// Insert header to the bridge GRANDPA pallet. pub(crate) fn insert_header_to_grandpa_pallet( state_root: bp_runtime::HashOf, diff --git a/bridges/bin/runtime-common/src/messages_call_ext.rs b/bridges/bin/runtime-common/src/messages_call_ext.rs index aacf3190b78b2..0c860e5451f1f 100644 --- a/bridges/bin/runtime-common/src/messages_call_ext.rs +++ b/bridges/bin/runtime-common/src/messages_call_ext.rs @@ -18,15 +18,10 @@ //! (and some other invalid) transactions. use bp_messages::{ - source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::{FromBridgedChainMessagesProof, MessageDispatch}, - InboundLaneData, LaneId, MessageNonce, + target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneId, MessageNonce, }; use bp_runtime::OwnedBridgeModule; -use frame_support::{ - dispatch::CallableCallFor, - traits::{Get, IsSubType}, -}; +use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; use pallet_bridge_messages::{Config, Pallet}; use sp_runtime::{transaction_validity::TransactionValidity, RuntimeDebug}; use sp_std::ops::RangeInclusive; @@ -214,18 +209,8 @@ pub trait MessagesCallSubType, I: 'static>: } impl< - BridgedHeaderHash, - SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain< - MessagesProof = FromBridgedChainMessagesProof, - >, - TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain< - >::OutboundPayload, - ::AccountId, - MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof, - >, Call: IsSubType, T>>, - T: frame_system::Config - + Config, + T: frame_system::Config + Config, I: 'static, > MessagesCallSubType for T::RuntimeCall { @@ -344,13 +329,14 @@ fn unrewarded_relayers_occupation, I: 'static>( inbound_lane_data: &InboundLaneData, ) -> UnrewardedRelayerOccupation { UnrewardedRelayerOccupation { - free_relayer_slots: T::MaxUnrewardedRelayerEntriesAtInboundLane::get() + free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX .saturating_sub(inbound_lane_data.relayers.len() as MessageNonce), free_message_slots: { let unconfirmed_messages = inbound_lane_data .last_delivered_nonce() .saturating_sub(inbound_lane_data.last_confirmed_nonce); - T::MaxUnconfirmedMessagesAtInboundLane::get().saturating_sub(unconfirmed_messages) + T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + .saturating_sub(unconfirmed_messages) }, } } @@ -360,10 +346,7 @@ mod tests { use super::*; use crate::{ messages_call_ext::MessagesCallSubType, - mock::{ - DummyMessageDispatch, MaxUnconfirmedMessagesAtInboundLane, - MaxUnrewardedRelayerEntriesAtInboundLane, TestRuntime, ThisChainRuntimeCall, - }, + mock::{BridgedUnderlyingChain, DummyMessageDispatch, TestRuntime, ThisChainRuntimeCall}, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, @@ -375,7 +358,7 @@ mod tests { fn fill_unrewarded_relayers() { let mut inbound_lane_state = pallet_bridge_messages::InboundLanes::::get(LaneId([0, 0, 0, 0])); - for n in 0..MaxUnrewardedRelayerEntriesAtInboundLane::get() { + for n in 0..BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX { inbound_lane_state.relayers.push_back(UnrewardedRelayer { relayer: Default::default(), messages: DeliveredMessages { begin: n + 1, end: n + 1 }, @@ -394,7 +377,7 @@ mod tests { relayer: Default::default(), messages: DeliveredMessages { begin: 1, - end: MaxUnconfirmedMessagesAtInboundLane::get(), + end: BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, }, }); pallet_bridge_messages::InboundLanes::::insert( @@ -510,8 +493,8 @@ mod tests { sp_io::TestExternalities::new(Default::default()).execute_with(|| { fill_unrewarded_messages(); assert!(validate_message_delivery( - MaxUnconfirmedMessagesAtInboundLane::get(), - MaxUnconfirmedMessagesAtInboundLane::get() - 1 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX - 1 )); }); } @@ -542,7 +525,7 @@ mod tests { pallet_bridge_messages::Call::::receive_messages_delivery_proof { proof: FromBridgedChainMessagesDeliveryProof { bridged_header_hash: Default::default(), - storage_proof: Vec::new(), + storage_proof: Default::default(), lane: LaneId([0, 0, 0, 0]), }, relayers_state: UnrewardedRelayersState { @@ -610,7 +593,7 @@ mod tests { free_message_slots: if is_empty { 0 } else { - MaxUnconfirmedMessagesAtInboundLane::get() + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX }, }, }, diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index b07efd3e87961..003cc74d44a0f 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -18,26 +18,16 @@ #![cfg(test)] -use crate::messages::{ - source::{ - FromThisChainMaximalOutboundPayloadSize, FromThisChainMessagePayload, - TargetHeaderChainAdapter, - }, - target::SourceHeaderChainAdapter, - BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages, -}; +use crate::messages_xcm_extension::XcmAsPlainPayload; -use bp_header_chain::{ChainWithGrandpa, HeaderChain}; +use bp_header_chain::ChainWithGrandpa; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - LaneId, MessageNonce, + ChainWithMessages, LaneId, MessageNonce, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::PayRewardFromAccount; -use bp_runtime::{ - messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider, -}; -use codec::{Decode, Encode}; +use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Parachain}; use frame_support::{ derive_impl, parameter_types, weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight}, @@ -61,8 +51,6 @@ pub type ThisChainHash = H256; pub type ThisChainHasher = BlakeTwo256; /// Runtime call at `ThisChain`. pub type ThisChainRuntimeCall = RuntimeCall; -/// Runtime call origin at `ThisChain`. -pub type ThisChainCallOrigin = RuntimeOrigin; /// Header of `ThisChain`. pub type ThisChainHeader = sp_runtime::generic::Header; /// Block of `ThisChain`. @@ -100,8 +88,6 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 0]); /// Bridged chain id used in tests. pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg"; -/// Maximal extrinsic weight at the `BridgedChain`. -pub const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: usize = 2048; /// Maximal extrinsic size at the `BridgedChain`. pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; @@ -126,7 +112,6 @@ crate::generate_bridge_reject_obsolete_headers_and_messages! { parameter_types! { pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID]; - pub const BridgedChainId: ChainId = TEST_BRIDGED_CHAIN_ID; pub const BridgedParasPalletName: &'static str = "Paras"; pub const ExistentialDeposit: ThisChainBalance = 500; pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 }; @@ -136,8 +121,6 @@ parameter_types! { pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); - pub const MaxUnrewardedRelayerEntriesAtInboundLane: MessageNonce = 16; - pub const MaxUnconfirmedMessagesAtInboundLane: MessageNonce = 1_000; pub const ReserveId: [u8; 8] = *b"brdgrlrs"; } @@ -204,17 +187,13 @@ impl pallet_bridge_messages::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type ActiveOutboundLanes = ActiveOutboundLanes; - type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; - type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; - type MaximalOutboundPayloadSize = FromThisChainMaximalOutboundPayloadSize; - type OutboundPayload = FromThisChainMessagePayload; + type OutboundPayload = XcmAsPlainPayload; type InboundPayload = Vec; type InboundRelayer = BridgedChainAccountId; type DeliveryPayments = (); - type TargetHeaderChain = TargetHeaderChainAdapter; type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), @@ -222,9 +201,11 @@ impl pallet_bridge_messages::Config for TestRuntime { >; type OnMessagesDelivered = (); - type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = DummyMessageDispatch; - type BridgedChainId = BridgedChainId; + + type ThisChain = ThisUnderlyingChain; + type BridgedChain = BridgedUnderlyingChain; + type BridgedHeaderChain = BridgeGrandpa; } impl pallet_bridge_relayers::Config for TestRuntime { @@ -263,55 +244,6 @@ impl MessageDispatch for DummyMessageDispatch { } } -/// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from -/// `BridgedChain`. -#[derive(Debug, PartialEq, Eq)] -pub struct OnThisChainBridge; - -impl MessageBridge for OnThisChainBridge { - const BRIDGED_MESSAGES_PALLET_NAME: &'static str = ""; - - type ThisChain = ThisChain; - type BridgedChain = BridgedChain; - type BridgedHeaderChain = pallet_bridge_grandpa::GrandpaChainHeaders; -} - -/// Bridge that is deployed on `BridgedChain` and allows sending/receiving messages to/from -/// `ThisChain`. -#[derive(Debug, PartialEq, Eq)] -pub struct OnBridgedChainBridge; - -impl MessageBridge for OnBridgedChainBridge { - const BRIDGED_MESSAGES_PALLET_NAME: &'static str = ""; - - type ThisChain = BridgedChain; - type BridgedChain = ThisChain; - type BridgedHeaderChain = ThisHeaderChain; -} - -/// Dummy implementation of `HeaderChain` for `ThisChain` at the `BridgedChain`. -pub struct ThisHeaderChain; - -impl HeaderChain for ThisHeaderChain { - fn finalized_header_state_root(_hash: HashOf) -> Option> { - unreachable!() - } -} - -/// Call origin at `BridgedChain`. -#[derive(Clone, Debug)] -pub struct BridgedChainOrigin; - -impl From - for Result, BridgedChainOrigin> -{ - fn from( - _origin: BridgedChainOrigin, - ) -> Result, BridgedChainOrigin> { - unreachable!() - } -} - /// Underlying chain of `ThisChain`. pub struct ThisUnderlyingChain; @@ -338,29 +270,20 @@ impl Chain for ThisUnderlyingChain { } } -/// The chain where we are in tests. -pub struct ThisChain; +impl ChainWithMessages for ThisUnderlyingChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = ""; -impl UnderlyingChainProvider for ThisChain { - type Chain = ThisUnderlyingChain; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000; } -impl ThisChainWithMessages for ThisChain { - type RuntimeOrigin = ThisChainCallOrigin; -} - -impl BridgedChainWithMessages for ThisChain {} - /// Underlying chain of `BridgedChain`. pub struct BridgedUnderlyingChain; /// Some parachain under `BridgedChain` consensus. pub struct BridgedUnderlyingParachain; -/// Runtime call of the `BridgedChain`. -#[derive(Decode, Encode)] -pub struct BridgedChainCall; impl Chain for BridgedUnderlyingChain { - const ID: ChainId = *b"buch"; + const ID: ChainId = TEST_BRIDGED_CHAIN_ID; type BlockNumber = BridgedChainBlockNumber; type Hash = BridgedChainHash; @@ -389,6 +312,12 @@ impl ChainWithGrandpa for BridgedUnderlyingChain { const AVERAGE_HEADER_SIZE: u32 = 64; } +impl ChainWithMessages for BridgedUnderlyingChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = ""; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000; +} + impl Chain for BridgedUnderlyingParachain { const ID: ChainId = *b"bupc"; @@ -416,19 +345,6 @@ impl Parachain for BridgedUnderlyingParachain { const MAX_HEADER_SIZE: u32 = 1_024; } -/// The other, bridged chain, used in tests. -pub struct BridgedChain; - -impl UnderlyingChainProvider for BridgedChain { - type Chain = BridgedUnderlyingChain; -} - -impl ThisChainWithMessages for BridgedChain { - type RuntimeOrigin = BridgedChainOrigin; -} - -impl BridgedChainWithMessages for BridgedChain {} - /// Run test within test externalities. pub fn run_test(test: impl FnOnce()) { sp_io::TestExternalities::new(Default::default()).execute_with(test) diff --git a/bridges/bin/runtime-common/src/parachains_benchmarking.rs b/bridges/bin/runtime-common/src/parachains_benchmarking.rs index b3050b9ac0f3c..7f43f78b719fc 100644 --- a/bridges/bin/runtime-common/src/parachains_benchmarking.rs +++ b/bridges/bin/runtime-common/src/parachains_benchmarking.rs @@ -18,14 +18,11 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{ - messages_benchmarking::insert_header_to_grandpa_pallet, - messages_generation::grow_trie_leaf_value, -}; +use crate::messages_benchmarking::insert_header_to_grandpa_pallet; use bp_parachains::parachain_head_storage_key_at_source; use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; -use bp_runtime::{record_all_trie_keys, StorageProofSize}; +use bp_runtime::{grow_trie_leaf_value, record_all_trie_keys, StorageProofSize}; use codec::Encode; use frame_support::traits::Get; use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index df5b92db7402b..69fdc330f294d 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.11.1", default-features = false, features = ["derive # Bridge dependencies +bp-header-chain = { path = "../../primitives/header-chain", default-features = false } bp-messages = { path = "../../primitives/messages", default-features = false } bp-runtime = { path = "../../primitives/runtime", default-features = false } @@ -28,15 +29,20 @@ frame-support = { path = "../../../substrate/frame/support", default-features = frame-system = { path = "../../../substrate/frame/system", default-features = false } sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false, optional = true } [dev-dependencies] bp-test-utils = { path = "../../primitives/test-utils" } +pallet-bridge-grandpa = { path = "../grandpa" } pallet-balances = { path = "../../../substrate/frame/balances" } sp-io = { path = "../../../substrate/primitives/io" } +sp-core = { path = "../../../substrate/primitives/core" } +sp-trie = { path = "../../../substrate/primitives/trie" } [features] default = ["std"] std = [ + "bp-header-chain/std", "bp-messages/std", "bp-runtime/std", "codec/std", @@ -48,6 +54,7 @@ std = [ "scale-info/std", "sp-runtime/std", "sp-std/std", + "sp-trie/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -62,3 +69,6 @@ try-runtime = [ "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] +test-helpers = [ + "sp-trie", +] diff --git a/bridges/modules/messages/README.md b/bridges/modules/messages/README.md index c06b96b857dea..80fd92eb0e5a7 100644 --- a/bridges/modules/messages/README.md +++ b/bridges/modules/messages/README.md @@ -104,17 +104,22 @@ the message. When a message is delivered to the target chain, the `MessagesDeliv `receive_messages_delivery_proof()` transaction. The `MessagesDelivered` contains the message lane identifier and inclusive range of delivered message nonces. -The pallet provides no means to get the result of message dispatch at the target chain. If that is required, it must be -done outside of the pallet. For example, XCM messages, when dispatched, have special instructions to send some data back -to the sender. Other dispatchers may use similar mechanism for that. -### How to plug-in Messages Module to Send Messages to the Bridged Chain? - -The `pallet_bridge_messages::Config` trait has 3 main associated types that are used to work with outbound messages. The -`pallet_bridge_messages::Config::TargetHeaderChain` defines how we see the bridged chain as the target for our outbound -messages. It must be able to check that the bridged chain may accept our message - like that the message has size below -maximal possible transaction size of the chain and so on. And when the relayer sends us a confirmation transaction, this -implementation must be able to parse and verify the proof of messages delivery. Normally, you would reuse the same -(configurable) type on all chains that are sending messages to the same bridged chain. +The pallet provides no means to get the result of message dispatch at the target chain. If that is +required, it must be done outside of the pallet. For example, XCM messages, when dispatched, have +special instructions to send some data back to the sender. Other dispatchers may use similar +mechanism for that. + +### How to plug-in Messages Module to Send and Receive Messages from the Bridged Chain? + +The `pallet_bridge_messages::Config` trait has 2 main associated types that are used to work with +inbound messages. The `pallet_bridge_messages::BridgedChain` defines basic primitives of the bridged +chain. The `pallet_bridge_messages::BridgedHeaderChain` defines the way we access the bridged chain +headers in our runtime. You may use `pallet_bridge_grandpa` if you're bridging with chain that uses +GRANDPA finality or `pallet_bridge_parachains::ParachainHeaders` if you're bridging with parachain. + +The `pallet_bridge_messages::Config::MessageDispatch` defines a way on how to dispatch delivered +messages. Apart from actually dispatching the message, the implementation must return the correct +dispatch weight of the message before dispatch is called. The last type is the `pallet_bridge_messages::Config::DeliveryConfirmationPayments`. When confirmation transaction is received, we call the `pay_reward()` method, passing the range of delivered messages. @@ -129,18 +134,6 @@ You should be looking at the `bp_messages::source_chain::ForbidOutboundMessages` [`bp_messages::source_chain`](../../primitives/messages/src/source_chain.rs). It implements all required traits and will simply reject all transactions, related to outbound messages. -### How to plug-in Messages Module to Receive Messages from the Bridged Chain? - -The `pallet_bridge_messages::Config` trait has 2 main associated types that are used to work with inbound messages. The -`pallet_bridge_messages::Config::SourceHeaderChain` defines how we see the bridged chain as the source of our inbound -messages. When relayer sends us a delivery transaction, this implementation must be able to parse and verify the proof -of messages wrapped in this transaction. Normally, you would reuse the same (configurable) type on all chains that are -sending messages to the same bridged chain. - -The `pallet_bridge_messages::Config::MessageDispatch` defines a way on how to dispatch delivered messages. Apart from -actually dispatching the message, the implementation must return the correct dispatch weight of the message before -dispatch is called. - ### I have a Messages Module in my Runtime, but I Want to Reject all Inbound Messages. What shall I do? You should be looking at the `bp_messages::target_chain::ForbidInboundMessages` structure from the @@ -150,36 +143,42 @@ and will simply reject all transactions, related to inbound messages. ### What about other Constants in the Messages Module Configuration Trait? Two settings that are used to check messages in the `send_message()` function. The -`pallet_bridge_messages::Config::ActiveOutboundLanes` is an array of all message lanes, that may be used to send -messages. All messages sent using other lanes are rejected. All messages that have size above -`pallet_bridge_messages::Config::MaximalOutboundPayloadSize` will also be rejected. - -To be able to reward the relayer for delivering messages, we store a map of message nonces range => identifier of the -relayer that has delivered this range at the target chain runtime storage. If a relayer delivers multiple consequent -ranges, they're merged into single entry. So there may be more than one entry for the same relayer. Eventually, this -whole map must be delivered back to the source chain to confirm delivery and pay rewards. So to make sure we are able to -craft this confirmation transaction, we need to: (1) keep the size of this map below a certain limit and (2) make sure -that the weight of processing this map is below a certain limit. Both size and processing weight mostly depend on the -number of entries. The number of entries is limited with the -`pallet_bridge_messages::ConfigMaxUnrewardedRelayerEntriesAtInboundLane` parameter. Processing weight also depends on -the total number of messages that are being confirmed, because every confirmed message needs to be read. So there's -another `pallet_bridge_messages::Config::MaxUnconfirmedMessagesAtInboundLane` parameter for that. - -When choosing values for these parameters, you must also keep in mind that if proof in your scheme is based on finality -of headers (and it is the most obvious option for Substrate-based chains with finality notion), then choosing too small -values for these parameters may cause significant delays in message delivery. That's because there are too many actors -involved in this scheme: 1) authorities that are finalizing headers of the target chain need to finalize header with -non-empty map; 2) the headers relayer then needs to submit this header and its finality proof to the source chain; 3) -the messages relayer must then send confirmation transaction (storage proof of this map) to the source chain; 4) when -the confirmation transaction will be mined at some header, source chain authorities must finalize this header; 5) the -headers relay then needs to submit this header and its finality proof to the target chain; 6) only now the messages -relayer may submit new messages from the source to target chain and prune the entry from the map. - -Delivery transaction requires the relayer to provide both number of entries and total number of messages in the map. -This means that the module never charges an extra cost for delivering a map - the relayer would need to pay exactly for -the number of entries+messages it has delivered. So the best guess for values of these parameters would be the pair that -would occupy `N` percent of the maximal transaction size and weight of the source chain. The `N` should be large enough -to process large maps, at the same time keeping reserve for future source chain upgrades. +`pallet_bridge_messages::Config::ActiveOutboundLanes` is an array of all message lanes, that +may be used to send messages. All messages sent using other lanes are rejected. All messages that have +size above `pallet_bridge_messages::Config::MaximalOutboundPayloadSize` will also be rejected. + +To be able to reward the relayer for delivering messages, we store a map of message nonces range => +identifier of the relayer that has delivered this range at the target chain runtime storage. If a +relayer delivers multiple consequent ranges, they're merged into single entry. So there may be more +than one entry for the same relayer. Eventually, this whole map must be delivered back to the source +chain to confirm delivery and pay rewards. So to make sure we are able to craft this confirmation +transaction, we need to: (1) keep the size of this map below a certain limit and (2) make sure that +the weight of processing this map is below a certain limit. Both size and processing weight mostly +depend on the number of entries. The number of entries is limited with the +`pallet_bridge_messages::Config::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` parameter. +Processing weight also depends on the total number of messages that are being confirmed, because every +confirmed message needs to be read. So there's another +`pallet_bridge_messages::Config::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` parameter +for that. + +When choosing values for these parameters, you must also keep in mind that if proof in your scheme +is based on finality of headers (and it is the most obvious option for Substrate-based chains with +finality notion), then choosing too small values for these parameters may cause significant delays +in message delivery. That's because there are too many actors involved in this scheme: 1) authorities +that are finalizing headers of the target chain need to finalize header with non-empty map; 2) the +headers relayer then needs to submit this header and its finality proof to the source chain; 3) the +messages relayer must then send confirmation transaction (storage proof of this map) to the source +chain; 4) when the confirmation transaction will be mined at some header, source chain authorities +must finalize this header; 5) the headers relay then needs to submit this header and its finality +proof to the target chain; 6) only now the messages relayer may submit new messages from the source +to target chain and prune the entry from the map. + +Delivery transaction requires the relayer to provide both number of entries and total number of +messages in the map. This means that the module never charges an extra cost for delivering a map - +the relayer would need to pay exactly for the number of entries+messages it has delivered. So the +best guess for values of these parameters would be the pair that would occupy `N` percent of the +maximal transaction size and weight of the source chain. The `N` should be large enough to process +large maps, at the same time keeping reserve for future source chain upgrades. ## Non-Essential Functionality diff --git a/bridges/modules/messages/src/benchmarking.rs b/bridges/modules/messages/src/benchmarking.rs index ee969d727ddc8..41d6b463a1239 100644 --- a/bridges/modules/messages/src/benchmarking.rs +++ b/bridges/modules/messages/src/benchmarking.rs @@ -20,23 +20,21 @@ use crate::{ inbound_lane::InboundLaneStorage, outbound_lane, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, - Call, OutboundLanes, RuntimeInboundLaneStorage, + BridgedChainOf, Call, OutboundLanes, RuntimeInboundLaneStorage, }; use bp_messages::{ - source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, DeliveredMessages, + source_chain::FromBridgedChainMessagesDeliveryProof, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, }; -use bp_runtime::StorageProofSize; +use bp_runtime::{HashOf, StorageProofSize}; use codec::Decode; use frame_benchmarking::{account, v2::*}; use frame_support::weights::Weight; use frame_system::RawOrigin; -use sp_runtime::{ - traits::{Get, TrailingZeroInput}, - BoundedVec, -}; +use sp_runtime::{traits::TrailingZeroInput, BoundedVec}; use sp_std::{ops::RangeInclusive, prelude::*}; const SEED: u32 = 0; @@ -99,11 +97,11 @@ pub trait Config: crate::Config { /// Prepare messages proof to receive by the module. fn prepare_message_proof( params: MessageProofParams, - ) -> (::MessagesProof, Weight); + ) -> (FromBridgedChainMessagesProof>>, Weight); /// Prepare messages delivery proof to receive by the module. fn prepare_message_delivery_proof( params: MessageDeliveryProofParams, - ) -> >::MessagesDeliveryProof; + ) -> FromBridgedChainMessagesDeliveryProof>>; /// Returns true if message has been successfully dispatched or not. fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool { @@ -190,7 +188,7 @@ mod benchmarks { // fn max_msgs, I: 'static>() -> u32 { - T::MaxUnconfirmedMessagesAtInboundLane::get() as u32 - + T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX as u32 - ReceiveMessagesProofSetup::::LATEST_RECEIVED_NONCE as u32 } @@ -520,5 +518,9 @@ mod benchmarks { assert!(T::is_message_successfully_dispatched(setup.last_nonce())); } - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime); + impl_benchmark_test_suite!( + Pallet, + crate::tests::mock::new_test_ext(), + crate::tests::mock::TestRuntime + ); } diff --git a/bridges/modules/messages/src/inbound_lane.rs b/bridges/modules/messages/src/inbound_lane.rs index da1698e6e0370..1c5585463c8b5 100644 --- a/bridges/modules/messages/src/inbound_lane.rs +++ b/bridges/modules/messages/src/inbound_lane.rs @@ -16,15 +16,14 @@ //! Everything about incoming messages receival. -use crate::Config; +use crate::{BridgedChainOf, Config}; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - DeliveredMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, OutboundLaneData, - ReceptionResult, UnrewardedRelayer, + ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, + OutboundLaneData, ReceptionResult, UnrewardedRelayer, }; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use frame_support::traits::Get; use scale_info::{Type, TypeInfo}; use sp_runtime::RuntimeDebug; use sp_std::prelude::PartialEq; @@ -101,7 +100,7 @@ impl, I: 'static> TypeInfo for StoredInboundLaneData { impl, I: 'static> MaxEncodedLen for StoredInboundLaneData { fn max_encoded_len() -> usize { InboundLaneData::::encoded_size_hint( - T::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize, + BridgedChainOf::::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize, ) .unwrap_or(usize::MAX) } @@ -216,10 +215,10 @@ mod tests { use super::*; use crate::{ inbound_lane, - mock::{ + tests::mock::{ dispatch_result, inbound_message_data, inbound_unrewarded_relayers_state, run_test, - unrewarded_relayer, TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID, - TEST_RELAYER_A, TEST_RELAYER_B, TEST_RELAYER_C, + unrewarded_relayer, BridgedChain, TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, + TEST_LANE_ID, TEST_RELAYER_A, TEST_RELAYER_B, TEST_RELAYER_C, }, RuntimeInboundLaneStorage, }; @@ -372,8 +371,7 @@ mod tests { fn fails_to_receive_messages_above_unrewarded_relayer_entries_limit_per_lane() { run_test(|| { let mut lane = inbound_lane::(TEST_LANE_ID); - let max_nonce = - ::MaxUnrewardedRelayerEntriesAtInboundLane::get(); + let max_nonce = BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; for current_nonce in 1..max_nonce + 1 { assert_eq!( lane.receive_message::( @@ -409,7 +407,7 @@ mod tests { fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() { run_test(|| { let mut lane = inbound_lane::(TEST_LANE_ID); - let max_nonce = ::MaxUnconfirmedMessagesAtInboundLane::get(); + let max_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; for current_nonce in 1..=max_nonce { assert_eq!( lane.receive_message::( diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index e31a4542056cb..cff7eb9dd6d02 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -50,20 +50,22 @@ use crate::{ outbound_lane::{OutboundLane, OutboundLaneStorage, ReceptionConfirmationError}, }; +use bp_header_chain::HeaderChain; use bp_messages::{ source_chain::{ - DeliveryConfirmationPayments, OnMessagesDelivered, SendMessageArtifacts, TargetHeaderChain, + DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered, + SendMessageArtifacts, }, target_chain::{ - DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, - SourceHeaderChain, + DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch, + ProvedLaneMessages, ProvedMessages, }, - DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, MessageKey, MessageNonce, - MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, - UnrewardedRelayersState, VerificationError, + ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, + MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, + OutboundMessageDetails, UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ - BasicOperatingMode, ChainId, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, + BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound}; @@ -72,6 +74,8 @@ use sp_std::{marker::PhantomData, prelude::*}; mod inbound_lane; mod outbound_lane; +mod proofs; +mod tests; mod weights_ext; pub mod weights; @@ -79,10 +83,9 @@ pub mod weights; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -#[cfg(test)] -mod mock; - pub use pallet::*; +#[cfg(feature = "test-helpers")] +pub use tests::*; /// The target that will be used when publishing logs related to this pallet. pub const LOG_TARGET: &str = "runtime::bridge-messages"; @@ -105,39 +108,16 @@ pub mod pallet { /// Benchmarks results from runtime we're plugged into. type WeightInfo: WeightInfoExt; - /// Gets the chain id value from the instance. - #[pallet::constant] - type BridgedChainId: Get; + /// This chain type. + type ThisChain: ChainWithMessages; + /// Bridged chain type. + type BridgedChain: ChainWithMessages; + /// Bridged chain headers provider. + type BridgedHeaderChain: HeaderChain; /// Get all active outbound lanes that the message pallet is serving. type ActiveOutboundLanes: Get<&'static [LaneId]>; - /// Maximal number of unrewarded relayer entries at inbound lane. Unrewarded means that the - /// relayer has delivered messages, but either confirmations haven't been delivered back to - /// the source chain, or we haven't received reward confirmations yet. - /// - /// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep - /// in mind that the same relayer account may take several (non-consecutive) entries in this - /// set. - type MaxUnrewardedRelayerEntriesAtInboundLane: Get; - /// Maximal number of unconfirmed messages at inbound lane. Unconfirmed means that the - /// message has been delivered, but either confirmations haven't been delivered back to the - /// source chain, or we haven't received reward confirmations for these messages yet. - /// - /// This constant limits difference between last message from last entry of the - /// `InboundLaneData::relayers` and first message at the first entry. - /// - /// There is no point of making this parameter lesser than - /// MaxUnrewardedRelayerEntriesAtInboundLane, because then maximal number of relayer entries - /// will be limited by maximal number of messages. - /// - /// This value also represents maximal number of messages in single delivery transaction. - /// Transaction that is declaring more messages than this value, will be rejected. Even if - /// these messages are from different lanes. - type MaxUnconfirmedMessagesAtInboundLane: Get; - - /// Maximal encoded size of the outbound payload. - #[pallet::constant] - type MaximalOutboundPayloadSize: Get; + /// Payload type of outbound messages. This payload is dispatched on the bridged chain. type OutboundPayload: Parameter + Size; @@ -146,13 +126,9 @@ pub mod pallet { /// Identifier of relayer that deliver messages to this chain. Relayer reward is paid on the /// bridged chain. type InboundRelayer: Parameter + MaxEncodedLen; - /// Delivery payments. - type DeliveryPayments: DeliveryPayments; // Types that are used by outbound_lane (on source chain). - /// Target header chain. - type TargetHeaderChain: TargetHeaderChain; /// Delivery confirmation payments. type DeliveryConfirmationPayments: DeliveryConfirmationPayments; /// Delivery confirmation callback. @@ -160,21 +136,18 @@ pub mod pallet { // Types that are used by inbound_lane (on target chain). - /// Source header chain, as it is represented on target chain. - type SourceHeaderChain: SourceHeaderChain; /// Message dispatch. type MessageDispatch: MessageDispatch; + /// Delivery payments. + type DeliveryPayments: DeliveryPayments; } - /// Shortcut to messages proof type for Config. - pub type MessagesProofOf = - <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof; - /// Shortcut to messages delivery proof type for Config. - pub type MessagesDeliveryProofOf = - <>::TargetHeaderChain as TargetHeaderChain< - >::OutboundPayload, - ::AccountId, - >>::MessagesDeliveryProof; + /// Shortcut to this chain type for Config. + pub type ThisChainOf = >::ThisChain; + /// Shortcut to bridged chain type for Config. + pub type BridgedChainOf = >::BridgedChain; + /// Shortcut to bridged header chain type for Config. + pub type BridgedHeaderChainOf = >::BridgedHeaderChain; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); @@ -269,7 +242,7 @@ pub mod pallet { pub fn receive_messages_proof( origin: OriginFor, relayer_id_at_bridged_chain: T::InboundRelayer, - proof: MessagesProofOf, + proof: FromBridgedChainMessagesProof>>, messages_count: u32, dispatch_weight: Weight, ) -> DispatchResultWithPostInfo { @@ -278,7 +251,8 @@ pub mod pallet { // reject transactions that are declaring too many messages ensure!( - MessageNonce::from(messages_count) <= T::MaxUnconfirmedMessagesAtInboundLane::get(), + MessageNonce::from(messages_count) <= + BridgedChainOf::::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, Error::::TooManyMessagesInTheProof ); @@ -303,15 +277,12 @@ pub mod pallet { let mut actual_weight = declared_weight; // verify messages proof && convert proof into messages - let messages = verify_and_decode_messages_proof::< - T::SourceHeaderChain, - T::InboundPayload, - >(proof, messages_count) - .map_err(|err| { - log::trace!(target: LOG_TARGET, "Rejecting invalid messages proof: {:?}", err,); + let messages = verify_and_decode_messages_proof::(proof, messages_count) + .map_err(|err| { + log::trace!(target: LOG_TARGET, "Rejecting invalid messages proof: {:?}", err,); - Error::::InvalidMessagesProof - })?; + Error::::InvalidMessagesProof + })?; // dispatch messages and (optionally) update lane(s) state(s) let mut total_messages = 0; @@ -424,14 +395,14 @@ pub mod pallet { ))] pub fn receive_messages_delivery_proof( origin: OriginFor, - proof: MessagesDeliveryProofOf, + proof: FromBridgedChainMessagesDeliveryProof>>, mut relayers_state: UnrewardedRelayersState, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; let proof_size = proof.size(); let confirmation_relayer = ensure_signed(origin)?; - let (lane_id, lane_data) = T::TargetHeaderChain::verify_messages_delivery_proof(proof) + let (lane_id, lane_data) = proofs::verify_messages_delivery_proof::(proof) .map_err(|err| { log::trace!( target: LOG_TARGET, @@ -714,18 +685,6 @@ where // let's check if outbound lane is active ensure!(T::ActiveOutboundLanes::get().contains(&lane), Error::::InactiveOutboundLane); - // let's first check if message can be delivered to target chain - T::TargetHeaderChain::verify_message(message).map_err(|err| { - log::trace!( - target: LOG_TARGET, - "Message to lane {:?} is rejected by target chain: {:?}", - lane, - err, - ); - - Error::::MessageRejectedByChainVerifier(err) - })?; - Ok(SendMessageArgs { lane_id: lane, payload: StoredMessagePayload::::try_from(message.encode()).map_err(|_| { @@ -802,7 +761,7 @@ impl, I: 'static> RuntimeInboundLaneStorage { /// maximal configured. /// /// Maximal inbound lane state set size is configured by the - /// `MaxUnrewardedRelayerEntriesAtInboundLane` constant from the pallet configuration. The PoV + /// `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` constant from the pallet configuration. The PoV /// of the call includes the maximal size of inbound lane state. If the actual size is smaller, /// we may subtract extra bytes from this component. pub fn extra_proof_size_bytes(&mut self) -> u64 { @@ -823,11 +782,11 @@ impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage< } fn max_unrewarded_relayer_entries(&self) -> MessageNonce { - T::MaxUnrewardedRelayerEntriesAtInboundLane::get() + BridgedChainOf::::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX } fn max_unconfirmed_messages(&self) -> MessageNonce { - T::MaxUnconfirmedMessagesAtInboundLane::get() + BridgedChainOf::::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX } fn get_or_init_data(&mut self) -> InboundLaneData { @@ -887,14 +846,14 @@ impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorag } /// Verify messages proof and return proved messages with decoded payload. -fn verify_and_decode_messages_proof( - proof: Chain::MessagesProof, +fn verify_and_decode_messages_proof, I: 'static>( + proof: FromBridgedChainMessagesProof>>, messages_count: u32, -) -> Result>, VerificationError> { - // `receive_messages_proof` weight formula and `MaxUnconfirmedMessagesAtInboundLane` check - // guarantees that the `message_count` is sane and Vec may be allocated. +) -> Result>, VerificationError> { + // `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` + // check guarantees that the `message_count` is sane and Vec may be allocated. // (tx with too many messages will either be rejected from the pool, or will fail earlier) - Chain::verify_messages_proof(proof, messages_count).map(|messages_by_lane| { + proofs::verify_messages_proof::(proof, messages_count).map(|messages_by_lane| { messages_by_lane .into_iter() .map(|(lane, lane_data)| { @@ -910,1208 +869,1208 @@ fn verify_and_decode_messages_proof::set_block_number(1); - System::::reset_events(); - } - - fn send_regular_message(lane_id: LaneId) { - get_ready_for_events(); - - let outbound_lane = outbound_lane::(lane_id); - let message_nonce = outbound_lane.data().latest_generated_nonce + 1; - let prev_enqueued_messages = outbound_lane.data().queued_messages().saturating_len(); - let valid_message = Pallet::::validate_message(lane_id, ®ULAR_PAYLOAD) - .expect("validate_message has failed"); - let artifacts = Pallet::::send_message(valid_message); - assert_eq!(artifacts.enqueued_messages, prev_enqueued_messages + 1); - - // check event with assigned nonce - assert_eq!( - System::::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: TestEvent::Messages(Event::MessageAccepted { - lane_id, - nonce: message_nonce - }), - topics: vec![], - }], - ); - } - - fn receive_messages_delivery_proof() { - System::::set_block_number(1); - System::::reset_events(); - - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![UnrewardedRelayer { - relayer: 0, - messages: DeliveredMessages::new(1), - }] - .into_iter() - .collect(), - }, - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - )); - - assert_eq!( - System::::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: TestEvent::Messages(Event::MessagesDelivered { - lane_id: TEST_LANE_ID, - messages: DeliveredMessages::new(1), - }), - topics: vec![], - }], - ); - } - - #[test] - fn pallet_rejects_transactions_if_halted() { - run_test(|| { - // send message first to be able to check that delivery_proof fails later - send_regular_message(TEST_LANE_ID); - - PalletOperatingMode::::put(MessagesOperatingMode::Basic( - BasicOperatingMode::Halted, - )); - - assert_noop!( - Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), - Error::::NotOperatingNormally, - ); - - assert_noop!( - Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![message(2, REGULAR_PAYLOAD)]).into(), - 1, - REGULAR_PAYLOAD.declared_weight, - ), - Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), - ); - - assert_noop!( - Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] - .into_iter() - .collect(), - }, - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - ), - Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), - ); - }); - } - - #[test] - fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { - run_test(|| { - // send message first to be able to check that delivery_proof fails later - send_regular_message(TEST_LANE_ID); - - PalletOperatingMode::::put( - MessagesOperatingMode::RejectingOutboundMessages, - ); - - assert_noop!( - Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), - Error::::NotOperatingNormally, - ); - - assert_ok!(Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), - 1, - REGULAR_PAYLOAD.declared_weight, - ),); - - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] - .into_iter() - .collect(), - }, - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - )); - }); - } - - #[test] - fn send_message_works() { - run_test(|| { - send_regular_message(TEST_LANE_ID); - }); - } - - #[test] - fn send_message_rejects_too_large_message() { - run_test(|| { - let mut message_payload = message_payload(1, 0); - // the payload isn't simply extra, so it'll definitely overflow - // `MAX_OUTBOUND_PAYLOAD_SIZE` if we add `MAX_OUTBOUND_PAYLOAD_SIZE` bytes to extra - message_payload - .extra - .extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]); - assert_noop!( - Pallet::::validate_message(TEST_LANE_ID, &message_payload.clone(),), - Error::::MessageRejectedByPallet( - VerificationError::MessageTooLarge - ), - ); - - // let's check that we're able to send `MAX_OUTBOUND_PAYLOAD_SIZE` messages - while message_payload.encoded_size() as u32 > MAX_OUTBOUND_PAYLOAD_SIZE { - message_payload.extra.pop(); - } - assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE); - - let valid_message = - Pallet::::validate_message(TEST_LANE_ID, &message_payload) - .expect("validate_message has failed"); - Pallet::::send_message(valid_message); - }) - } - - #[test] - fn chain_verifier_rejects_invalid_message_in_send_message() { - run_test(|| { - // messages with this payload are rejected by target chain verifier - assert_noop!( - Pallet::::validate_message( - TEST_LANE_ID, - &PAYLOAD_REJECTED_BY_TARGET_CHAIN, - ), - Error::::MessageRejectedByChainVerifier(VerificationError::Other( - mock::TEST_ERROR - )), - ); - }); - } - - #[test] - fn receive_messages_proof_works() { - run_test(|| { - assert_ok!(Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), - 1, - REGULAR_PAYLOAD.declared_weight, - )); - - assert_eq!(InboundLanes::::get(TEST_LANE_ID).0.last_delivered_nonce(), 1); - - assert!(TestDeliveryPayments::is_reward_paid(1)); - }); - } - - #[test] - fn receive_messages_proof_updates_confirmed_message_nonce() { - run_test(|| { - // say we have received 10 messages && last confirmed message is 8 - InboundLanes::::insert( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 8, - relayers: vec![ - unrewarded_relayer(9, 9, TEST_RELAYER_A), - unrewarded_relayer(10, 10, TEST_RELAYER_B), - ] - .into_iter() - .collect(), - }, - ); - assert_eq!( - inbound_unrewarded_relayers_state(TEST_LANE_ID), - UnrewardedRelayersState { - unrewarded_relayer_entries: 2, - messages_in_oldest_entry: 1, - total_messages: 2, - last_delivered_nonce: 10, - }, - ); - - // message proof includes outbound lane state with latest confirmed message updated to 9 - let mut message_proof: TestMessagesProof = - Ok(vec![message(11, REGULAR_PAYLOAD)]).into(); - message_proof.result.as_mut().unwrap()[0].1.lane_state = - Some(OutboundLaneData { latest_received_nonce: 9, ..Default::default() }); - - assert_ok!(Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - message_proof, - 1, - REGULAR_PAYLOAD.declared_weight, - )); - - assert_eq!( - InboundLanes::::get(TEST_LANE_ID).0, - InboundLaneData { - last_confirmed_nonce: 9, - relayers: vec![ - unrewarded_relayer(10, 10, TEST_RELAYER_B), - unrewarded_relayer(11, 11, TEST_RELAYER_A) - ] - .into_iter() - .collect(), - }, - ); - assert_eq!( - inbound_unrewarded_relayers_state(TEST_LANE_ID), - UnrewardedRelayersState { - unrewarded_relayer_entries: 2, - messages_in_oldest_entry: 1, - total_messages: 2, - last_delivered_nonce: 11, - }, - ); - }); - } - - #[test] - fn receive_messages_fails_if_dispatcher_is_inactive() { - run_test(|| { - TestMessageDispatch::deactivate(); - assert_noop!( - Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), - 1, - REGULAR_PAYLOAD.declared_weight, - ), - Error::::MessageDispatchInactive, - ); - }); - } - - #[test] - fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() { - run_test(|| { - let mut declared_weight = REGULAR_PAYLOAD.declared_weight; - *declared_weight.ref_time_mut() -= 1; - assert_noop!( - Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), - 1, - declared_weight, - ), - Error::::InsufficientDispatchWeight - ); - assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); - }); - } - - #[test] - fn receive_messages_proof_rejects_invalid_proof() { - run_test(|| { - assert_noop!( - Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Err(()).into(), - 1, - Weight::zero(), - ), - Error::::InvalidMessagesProof, - ); - }); - } - - #[test] - fn receive_messages_proof_rejects_proof_with_too_many_messages() { - run_test(|| { - assert_noop!( - Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), - u32::MAX, - Weight::zero(), - ), - Error::::TooManyMessagesInTheProof, - ); - }); - } - - #[test] - fn receive_messages_delivery_proof_works() { - run_test(|| { - send_regular_message(TEST_LANE_ID); - receive_messages_delivery_proof(); - - assert_eq!( - OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, - 1, - ); - }); - } - - #[test] - fn receive_messages_delivery_proof_rewards_relayers() { - run_test(|| { - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - - // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A - let single_message_delivery_proof = TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into_iter().collect(), - ..Default::default() - }, - ))); - let single_message_delivery_proof_size = single_message_delivery_proof.size(); - let result = Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - single_message_delivery_proof, - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - ); - assert_ok!(result); - assert_eq!( - result.unwrap().actual_weight.unwrap(), - TestWeightInfo::receive_messages_delivery_proof_weight( - &PreComputedSize(single_message_delivery_proof_size as _), - &UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - total_messages: 1, - ..Default::default() - }, - ) - ); - assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); - assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); - assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 1))); - - // this reports delivery of both message 1 and message 2 => reward is paid only to - // TEST_RELAYER_B - let two_messages_delivery_proof = TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - relayers: vec![ - unrewarded_relayer(1, 1, TEST_RELAYER_A), - unrewarded_relayer(2, 2, TEST_RELAYER_B), - ] - .into_iter() - .collect(), - ..Default::default() - }, - ))); - let two_messages_delivery_proof_size = two_messages_delivery_proof.size(); - let result = Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - two_messages_delivery_proof, - UnrewardedRelayersState { - unrewarded_relayer_entries: 2, - messages_in_oldest_entry: 1, - total_messages: 2, - last_delivered_nonce: 2, - }, - ); - assert_ok!(result); - // even though the pre-dispatch weight was for two messages, the actual weight is - // for single message only - assert_eq!( - result.unwrap().actual_weight.unwrap(), - TestWeightInfo::receive_messages_delivery_proof_weight( - &PreComputedSize(two_messages_delivery_proof_size as _), - &UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - total_messages: 1, - ..Default::default() - }, - ) - ); - assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); - assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); - assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); - }); - } - - #[test] - fn receive_messages_delivery_proof_rejects_invalid_proof() { - run_test(|| { - assert_noop!( - Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Err(())), - Default::default(), - ), - Error::::InvalidMessagesDeliveryProof, - ); - }); - } - - #[test] - fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() { - run_test(|| { - // when number of relayers entries is invalid - assert_noop!( - Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - relayers: vec![ - unrewarded_relayer(1, 1, TEST_RELAYER_A), - unrewarded_relayer(2, 2, TEST_RELAYER_B) - ] - .into_iter() - .collect(), - ..Default::default() - } - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - total_messages: 2, - last_delivered_nonce: 2, - ..Default::default() - }, - ), - Error::::InvalidUnrewardedRelayersState, - ); - - // when number of messages is invalid - assert_noop!( - Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - relayers: vec![ - unrewarded_relayer(1, 1, TEST_RELAYER_A), - unrewarded_relayer(2, 2, TEST_RELAYER_B) - ] - .into_iter() - .collect(), - ..Default::default() - } - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 2, - total_messages: 1, - last_delivered_nonce: 2, - ..Default::default() - }, - ), - Error::::InvalidUnrewardedRelayersState, - ); - - // when last delivered nonce is invalid - assert_noop!( - Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - relayers: vec![ - unrewarded_relayer(1, 1, TEST_RELAYER_A), - unrewarded_relayer(2, 2, TEST_RELAYER_B) - ] - .into_iter() - .collect(), - ..Default::default() - } - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 2, - total_messages: 2, - last_delivered_nonce: 8, - ..Default::default() - }, - ), - Error::::InvalidUnrewardedRelayersState, - ); - }); - } - - #[test] - fn receive_messages_accepts_single_message_with_invalid_payload() { - run_test(|| { - let mut invalid_message = message(1, REGULAR_PAYLOAD); - invalid_message.payload = Vec::new(); - - assert_ok!(Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok(vec![invalid_message]).into(), - 1, - Weight::zero(), /* weight may be zero in this case (all messages are - * improperly encoded) */ - ),); - - assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 1,); - }); - } - - #[test] - fn receive_messages_accepts_batch_with_message_with_invalid_payload() { - run_test(|| { - let mut invalid_message = message(2, REGULAR_PAYLOAD); - invalid_message.payload = Vec::new(); - - assert_ok!(Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - Ok( - vec![message(1, REGULAR_PAYLOAD), invalid_message, message(3, REGULAR_PAYLOAD),] - ) - .into(), - 3, - REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight, - ),); - - assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 3,); - }); - } - - #[test] - fn actual_dispatch_weight_does_not_overflow() { - run_test(|| { - let message1 = message(1, message_payload(0, u64::MAX / 2)); - let message2 = message(2, message_payload(0, u64::MAX / 2)); - let message3 = message(3, message_payload(0, u64::MAX / 2)); - - assert_noop!( - Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - // this may cause overflow if source chain storage is invalid - Ok(vec![message1, message2, message3]).into(), - 3, - Weight::MAX, - ), - Error::::InsufficientDispatchWeight - ); - assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); - }); - } - - #[test] - fn ref_time_refund_from_receive_messages_proof_works() { - run_test(|| { - fn submit_with_unspent_weight( - nonce: MessageNonce, - unspent_weight: u64, - ) -> (Weight, Weight) { - let mut payload = REGULAR_PAYLOAD; - *payload.dispatch_result.unspent_weight.ref_time_mut() = unspent_weight; - let proof = Ok(vec![message(nonce, payload)]).into(); - let messages_count = 1; - let pre_dispatch_weight = - ::WeightInfo::receive_messages_proof_weight( - &proof, - messages_count, - REGULAR_PAYLOAD.declared_weight, - ); - let result = Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - proof, - messages_count, - REGULAR_PAYLOAD.declared_weight, - ) - .expect("delivery has failed"); - let post_dispatch_weight = - result.actual_weight.expect("receive_messages_proof always returns Some"); - - // message delivery transactions are never free - assert_eq!(result.pays_fee, Pays::Yes); - - (pre_dispatch_weight, post_dispatch_weight) - } - - // when dispatch is returning `unspent_weight < declared_weight` - let (pre, post) = submit_with_unspent_weight(1, 1); - assert_eq!(post.ref_time(), pre.ref_time() - 1); - - // when dispatch is returning `unspent_weight = declared_weight` - let (pre, post) = - submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight.ref_time()); - assert_eq!( - post.ref_time(), - pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time() - ); - - // when dispatch is returning `unspent_weight > declared_weight` - let (pre, post) = - submit_with_unspent_weight(3, REGULAR_PAYLOAD.declared_weight.ref_time() + 1); - assert_eq!( - post.ref_time(), - pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time() - ); - - // when there's no unspent weight - let (pre, post) = submit_with_unspent_weight(4, 0); - assert_eq!(post.ref_time(), pre.ref_time()); - - // when dispatch is returning `unspent_weight < declared_weight` - let (pre, post) = submit_with_unspent_weight(5, 1); - assert_eq!(post.ref_time(), pre.ref_time() - 1); - }); - } - - #[test] - fn proof_size_refund_from_receive_messages_proof_works() { - run_test(|| { - let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize; - - // if there's maximal number of unrewarded relayer entries at the inbound lane, then - // `proof_size` is unchanged in post-dispatch weight - let proof: TestMessagesProof = Ok(vec![message(101, REGULAR_PAYLOAD)]).into(); - let messages_count = 1; - let pre_dispatch_weight = - ::WeightInfo::receive_messages_proof_weight( - &proof, - messages_count, - REGULAR_PAYLOAD.declared_weight, - ); - InboundLanes::::insert( - TEST_LANE_ID, - StoredInboundLaneData(InboundLaneData { - relayers: vec![ - UnrewardedRelayer { - relayer: 42, - messages: DeliveredMessages { begin: 0, end: 100 } - }; - max_entries - ] - .into_iter() - .collect(), - last_confirmed_nonce: 0, - }), - ); - let post_dispatch_weight = Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - proof.clone(), - messages_count, - REGULAR_PAYLOAD.declared_weight, - ) - .unwrap() - .actual_weight - .unwrap(); - assert_eq!(post_dispatch_weight.proof_size(), pre_dispatch_weight.proof_size()); - - // if count of unrewarded relayer entries is less than maximal, then some `proof_size` - // must be refunded - InboundLanes::::insert( - TEST_LANE_ID, - StoredInboundLaneData(InboundLaneData { - relayers: vec![ - UnrewardedRelayer { - relayer: 42, - messages: DeliveredMessages { begin: 0, end: 100 } - }; - max_entries - 1 - ] - .into_iter() - .collect(), - last_confirmed_nonce: 0, - }), - ); - let post_dispatch_weight = Pallet::::receive_messages_proof( - RuntimeOrigin::signed(1), - TEST_RELAYER_A, - proof, - messages_count, - REGULAR_PAYLOAD.declared_weight, - ) - .unwrap() - .actual_weight - .unwrap(); - assert!( - post_dispatch_weight.proof_size() < pre_dispatch_weight.proof_size(), - "Expected post-dispatch PoV {} to be less than pre-dispatch PoV {}", - post_dispatch_weight.proof_size(), - pre_dispatch_weight.proof_size(), - ); - }); - } - - #[test] - fn messages_delivered_callbacks_are_called() { - run_test(|| { - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - - // messages 1+2 are confirmed in 1 tx, message 3 in a separate tx - // dispatch of message 2 has failed - let mut delivered_messages_1_and_2 = DeliveredMessages::new(1); - delivered_messages_1_and_2.note_dispatched_message(); - let messages_1_and_2_proof = Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 0, - relayers: vec![UnrewardedRelayer { - relayer: 0, - messages: delivered_messages_1_and_2.clone(), - }] - .into_iter() - .collect(), - }, - )); - let delivered_message_3 = DeliveredMessages::new(3); - let messages_3_proof = Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 0, - relayers: vec![UnrewardedRelayer { relayer: 0, messages: delivered_message_3 }] - .into_iter() - .collect(), - }, - )); - - // first tx with messages 1+2 - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(messages_1_and_2_proof), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 2, - total_messages: 2, - last_delivered_nonce: 2, - }, - )); - // second tx with message 3 - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(messages_3_proof), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 3, - }, - )); - }); - } - - #[test] - fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messages_than_expected( - ) { - run_test(|| { - // send message first to be able to check that delivery_proof fails later - send_regular_message(TEST_LANE_ID); - - // 1) InboundLaneData declares that the `last_confirmed_nonce` is 1; - // 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` - // returns `last_confirmed_nonce`; - // 3) it means that we're going to confirm delivery of messages 1..=1; - // 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and - // number of actually confirmed messages is `1`. - assert_noop!( - Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { last_confirmed_nonce: 1, relayers: Default::default() }, - ))), - UnrewardedRelayersState { last_delivered_nonce: 1, ..Default::default() }, - ), - Error::::ReceptionConfirmation( - ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected - ), - ); - }); - } - - #[test] - fn storage_keys_computed_properly() { - assert_eq!( - PalletOperatingMode::::storage_value_final_key().to_vec(), - bp_messages::storage_keys::operating_mode_key("Messages").0, - ); - - assert_eq!( - OutboundMessages::::storage_map_final_key(MessageKey { - lane_id: TEST_LANE_ID, - nonce: 42 - }), - bp_messages::storage_keys::message_key("Messages", &TEST_LANE_ID, 42).0, - ); - - assert_eq!( - OutboundLanes::::storage_map_final_key(TEST_LANE_ID), - bp_messages::storage_keys::outbound_lane_data_key("Messages", &TEST_LANE_ID).0, - ); - - assert_eq!( - InboundLanes::::storage_map_final_key(TEST_LANE_ID), - bp_messages::storage_keys::inbound_lane_data_key("Messages", &TEST_LANE_ID).0, - ); - } - - #[test] - fn inbound_message_details_works() { - run_test(|| { - assert_eq!( - Pallet::::inbound_message_data( - TEST_LANE_ID, - REGULAR_PAYLOAD.encode(), - OutboundMessageDetails { nonce: 0, dispatch_weight: Weight::zero(), size: 0 }, - ), - InboundMessageDetails { dispatch_weight: REGULAR_PAYLOAD.declared_weight }, - ); - }); - } - - #[test] - fn on_idle_callback_respects_remaining_weight() { - run_test(|| { - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - send_regular_message(TEST_LANE_ID); - - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 4, - relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)] - .into_iter() - .collect(), - }, - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 4, - total_messages: 4, - last_delivered_nonce: 4, - }, - )); - - // all 4 messages may be pruned now - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, - 4 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 1 - ); - System::::set_block_number(2); - - // if passed wight is too low to do anything - let dbw = DbWeight::get(); - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 1)), - Weight::zero(), - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 1 - ); - - // if passed wight is enough to prune single message - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 2)), - dbw.reads_writes(1, 2), - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 2 - ); - - // if passed wight is enough to prune two more messages - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 3)), - dbw.reads_writes(1, 3), - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 4 - ); - - // if passed wight is enough to prune many messages - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 5 - ); - }); - } - - #[test] - fn on_idle_callback_is_rotating_lanes_to_prune() { - run_test(|| { - // send + receive confirmation for lane 1 - send_regular_message(TEST_LANE_ID); - receive_messages_delivery_proof(); - // send + receive confirmation for lane 2 - send_regular_message(TEST_LANE_ID_2); - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID_2, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] - .into_iter() - .collect(), - }, - ))), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - )); - - // nothing is pruned yet - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, - 1 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 1 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().latest_received_nonce, - 1 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 1 - ); - - // in block#2.on_idle lane messages of lane 1 are pruned - let dbw = DbWeight::get(); - System::::set_block_number(2); - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 2 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 1 - ); - - // in block#3.on_idle lane messages of lane 2 are pruned - System::::set_block_number(3); - - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, - 2 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 2 - ); - }); - } - - #[test] - fn outbound_message_from_unconfigured_lane_is_rejected() { - run_test(|| { - assert_noop!( - Pallet::::validate_message(TEST_LANE_ID_3, ®ULAR_PAYLOAD,), - Error::::InactiveOutboundLane, - ); - }); - } - - #[test] - fn test_bridge_messages_call_is_correctly_defined() { - let account_id = 1; - let message_proof: TestMessagesProof = Ok(vec![message(1, REGULAR_PAYLOAD)]).into(); - let message_delivery_proof = TestMessagesDeliveryProof(Ok(( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![UnrewardedRelayer { - relayer: 0, - messages: DeliveredMessages::new(1), - }] - .into_iter() - .collect(), - }, - ))); - let unrewarded_relayer_state = UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - total_messages: 1, - last_delivered_nonce: 1, - ..Default::default() - }; - - let direct_receive_messages_proof_call = Call::::receive_messages_proof { - relayer_id_at_bridged_chain: account_id, - proof: message_proof.clone(), - messages_count: 1, - dispatch_weight: REGULAR_PAYLOAD.declared_weight, - }; - let indirect_receive_messages_proof_call = BridgeMessagesCall::< - AccountId, - TestMessagesProof, - TestMessagesDeliveryProof, - >::receive_messages_proof { - relayer_id_at_bridged_chain: account_id, - proof: message_proof, - messages_count: 1, - dispatch_weight: REGULAR_PAYLOAD.declared_weight, - }; - assert_eq!( - direct_receive_messages_proof_call.encode(), - indirect_receive_messages_proof_call.encode() - ); - - let direct_receive_messages_delivery_proof_call = - Call::::receive_messages_delivery_proof { - proof: message_delivery_proof.clone(), - relayers_state: unrewarded_relayer_state.clone(), - }; - let indirect_receive_messages_delivery_proof_call = BridgeMessagesCall::< - AccountId, - TestMessagesProof, - TestMessagesDeliveryProof, - >::receive_messages_delivery_proof { - proof: message_delivery_proof, - relayers_state: unrewarded_relayer_state, - }; - assert_eq!( - direct_receive_messages_delivery_proof_call.encode(), - indirect_receive_messages_delivery_proof_call.encode() - ); - } - - generate_owned_bridge_module_tests!( - MessagesOperatingMode::Basic(BasicOperatingMode::Normal), - MessagesOperatingMode::Basic(BasicOperatingMode::Halted) - ); - - #[test] - fn inbound_storage_extra_proof_size_bytes_works() { - fn relayer_entry() -> UnrewardedRelayer { - UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100 } } - } - - fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { - RuntimeInboundLaneStorage { - lane_id: Default::default(), - cached_data: Some(InboundLaneData { - relayers: vec![relayer_entry(); relayer_entries].into_iter().collect(), - last_confirmed_nonce: 0, - }), - _phantom: Default::default(), - } - } - - let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize; - - // when we have exactly `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers - assert_eq!(storage(max_entries).extra_proof_size_bytes(), 0); - - // when we have less than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers - assert_eq!( - storage(max_entries - 1).extra_proof_size_bytes(), - relayer_entry().encode().len() as u64 - ); - assert_eq!( - storage(max_entries - 2).extra_proof_size_bytes(), - 2 * relayer_entry().encode().len() as u64 - ); - - // when we have more than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers - // (shall not happen in practice) - assert_eq!(storage(max_entries + 1).extra_proof_size_bytes(), 0); - } - - #[test] - fn maybe_outbound_lanes_count_returns_correct_value() { - assert_eq!( - MaybeOutboundLanesCount::::get(), - Some(mock::ActiveOutboundLanes::get().len() as u32) - ); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// mock::{ +// inbound_unrewarded_relayers_state, message, message_payload, run_test, +// unrewarded_relayer, AccountId, DbWeight, RuntimeEvent as TestEvent, RuntimeOrigin, +// TestDeliveryConfirmationPayments, TestDeliveryPayments, TestMessageDispatch, +// TestMessagesDeliveryProof, TestMessagesProof, TestOnMessagesDelivered, TestRelayer, +// TestRuntime, TestWeightInfo, MAX_OUTBOUND_PAYLOAD_SIZE, +// PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_LANE_ID_2, +// TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, +// }, +// outbound_lane::ReceptionConfirmationError, +// }; +// use bp_messages::{ +// source_chain::MessagesBridge, BridgeMessagesCall, UnrewardedRelayer, +// UnrewardedRelayersState, +// }; +// use bp_test_utils::generate_owned_bridge_module_tests; +// use frame_support::{ +// assert_noop, assert_ok, +// dispatch::Pays, +// storage::generator::{StorageMap, StorageValue}, +// traits::Hooks, +// weights::Weight, +// }; +// use frame_system::{EventRecord, Pallet as System, Phase}; +// use sp_runtime::DispatchError; +// +// fn get_ready_for_events() { +// System::::set_block_number(1); +// System::::reset_events(); +// } +// +// fn send_regular_message(lane_id: LaneId) { +// get_ready_for_events(); +// +// let outbound_lane = outbound_lane::(lane_id); +// let message_nonce = outbound_lane.data().latest_generated_nonce + 1; +// let prev_enqueued_messages = outbound_lane.data().queued_messages().saturating_len(); +// let valid_message = Pallet::::validate_message(lane_id, ®ULAR_PAYLOAD) +// .expect("validate_message has failed"); +// let artifacts = Pallet::::send_message(valid_message); +// assert_eq!(artifacts.enqueued_messages, prev_enqueued_messages + 1); +// +// // check event with assigned nonce +// assert_eq!( +// System::::events(), +// vec![EventRecord { +// phase: Phase::Initialization, +// event: TestEvent::Messages(Event::MessageAccepted { +// lane_id, +// nonce: message_nonce +// }), +// topics: vec![], +// }], +// ); +// } +// +// fn receive_messages_delivery_proof() { +// System::::set_block_number(1); +// System::::reset_events(); +// +// assert_ok!(Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 1, +// relayers: vec![UnrewardedRelayer { +// relayer: 0, +// messages: DeliveredMessages::new(1), +// }] +// .into_iter() +// .collect(), +// }, +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 1, +// total_messages: 1, +// last_delivered_nonce: 1, +// }, +// )); +// +// assert_eq!( +// System::::events(), +// vec![EventRecord { +// phase: Phase::Initialization, +// event: TestEvent::Messages(Event::MessagesDelivered { +// lane_id: TEST_LANE_ID, +// messages: DeliveredMessages::new(1), +// }), +// topics: vec![], +// }], +// ); +// } +// +// #[test] +// fn pallet_rejects_transactions_if_halted() { +// run_test(|| { +// // send message first to be able to check that delivery_proof fails later +// send_regular_message(TEST_LANE_ID); +// +// PalletOperatingMode::::put(MessagesOperatingMode::Basic( +// BasicOperatingMode::Halted, +// )); +// +// assert_noop!( +// Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), +// Error::::NotOperatingNormally, +// ); +// +// assert_noop!( +// Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![message(2, REGULAR_PAYLOAD)]).into(), +// 1, +// REGULAR_PAYLOAD.declared_weight, +// ), +// Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), +// ); +// +// assert_noop!( +// Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 1, +// relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] +// .into_iter() +// .collect(), +// }, +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 1, +// total_messages: 1, +// last_delivered_nonce: 1, +// }, +// ), +// Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), +// ); +// }); +// } +// +// #[test] +// fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { +// run_test(|| { +// // send message first to be able to check that delivery_proof fails later +// send_regular_message(TEST_LANE_ID); +// +// PalletOperatingMode::::put( +// MessagesOperatingMode::RejectingOutboundMessages, +// ); +// +// assert_noop!( +// Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), +// Error::::NotOperatingNormally, +// ); +// +// assert_ok!(Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), +// 1, +// REGULAR_PAYLOAD.declared_weight, +// ),); +// +// assert_ok!(Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 1, +// relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] +// .into_iter() +// .collect(), +// }, +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 1, +// total_messages: 1, +// last_delivered_nonce: 1, +// }, +// )); +// }); +// } +// +// #[test] +// fn send_message_works() { +// run_test(|| { +// send_regular_message(TEST_LANE_ID); +// }); +// } +// +// #[test] +// fn send_message_rejects_too_large_message() { +// run_test(|| { +// let mut message_payload = message_payload(1, 0); +// // the payload isn't simply extra, so it'll definitely overflow +// // `MAX_OUTBOUND_PAYLOAD_SIZE` if we add `MAX_OUTBOUND_PAYLOAD_SIZE` bytes to extra +// message_payload +// .extra +// .extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]); +// assert_noop!( +// Pallet::::validate_message(TEST_LANE_ID, &message_payload.clone(),), +// Error::::MessageRejectedByPallet( +// VerificationError::MessageTooLarge +// ), +// ); +// +// // let's check that we're able to send `MAX_OUTBOUND_PAYLOAD_SIZE` messages +// while message_payload.encoded_size() as u32 > MAX_OUTBOUND_PAYLOAD_SIZE { +// message_payload.extra.pop(); +// } +// assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE); +// +// let valid_message = +// Pallet::::validate_message(TEST_LANE_ID, &message_payload) +// .expect("validate_message has failed"); +// Pallet::::send_message(valid_message); +// }) +// } +// +// #[test] +// fn chain_verifier_rejects_invalid_message_in_send_message() { +// run_test(|| { +// // messages with this payload are rejected by target chain verifier +// assert_noop!( +// Pallet::::validate_message( +// TEST_LANE_ID, +// &PAYLOAD_REJECTED_BY_TARGET_CHAIN, +// ), +// Error::::MessageRejectedByChainVerifier(VerificationError::Other( +// mock::TEST_ERROR +// )), +// ); +// }); +// } +// +// #[test] +// fn receive_messages_proof_works() { +// run_test(|| { +// assert_ok!(Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), +// 1, +// REGULAR_PAYLOAD.declared_weight, +// )); +// +// assert_eq!(InboundLanes::::get(TEST_LANE_ID).0.last_delivered_nonce(), 1); +// +// assert!(TestDeliveryPayments::is_reward_paid(1)); +// }); +// } +// +// #[test] +// fn receive_messages_proof_updates_confirmed_message_nonce() { +// run_test(|| { +// // say we have received 10 messages && last confirmed message is 8 +// InboundLanes::::insert( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 8, +// relayers: vec![ +// unrewarded_relayer(9, 9, TEST_RELAYER_A), +// unrewarded_relayer(10, 10, TEST_RELAYER_B), +// ] +// .into_iter() +// .collect(), +// }, +// ); +// assert_eq!( +// inbound_unrewarded_relayers_state(TEST_LANE_ID), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 2, +// messages_in_oldest_entry: 1, +// total_messages: 2, +// last_delivered_nonce: 10, +// }, +// ); +// +// // message proof includes outbound lane state with latest confirmed message updated to 9 +// let mut message_proof: TestMessagesProof = +// Ok(vec![message(11, REGULAR_PAYLOAD)]).into(); +// message_proof.result.as_mut().unwrap()[0].1.lane_state = +// Some(OutboundLaneData { latest_received_nonce: 9, ..Default::default() }); +// +// assert_ok!(Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// message_proof, +// 1, +// REGULAR_PAYLOAD.declared_weight, +// )); +// +// assert_eq!( +// InboundLanes::::get(TEST_LANE_ID).0, +// InboundLaneData { +// last_confirmed_nonce: 9, +// relayers: vec![ +// unrewarded_relayer(10, 10, TEST_RELAYER_B), +// unrewarded_relayer(11, 11, TEST_RELAYER_A) +// ] +// .into_iter() +// .collect(), +// }, +// ); +// assert_eq!( +// inbound_unrewarded_relayers_state(TEST_LANE_ID), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 2, +// messages_in_oldest_entry: 1, +// total_messages: 2, +// last_delivered_nonce: 11, +// }, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_fails_if_dispatcher_is_inactive() { +// run_test(|| { +// TestMessageDispatch::deactivate(); +// assert_noop!( +// Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), +// 1, +// REGULAR_PAYLOAD.declared_weight, +// ), +// Error::::MessageDispatchInactive, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() { +// run_test(|| { +// let mut declared_weight = REGULAR_PAYLOAD.declared_weight; +// *declared_weight.ref_time_mut() -= 1; +// assert_noop!( +// Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), +// 1, +// declared_weight, +// ), +// Error::::InsufficientDispatchWeight +// ); +// assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); +// }); +// } +// +// #[test] +// fn receive_messages_proof_rejects_invalid_proof() { +// run_test(|| { +// assert_noop!( +// Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Err(()).into(), +// 1, +// Weight::zero(), +// ), +// Error::::InvalidMessagesProof, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_proof_rejects_proof_with_too_many_messages() { +// run_test(|| { +// assert_noop!( +// Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), +// u32::MAX, +// Weight::zero(), +// ), +// Error::::TooManyMessagesInTheProof, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_delivery_proof_works() { +// run_test(|| { +// send_regular_message(TEST_LANE_ID); +// receive_messages_delivery_proof(); +// +// assert_eq!( +// OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, +// 1, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_delivery_proof_rewards_relayers() { +// run_test(|| { +// send_regular_message(TEST_LANE_ID); +// send_regular_message(TEST_LANE_ID); +// +// // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A +// let single_message_delivery_proof = TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into_iter().collect(), +// ..Default::default() +// }, +// ))); +// let single_message_delivery_proof_size = single_message_delivery_proof.size(); +// let result = Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// single_message_delivery_proof, +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 1, +// total_messages: 1, +// last_delivered_nonce: 1, +// }, +// ); +// assert_ok!(result); +// assert_eq!( +// result.unwrap().actual_weight.unwrap(), +// TestWeightInfo::receive_messages_delivery_proof_weight( +// &PreComputedSize(single_message_delivery_proof_size as _), +// &UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// total_messages: 1, +// ..Default::default() +// }, +// ) +// ); +// assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); +// assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); +// assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 1))); +// +// // this reports delivery of both message 1 and message 2 => reward is paid only to +// // TEST_RELAYER_B +// let two_messages_delivery_proof = TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// relayers: vec![ +// unrewarded_relayer(1, 1, TEST_RELAYER_A), +// unrewarded_relayer(2, 2, TEST_RELAYER_B), +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// }, +// ))); +// let two_messages_delivery_proof_size = two_messages_delivery_proof.size(); +// let result = Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// two_messages_delivery_proof, +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 2, +// messages_in_oldest_entry: 1, +// total_messages: 2, +// last_delivered_nonce: 2, +// }, +// ); +// assert_ok!(result); +// // even though the pre-dispatch weight was for two messages, the actual weight is +// // for single message only +// assert_eq!( +// result.unwrap().actual_weight.unwrap(), +// TestWeightInfo::receive_messages_delivery_proof_weight( +// &PreComputedSize(two_messages_delivery_proof_size as _), +// &UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// total_messages: 1, +// ..Default::default() +// }, +// ) +// ); +// assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); +// assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); +// assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); +// }); +// } +// +// #[test] +// fn receive_messages_delivery_proof_rejects_invalid_proof() { +// run_test(|| { +// assert_noop!( +// Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Err(())), +// Default::default(), +// ), +// Error::::InvalidMessagesDeliveryProof, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() { +// run_test(|| { +// // when number of relayers entries is invalid +// assert_noop!( +// Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// relayers: vec![ +// unrewarded_relayer(1, 1, TEST_RELAYER_A), +// unrewarded_relayer(2, 2, TEST_RELAYER_B) +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// } +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// total_messages: 2, +// last_delivered_nonce: 2, +// ..Default::default() +// }, +// ), +// Error::::InvalidUnrewardedRelayersState, +// ); +// +// // when number of messages is invalid +// assert_noop!( +// Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// relayers: vec![ +// unrewarded_relayer(1, 1, TEST_RELAYER_A), +// unrewarded_relayer(2, 2, TEST_RELAYER_B) +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// } +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 2, +// total_messages: 1, +// last_delivered_nonce: 2, +// ..Default::default() +// }, +// ), +// Error::::InvalidUnrewardedRelayersState, +// ); +// +// // when last delivered nonce is invalid +// assert_noop!( +// Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// relayers: vec![ +// unrewarded_relayer(1, 1, TEST_RELAYER_A), +// unrewarded_relayer(2, 2, TEST_RELAYER_B) +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// } +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 2, +// total_messages: 2, +// last_delivered_nonce: 8, +// ..Default::default() +// }, +// ), +// Error::::InvalidUnrewardedRelayersState, +// ); +// }); +// } +// +// #[test] +// fn receive_messages_accepts_single_message_with_invalid_payload() { +// run_test(|| { +// let mut invalid_message = message(1, REGULAR_PAYLOAD); +// invalid_message.payload = Vec::new(); +// +// assert_ok!(Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok(vec![invalid_message]).into(), +// 1, +// Weight::zero(), /* weight may be zero in this case (all messages are +// * improperly encoded) */ +// ),); +// +// assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 1,); +// }); +// } +// +// #[test] +// fn receive_messages_accepts_batch_with_message_with_invalid_payload() { +// run_test(|| { +// let mut invalid_message = message(2, REGULAR_PAYLOAD); +// invalid_message.payload = Vec::new(); +// +// assert_ok!(Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// Ok( +// vec![message(1, REGULAR_PAYLOAD), invalid_message, message(3, REGULAR_PAYLOAD),] +// ) +// .into(), +// 3, +// REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight, +// ),); +// +// assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 3,); +// }); +// } +// +// #[test] +// fn actual_dispatch_weight_does_not_overflow() { +// run_test(|| { +// let message1 = message(1, message_payload(0, u64::MAX / 2)); +// let message2 = message(2, message_payload(0, u64::MAX / 2)); +// let message3 = message(3, message_payload(0, u64::MAX / 2)); +// +// assert_noop!( +// Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// // this may cause overflow if source chain storage is invalid +// Ok(vec![message1, message2, message3]).into(), +// 3, +// Weight::MAX, +// ), +// Error::::InsufficientDispatchWeight +// ); +// assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); +// }); +// } +// +// #[test] +// fn ref_time_refund_from_receive_messages_proof_works() { +// run_test(|| { +// fn submit_with_unspent_weight( +// nonce: MessageNonce, +// unspent_weight: u64, +// ) -> (Weight, Weight) { +// let mut payload = REGULAR_PAYLOAD; +// *payload.dispatch_result.unspent_weight.ref_time_mut() = unspent_weight; +// let proof = Ok(vec![message(nonce, payload)]).into(); +// let messages_count = 1; +// let pre_dispatch_weight = +// ::WeightInfo::receive_messages_proof_weight( +// &proof, +// messages_count, +// REGULAR_PAYLOAD.declared_weight, +// ); +// let result = Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// proof, +// messages_count, +// REGULAR_PAYLOAD.declared_weight, +// ) +// .expect("delivery has failed"); +// let post_dispatch_weight = +// result.actual_weight.expect("receive_messages_proof always returns Some"); +// +// // message delivery transactions are never free +// assert_eq!(result.pays_fee, Pays::Yes); +// +// (pre_dispatch_weight, post_dispatch_weight) +// } +// +// // when dispatch is returning `unspent_weight < declared_weight` +// let (pre, post) = submit_with_unspent_weight(1, 1); +// assert_eq!(post.ref_time(), pre.ref_time() - 1); +// +// // when dispatch is returning `unspent_weight = declared_weight` +// let (pre, post) = +// submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight.ref_time()); +// assert_eq!( +// post.ref_time(), +// pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time() +// ); +// +// // when dispatch is returning `unspent_weight > declared_weight` +// let (pre, post) = +// submit_with_unspent_weight(3, REGULAR_PAYLOAD.declared_weight.ref_time() + 1); +// assert_eq!( +// post.ref_time(), +// pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time() +// ); +// +// // when there's no unspent weight +// let (pre, post) = submit_with_unspent_weight(4, 0); +// assert_eq!(post.ref_time(), pre.ref_time()); +// +// // when dispatch is returning `unspent_weight < declared_weight` +// let (pre, post) = submit_with_unspent_weight(5, 1); +// assert_eq!(post.ref_time(), pre.ref_time() - 1); +// }); +// } +// +// #[test] +// fn proof_size_refund_from_receive_messages_proof_works() { +// run_test(|| { +// let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize; +// +// // if there's maximal number of unrewarded relayer entries at the inbound lane, then +// // `proof_size` is unchanged in post-dispatch weight +// let proof: TestMessagesProof = Ok(vec![message(101, REGULAR_PAYLOAD)]).into(); +// let messages_count = 1; +// let pre_dispatch_weight = +// ::WeightInfo::receive_messages_proof_weight( +// &proof, +// messages_count, +// REGULAR_PAYLOAD.declared_weight, +// ); +// InboundLanes::::insert( +// TEST_LANE_ID, +// StoredInboundLaneData(InboundLaneData { +// relayers: vec![ +// UnrewardedRelayer { +// relayer: 42, +// messages: DeliveredMessages { begin: 0, end: 100 } +// }; +// max_entries +// ] +// .into_iter() +// .collect(), +// last_confirmed_nonce: 0, +// }), +// ); +// let post_dispatch_weight = Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// proof.clone(), +// messages_count, +// REGULAR_PAYLOAD.declared_weight, +// ) +// .unwrap() +// .actual_weight +// .unwrap(); +// assert_eq!(post_dispatch_weight.proof_size(), pre_dispatch_weight.proof_size()); +// +// // if count of unrewarded relayer entries is less than maximal, then some `proof_size` +// // must be refunded +// InboundLanes::::insert( +// TEST_LANE_ID, +// StoredInboundLaneData(InboundLaneData { +// relayers: vec![ +// UnrewardedRelayer { +// relayer: 42, +// messages: DeliveredMessages { begin: 0, end: 100 } +// }; +// max_entries - 1 +// ] +// .into_iter() +// .collect(), +// last_confirmed_nonce: 0, +// }), +// ); +// let post_dispatch_weight = Pallet::::receive_messages_proof( +// RuntimeOrigin::signed(1), +// TEST_RELAYER_A, +// proof, +// messages_count, +// REGULAR_PAYLOAD.declared_weight, +// ) +// .unwrap() +// .actual_weight +// .unwrap(); +// assert!( +// post_dispatch_weight.proof_size() < pre_dispatch_weight.proof_size(), +// "Expected post-dispatch PoV {} to be less than pre-dispatch PoV {}", +// post_dispatch_weight.proof_size(), +// pre_dispatch_weight.proof_size(), +// ); +// }); +// } +// +// #[test] +// fn messages_delivered_callbacks_are_called() { +// run_test(|| { +// send_regular_message(TEST_LANE_ID); +// send_regular_message(TEST_LANE_ID); +// send_regular_message(TEST_LANE_ID); +// +// // messages 1+2 are confirmed in 1 tx, message 3 in a separate tx +// // dispatch of message 2 has failed +// let mut delivered_messages_1_and_2 = DeliveredMessages::new(1); +// delivered_messages_1_and_2.note_dispatched_message(); +// let messages_1_and_2_proof = Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 0, +// relayers: vec![UnrewardedRelayer { +// relayer: 0, +// messages: delivered_messages_1_and_2.clone(), +// }] +// .into_iter() +// .collect(), +// }, +// )); +// let delivered_message_3 = DeliveredMessages::new(3); +// let messages_3_proof = Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 0, +// relayers: vec![UnrewardedRelayer { relayer: 0, messages: delivered_message_3 }] +// .into_iter() +// .collect(), +// }, +// )); +// +// // first tx with messages 1+2 +// assert_ok!(Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(messages_1_and_2_proof), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 2, +// total_messages: 2, +// last_delivered_nonce: 2, +// }, +// )); +// // second tx with message 3 +// assert_ok!(Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(messages_3_proof), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 1, +// total_messages: 1, +// last_delivered_nonce: 3, +// }, +// )); +// }); +// } +// +// #[test] +// fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messages_than_expected( +// ) { +// run_test(|| { +// // send message first to be able to check that delivery_proof fails later +// send_regular_message(TEST_LANE_ID); +// +// // 1) InboundLaneData declares that the `last_confirmed_nonce` is 1; +// // 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` +// // returns `last_confirmed_nonce`; +// // 3) it means that we're going to confirm delivery of messages 1..=1; +// // 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and +// // number of actually confirmed messages is `1`. +// assert_noop!( +// Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { last_confirmed_nonce: 1, relayers: Default::default() }, +// ))), +// UnrewardedRelayersState { last_delivered_nonce: 1, ..Default::default() }, +// ), +// Error::::ReceptionConfirmation( +// ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected +// ), +// ); +// }); +// } +// +// #[test] +// fn storage_keys_computed_properly() { +// assert_eq!( +// PalletOperatingMode::::storage_value_final_key().to_vec(), +// bp_messages::storage_keys::operating_mode_key("Messages").0, +// ); +// +// assert_eq!( +// OutboundMessages::::storage_map_final_key(MessageKey { +// lane_id: TEST_LANE_ID, +// nonce: 42 +// }), +// bp_messages::storage_keys::message_key("Messages", &TEST_LANE_ID, 42).0, +// ); +// +// assert_eq!( +// OutboundLanes::::storage_map_final_key(TEST_LANE_ID), +// bp_messages::storage_keys::outbound_lane_data_key("Messages", &TEST_LANE_ID).0, +// ); +// +// assert_eq!( +// InboundLanes::::storage_map_final_key(TEST_LANE_ID), +// bp_messages::storage_keys::inbound_lane_data_key("Messages", &TEST_LANE_ID).0, +// ); +// } +// +// #[test] +// fn inbound_message_details_works() { +// run_test(|| { +// assert_eq!( +// Pallet::::inbound_message_data( +// TEST_LANE_ID, +// REGULAR_PAYLOAD.encode(), +// OutboundMessageDetails { nonce: 0, dispatch_weight: Weight::zero(), size: 0 }, +// ), +// InboundMessageDetails { dispatch_weight: REGULAR_PAYLOAD.declared_weight }, +// ); +// }); +// } +// +// #[test] +// fn on_idle_callback_respects_remaining_weight() { +// run_test(|| { +// send_regular_message(TEST_LANE_ID); +// send_regular_message(TEST_LANE_ID); +// send_regular_message(TEST_LANE_ID); +// send_regular_message(TEST_LANE_ID); +// +// assert_ok!(Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 4, +// relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)] +// .into_iter() +// .collect(), +// }, +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 4, +// total_messages: 4, +// last_delivered_nonce: 4, +// }, +// )); +// +// // all 4 messages may be pruned now +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, +// 4 +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 1 +// ); +// System::::set_block_number(2); +// +// // if passed wight is too low to do anything +// let dbw = DbWeight::get(); +// assert_eq!( +// Pallet::::on_idle(0, dbw.reads_writes(1, 1)), +// Weight::zero(), +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 1 +// ); +// +// // if passed wight is enough to prune single message +// assert_eq!( +// Pallet::::on_idle(0, dbw.reads_writes(1, 2)), +// dbw.reads_writes(1, 2), +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 2 +// ); +// +// // if passed wight is enough to prune two more messages +// assert_eq!( +// Pallet::::on_idle(0, dbw.reads_writes(1, 3)), +// dbw.reads_writes(1, 3), +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 4 +// ); +// +// // if passed wight is enough to prune many messages +// assert_eq!( +// Pallet::::on_idle(0, dbw.reads_writes(100, 100)), +// dbw.reads_writes(1, 2), +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 5 +// ); +// }); +// } +// +// #[test] +// fn on_idle_callback_is_rotating_lanes_to_prune() { +// run_test(|| { +// // send + receive confirmation for lane 1 +// send_regular_message(TEST_LANE_ID); +// receive_messages_delivery_proof(); +// // send + receive confirmation for lane 2 +// send_regular_message(TEST_LANE_ID_2); +// assert_ok!(Pallet::::receive_messages_delivery_proof( +// RuntimeOrigin::signed(1), +// TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID_2, +// InboundLaneData { +// last_confirmed_nonce: 1, +// relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] +// .into_iter() +// .collect(), +// }, +// ))), +// UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// messages_in_oldest_entry: 1, +// total_messages: 1, +// last_delivered_nonce: 1, +// }, +// )); +// +// // nothing is pruned yet +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, +// 1 +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 1 +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID_2).data().latest_received_nonce, +// 1 +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, +// 1 +// ); +// +// // in block#2.on_idle lane messages of lane 1 are pruned +// let dbw = DbWeight::get(); +// System::::set_block_number(2); +// assert_eq!( +// Pallet::::on_idle(0, dbw.reads_writes(100, 100)), +// dbw.reads_writes(1, 2), +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 2 +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, +// 1 +// ); +// +// // in block#3.on_idle lane messages of lane 2 are pruned +// System::::set_block_number(3); +// +// assert_eq!( +// Pallet::::on_idle(0, dbw.reads_writes(100, 100)), +// dbw.reads_writes(1, 2), +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, +// 2 +// ); +// assert_eq!( +// outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, +// 2 +// ); +// }); +// } +// +// #[test] +// fn outbound_message_from_unconfigured_lane_is_rejected() { +// run_test(|| { +// assert_noop!( +// Pallet::::validate_message(TEST_LANE_ID_3, ®ULAR_PAYLOAD,), +// Error::::InactiveOutboundLane, +// ); +// }); +// } +// +// #[test] +// fn test_bridge_messages_call_is_correctly_defined() { +// let account_id = 1; +// let message_proof: TestMessagesProof = Ok(vec![message(1, REGULAR_PAYLOAD)]).into(); +// let message_delivery_proof = TestMessagesDeliveryProof(Ok(( +// TEST_LANE_ID, +// InboundLaneData { +// last_confirmed_nonce: 1, +// relayers: vec![UnrewardedRelayer { +// relayer: 0, +// messages: DeliveredMessages::new(1), +// }] +// .into_iter() +// .collect(), +// }, +// ))); +// let unrewarded_relayer_state = UnrewardedRelayersState { +// unrewarded_relayer_entries: 1, +// total_messages: 1, +// last_delivered_nonce: 1, +// ..Default::default() +// }; +// +// let direct_receive_messages_proof_call = Call::::receive_messages_proof { +// relayer_id_at_bridged_chain: account_id, +// proof: message_proof.clone(), +// messages_count: 1, +// dispatch_weight: REGULAR_PAYLOAD.declared_weight, +// }; +// let indirect_receive_messages_proof_call = BridgeMessagesCall::< +// AccountId, +// TestMessagesProof, +// TestMessagesDeliveryProof, +// >::receive_messages_proof { +// relayer_id_at_bridged_chain: account_id, +// proof: message_proof, +// messages_count: 1, +// dispatch_weight: REGULAR_PAYLOAD.declared_weight, +// }; +// assert_eq!( +// direct_receive_messages_proof_call.encode(), +// indirect_receive_messages_proof_call.encode() +// ); +// +// let direct_receive_messages_delivery_proof_call = +// Call::::receive_messages_delivery_proof { +// proof: message_delivery_proof.clone(), +// relayers_state: unrewarded_relayer_state.clone(), +// }; +// let indirect_receive_messages_delivery_proof_call = BridgeMessagesCall::< +// AccountId, +// TestMessagesProof, +// TestMessagesDeliveryProof, +// >::receive_messages_delivery_proof { +// proof: message_delivery_proof, +// relayers_state: unrewarded_relayer_state, +// }; +// assert_eq!( +// direct_receive_messages_delivery_proof_call.encode(), +// indirect_receive_messages_delivery_proof_call.encode() +// ); +// } +// +// generate_owned_bridge_module_tests!( +// MessagesOperatingMode::Basic(BasicOperatingMode::Normal), +// MessagesOperatingMode::Basic(BasicOperatingMode::Halted) +// ); +// +// #[test] +// fn inbound_storage_extra_proof_size_bytes_works() { +// fn relayer_entry() -> UnrewardedRelayer { +// UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100 } } +// } +// +// fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { +// RuntimeInboundLaneStorage { +// lane_id: Default::default(), +// cached_data: Some(InboundLaneData { +// relayers: vec![relayer_entry(); relayer_entries].into_iter().collect(), +// last_confirmed_nonce: 0, +// }), +// _phantom: Default::default(), +// } +// } +// +// let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize; +// +// // when we have exactly `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers +// assert_eq!(storage(max_entries).extra_proof_size_bytes(), 0); +// +// // when we have less than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers +// assert_eq!( +// storage(max_entries - 1).extra_proof_size_bytes(), +// relayer_entry().encode().len() as u64 +// ); +// assert_eq!( +// storage(max_entries - 2).extra_proof_size_bytes(), +// 2 * relayer_entry().encode().len() as u64 +// ); +// +// // when we have more than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers +// // (shall not happen in practice) +// assert_eq!(storage(max_entries + 1).extra_proof_size_bytes(), 0); +// } +// +// #[test] +// fn maybe_outbound_lanes_count_returns_correct_value() { +// assert_eq!( +// MaybeOutboundLanesCount::::get(), +// Some(mock::ActiveOutboundLanes::get().len() as u32) +// ); +// } +// } diff --git a/bridges/modules/messages/src/outbound_lane.rs b/bridges/modules/messages/src/outbound_lane.rs index acef5546d2a64..2508ea9a4c419 100644 --- a/bridges/modules/messages/src/outbound_lane.rs +++ b/bridges/modules/messages/src/outbound_lane.rs @@ -18,16 +18,19 @@ use crate::{Config, LOG_TARGET}; -use bp_messages::{DeliveredMessages, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer}; +use bp_messages::{ + ChainWithMessages, DeliveredMessages, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer, +}; use codec::{Decode, Encode}; use frame_support::{ + traits::Get, weights::{RuntimeDbWeight, Weight}, BoundedVec, PalletError, }; use num_traits::Zero; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; -use sp_std::collections::vec_deque::VecDeque; +use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData}; /// Outbound lane storage. pub trait OutboundLaneStorage { @@ -48,8 +51,17 @@ pub trait OutboundLaneStorage { fn remove_message(&mut self, nonce: &MessageNonce); } +/// Limit for the `StoredMessagePayload` vector. +pub struct StoredMessagePayloadLimit(PhantomData<(T, I)>); + +impl, I: 'static> Get for StoredMessagePayloadLimit { + fn get() -> u32 { + T::BridgedChain::maximal_incoming_message_size() + } +} + /// Outbound message data wrapper that implements `MaxEncodedLen`. -pub type StoredMessagePayload = BoundedVec>::MaximalOutboundPayloadSize>; +pub type StoredMessagePayload = BoundedVec>; /// Result of messages receival confirmation. #[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo)] @@ -204,11 +216,11 @@ fn ensure_unrewarded_relayers_are_correct( mod tests { use super::*; use crate::{ - mock::{ + outbound_lane, + tests::mock::{ outbound_message_data, run_test, unrewarded_relayer, TestRelayer, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID, }, - outbound_lane, }; use frame_support::weights::constants::RocksDbWeight; use sp_std::ops::RangeInclusive; diff --git a/bridges/modules/messages/src/proofs.rs b/bridges/modules/messages/src/proofs.rs new file mode 100644 index 0000000000000..9d331d846d8cf --- /dev/null +++ b/bridges/modules/messages/src/proofs.rs @@ -0,0 +1,503 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tools for messages and delivery proof verification. + +use crate::{BridgedChainOf, BridgedHeaderChainOf, Config}; + +use bp_header_chain::HeaderChain; +use bp_messages::{ + source_chain::FromBridgedChainMessagesDeliveryProof, + target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages}, + ChainWithMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, + OutboundLaneData, VerificationError, +}; +use bp_runtime::{HashOf, RangeInclusiveExt, TrustedVecDb}; +use sp_std::vec::Vec; + +/// 'Parsed' message delivery proof - inbound lane id and its state. +pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = + (LaneId, InboundLaneData<::AccountId>); + +/// Verify proof of Bridged -> This chain messages. +/// +/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged +/// parachains, please use the `verify_messages_proof_from_parachain`. +/// +/// The `messages_count` argument verification (sane limits) is supposed to be made +/// outside of this function. This function only verifies that the proof declares exactly +/// `messages_count` messages. +pub fn verify_messages_proof, I: 'static>( + proof: FromBridgedChainMessagesProof>>, + messages_count: u32, +) -> Result, VerificationError> { + let FromBridgedChainMessagesProof { + bridged_header_hash, + storage, + lane, + nonces_start, + nonces_end, + } = proof; + let storage = BridgedHeaderChainOf::::verify_vec_db_storage(bridged_header_hash, storage) + .map_err(VerificationError::HeaderChain)?; + let mut parser = StorageAdapter:: { storage, _dummy: Default::default() }; + let nonces_range = nonces_start..=nonces_end; + + // receiving proofs where end < begin is ok (if proof includes outbound lane state) + let messages_in_the_proof = nonces_range.checked_len().unwrap_or(0); + if messages_in_the_proof != MessageNonce::from(messages_count) { + return Err(VerificationError::MessagesCountMismatch) + } + + // Read messages first. All messages that are claimed to be in the proof must + // be in the proof. So any error in `read_value`, or even missing value is fatal. + // + // Mind that we allow proofs with no messages if outbound lane state is proved. + let mut messages = Vec::with_capacity(messages_in_the_proof as _); + for nonce in nonces_range { + let message_key = MessageKey { lane_id: lane, nonce }; + let message_payload = parser.read_and_decode_message_payload(&message_key)?; + messages.push(Message { key: message_key, payload: message_payload }); + } + + // Now let's check if proof contains outbound lane state proof. It is optional, so + // we simply ignore `read_value` errors and missing value. + let proved_lane_messages = ProvedLaneMessages { + lane_state: parser.read_and_decode_outbound_lane_data(&lane)?, + messages, + }; + + // Now we may actually check if the proof is empty or not. + if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() { + return Err(VerificationError::EmptyMessageProof) + } + + // Check that the `VecDb` doesn't have any untouched keys. + parser.storage.ensure_no_unused_keys().map_err(VerificationError::VecDb)?; + + // We only support single lane messages in this generated_schema + let mut proved_messages = ProvedMessages::new(); + proved_messages.insert(lane, proved_lane_messages); + + Ok(proved_messages) +} + +/// Verify proof of This -> Bridged chain messages delivery. +pub fn verify_messages_delivery_proof, I: 'static>( + proof: FromBridgedChainMessagesDeliveryProof>>, +) -> Result, VerificationError> { + let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof; + let mut storage = + T::BridgedHeaderChain::verify_vec_db_storage(bridged_header_hash, storage_proof) + .map_err(VerificationError::HeaderChain)?; + // Messages delivery proof is just proof of single storage key read => any error + // is fatal. + let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key( + T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &lane, + ); + let inbound_lane_data = storage + .get_and_decode_mandatory(&storage_inbound_lane_data_key) + .map_err(VerificationError::InboundLaneStorage)?; + + // check that the storage proof doesn't have any untouched trie nodes + storage.ensure_no_unused_keys().map_err(VerificationError::VecDb)?; + + Ok((lane, inbound_lane_data)) +} + +struct StorageAdapter { + storage: TrustedVecDb, + _dummy: sp_std::marker::PhantomData<(T, I)>, +} + +impl, I: 'static> StorageAdapter { + fn read_and_decode_outbound_lane_data( + &mut self, + lane_id: &LaneId, + ) -> Result, VerificationError> { + let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key( + T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + lane_id, + ); + + self.storage + .get_and_decode_optional(&storage_outbound_lane_data_key) + .map_err(VerificationError::OutboundLaneStorage) + } + + fn read_and_decode_message_payload( + &mut self, + message_key: &MessageKey, + ) -> Result { + let storage_message_key = bp_messages::storage_keys::message_key( + T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &message_key.lane_id, + message_key.nonce, + ); + self.storage + .get_and_decode_mandatory(&storage_message_key) + .map_err(VerificationError::MessageStorage) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{ + messages_generation::{ + encode_all_messages, encode_lane_data, generate_dummy_message, + prepare_messages_storage_proof, + }, + mock::*, + }; + + use bp_header_chain::{HeaderChainError, StoredHeaderDataBuilder}; + use bp_runtime::{HeaderId, VecDbError}; + use codec::Encode; + use sp_runtime::traits::Header; + + fn using_messages_proof( + nonces_end: MessageNonce, + outbound_lane_data: Option, + encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option>, + encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, + add_duplicate_key: bool, + add_unused_key: bool, + test: impl Fn(FromBridgedChainMessagesProof) -> R, + ) -> R { + let (state_root, storage) = prepare_messages_storage_proof::( + TEST_LANE_ID, + 1..=nonces_end, + outbound_lane_data, + bp_runtime::StorageProofSize::Minimal(0), + generate_dummy_message, + encode_message, + encode_outbound_lane_data, + add_duplicate_key, + add_unused_key, + ); + + sp_io::TestExternalities::new(Default::default()).execute_with(move || { + let bridged_header = BridgedChainHeader::new( + 0, + Default::default(), + state_root, + Default::default(), + Default::default(), + ); + let bridged_header_hash = bridged_header.hash(); + + pallet_bridge_grandpa::BestFinalized::::put(HeaderId( + 0, + bridged_header_hash, + )); + pallet_bridge_grandpa::ImportedHeaders::::insert( + bridged_header_hash, + bridged_header.build(), + ); + test(FromBridgedChainMessagesProof { + bridged_header_hash, + storage, + lane: TEST_LANE_ID, + nonces_start: 1, + nonces_end, + }) + }) + } + + #[test] + fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() { + assert_eq!( + using_messages_proof( + 10, + None, + encode_all_messages, + encode_lane_data, + false, + false, + |proof| { verify_messages_proof::(proof, 5) } + ), + Err(VerificationError::MessagesCountMismatch), + ); + } + + #[test] + fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() { + assert_eq!( + using_messages_proof( + 10, + None, + encode_all_messages, + encode_lane_data, + false, + false, + |proof| { verify_messages_proof::(proof, 15) } + ), + Err(VerificationError::MessagesCountMismatch), + ); + } + + #[test] + fn message_proof_is_rejected_if_header_is_missing_from_the_chain() { + assert_eq!( + using_messages_proof( + 10, + None, + encode_all_messages, + encode_lane_data, + false, + false, + |proof| { + let bridged_header_hash = + pallet_bridge_grandpa::BestFinalized::::get().unwrap().1; + pallet_bridge_grandpa::ImportedHeaders::::remove( + bridged_header_hash, + ); + verify_messages_proof::(proof, 10) + } + ), + Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)), + ); + } + + #[test] + fn message_proof_is_rejected_if_header_state_root_mismatches() { + assert_eq!( + using_messages_proof( + 10, + None, + encode_all_messages, + encode_lane_data, + false, + false, + |proof| { + let bridged_header_hash = + pallet_bridge_grandpa::BestFinalized::::get().unwrap().1; + pallet_bridge_grandpa::ImportedHeaders::::insert( + bridged_header_hash, + BridgedChainHeader::new( + 0, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ) + .build(), + ); + verify_messages_proof::(proof, 10) + } + ), + Err(VerificationError::HeaderChain(HeaderChainError::VecDb(VecDbError::InvalidProof))), + ); + } + + #[test] + fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() { + assert_eq!( + using_messages_proof( + 10, + None, + encode_all_messages, + encode_lane_data, + true, + false, + |proof| { verify_messages_proof::(proof, 10) }, + ), + Err(VerificationError::HeaderChain(HeaderChainError::VecDb(VecDbError::InvalidProof))), + ); + } + + #[test] + fn message_proof_is_rejected_if_it_has_unused_trie_nodes() { + assert_eq!( + using_messages_proof( + 10, + None, + encode_all_messages, + encode_lane_data, + false, + true, + |proof| { verify_messages_proof::(proof, 10) }, + ), + Err(VerificationError::VecDb(VecDbError::UnusedKey)), + ); + } + + #[test] + fn message_proof_is_rejected_if_required_message_is_missing() { + matches!( + using_messages_proof( + 10, + None, + |n, m| if n != 5 { Some(m.encode()) } else { None }, + encode_lane_data, + false, + false, + |proof| verify_messages_proof::(proof, 10) + ), + Err(VerificationError::MessageStorage(VecDbError::EmptyVal)), + ); + } + + #[test] + fn message_proof_is_rejected_if_message_decode_fails() { + matches!( + using_messages_proof( + 10, + None, + |n, m| { + let mut m = m.encode(); + if n == 5 { + m = vec![42] + } + Some(m) + }, + encode_lane_data, + false, + false, + |proof| verify_messages_proof::(proof, 10), + ), + Err(VerificationError::MessageStorage(VecDbError::DecodeError)), + ); + } + + #[test] + fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() { + matches!( + using_messages_proof( + 10, + Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + encode_all_messages, + |d| { + let mut d = d.encode(); + d.truncate(1); + d + }, + false, + false, + |proof| verify_messages_proof::(proof, 10), + ), + Err(VerificationError::OutboundLaneStorage(VecDbError::DecodeError)), + ); + } + + #[test] + fn message_proof_is_rejected_if_it_is_empty() { + assert_eq!( + using_messages_proof( + 0, + None, + encode_all_messages, + encode_lane_data, + false, + false, + |proof| { verify_messages_proof::(proof, 0) }, + ), + Err(VerificationError::EmptyMessageProof), + ); + } + + #[test] + fn non_empty_message_proof_without_messages_is_accepted() { + assert_eq!( + using_messages_proof( + 0, + Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + encode_all_messages, + encode_lane_data, + false, + false, + |proof| verify_messages_proof::(proof, 0), + ), + Ok(vec![( + TEST_LANE_ID, + ProvedLaneMessages { + lane_state: Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + messages: Vec::new(), + }, + )] + .into_iter() + .collect()), + ); + } + + #[test] + fn non_empty_message_proof_is_accepted() { + assert_eq!( + using_messages_proof( + 1, + Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + encode_all_messages, + encode_lane_data, + false, + false, + |proof| verify_messages_proof::(proof, 1), + ), + Ok(vec![( + TEST_LANE_ID, + ProvedLaneMessages { + lane_state: Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + messages: vec![Message { + key: MessageKey { lane_id: TEST_LANE_ID, nonce: 1 }, + payload: vec![42], + }], + }, + )] + .into_iter() + .collect()), + ); + } + + #[test] + fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() { + assert_eq!( + using_messages_proof( + 1, + None, + encode_all_messages, + encode_lane_data, + false, + false, + |mut proof| { + proof.nonces_end = u64::MAX; + verify_messages_proof::(proof, u32::MAX) + }, + ), + Err(VerificationError::MessagesCountMismatch), + ); + } +} diff --git a/bridges/bin/runtime-common/src/messages_generation.rs b/bridges/modules/messages/src/tests/messages_generation.rs similarity index 58% rename from bridges/bin/runtime-common/src/messages_generation.rs rename to bridges/modules/messages/src/tests/messages_generation.rs index 21b7a6fa74905..6d0969004f7e2 100644 --- a/bridges/bin/runtime-common/src/messages_generation.rs +++ b/bridges/modules/messages/src/tests/messages_generation.rs @@ -15,25 +15,29 @@ // along with Parity Bridges Common. If not, see . //! Helpers for generating message storage proofs, that are used by tests and by benchmarks. - -use crate::messages::{AccountIdOf, BridgedChain, HashOf, HasherOf, MessageBridge, ThisChain}; +// TODO: remove me in https://github.com/paritytech/parity-bridges-common/issues/1666 +#![allow(dead_code)] use bp_messages::{ - storage_keys, InboundLaneData, LaneId, MessageKey, MessageNonce, MessagePayload, - OutboundLaneData, + storage_keys, ChainWithMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, + MessagePayload, OutboundLaneData, }; use bp_runtime::{ - record_all_trie_keys, Chain, RangeInclusiveExt, RawStorageProof, StorageProofSize, - UnderlyingChainOf, UntrustedVecDb, + grow_trie_leaf_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf, + RangeInclusiveExt, StorageProofSize, UntrustedVecDb, }; use codec::Encode; use frame_support::sp_runtime::StateVersion; use sp_std::{ops::RangeInclusive, prelude::*}; use sp_trie::{ - trie_types::TrieDBMutBuilderV1, LayoutV0, LayoutV1, MemoryDB, StorageProof, TrieConfiguration, - TrieDBMutBuilder, TrieMut, + LayoutV0, LayoutV1, MemoryDB, StorageProof, TrieConfiguration, TrieDBMutBuilder, TrieMut, }; +/// Dummy message generation function. +pub fn generate_dummy_message(_: MessageNonce) -> MessagePayload { + vec![42] +} + /// Simple and correct message data encode function. pub fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option> { Some(m.encode()) @@ -48,46 +52,74 @@ pub fn encode_lane_data(d: &OutboundLaneData) -> Vec { /// /// Returns state trie root and nodes with prepared messages. #[allow(clippy::too_many_arguments)] -pub fn prepare_messages_storage_proof( +pub fn prepare_messages_storage_proof( lane: LaneId, message_nonces: RangeInclusive, outbound_lane_data: Option, size: StorageProofSize, - message_payload: MessagePayload, + generate_message: impl Fn(MessageNonce) -> MessagePayload, encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option>, encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, add_duplicate_key: bool, add_unused_key: bool, -) -> (HashOf>, UntrustedVecDb) +) -> (HashOf, UntrustedVecDb) +where + HashOf: Copy + Default, +{ + match BridgedChain::STATE_VERSION { + StateVersion::V0 => do_prepare_messages_storage_proof::< + BridgedChain, + ThisChain, + LayoutV0>, + >( + lane, + message_nonces, + outbound_lane_data, + size, + generate_message, + encode_message, + encode_outbound_lane_data, + add_duplicate_key, + add_unused_key, + ), + StateVersion::V1 => do_prepare_messages_storage_proof::< + BridgedChain, + ThisChain, + LayoutV1>, + >( + lane, + message_nonces, + outbound_lane_data, + size, + generate_message, + encode_message, + encode_outbound_lane_data, + add_duplicate_key, + add_unused_key, + ), + } +} + +/// Prepare storage proof that proves given messages delivery. +pub fn prepare_message_delivery_storage_proof( + lane: LaneId, + inbound_lane_data: InboundLaneData>, + size: StorageProofSize, +) -> (HashOf, UntrustedVecDb) where - B: MessageBridge, - HashOf>: Copy + Default, + HashOf: Copy + Default, { - match >>::STATE_VERSION { - StateVersion::V0 => - do_prepare_messages_storage_proof::>>>( - lane, - message_nonces, - outbound_lane_data, - size, - message_payload, - encode_message, - encode_outbound_lane_data, - add_duplicate_key, - add_unused_key, - ), - StateVersion::V1 => - do_prepare_messages_storage_proof::>>>( - lane, - message_nonces, - outbound_lane_data, - size, - message_payload, - encode_message, - encode_outbound_lane_data, - add_duplicate_key, - add_unused_key, - ), + match BridgedChain::STATE_VERSION { + StateVersion::V0 => do_prepare_message_delivery_storage_proof::< + BridgedChain, + ThisChain, + LayoutV0>, + >(lane, inbound_lane_data, size), + StateVersion::V1 => do_prepare_message_delivery_storage_proof::< + BridgedChain, + ThisChain, + LayoutV1>, + >(lane, inbound_lane_data, size), } } @@ -95,21 +127,20 @@ where /// /// Returns state trie root and nodes with prepared messages. #[allow(clippy::too_many_arguments)] -pub fn do_prepare_messages_storage_proof( +fn do_prepare_messages_storage_proof( lane: LaneId, message_nonces: RangeInclusive, outbound_lane_data: Option, size: StorageProofSize, - message_payload: MessagePayload, + generate_message: impl Fn(MessageNonce) -> MessagePayload, encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option>, encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, add_duplicate_key: bool, add_unused_key: bool, -) -> (HashOf>, UntrustedVecDb) +) -> (HashOf, UntrustedVecDb) where - B: MessageBridge, - L: TrieConfiguration>>, - HashOf>: Copy + Default, + L: TrieConfiguration>, + HashOf: Copy + Default, { // prepare Bridged chain storage with messages and (optionally) outbound lane state let message_count = message_nonces.saturating_len(); @@ -122,7 +153,7 @@ where // insert messages for (i, nonce) in message_nonces.into_iter().enumerate() { let message_key = MessageKey { lane_id: lane, nonce }; - let message_payload = match encode_message(nonce, &message_payload) { + let message_payload = match encode_message(nonce, &generate_message(nonce)) { Some(message_payload) => if i == 0 { grow_trie_leaf_value(message_payload, size) @@ -132,7 +163,7 @@ where None => continue, }; let storage_key = storage_keys::message_key( - B::BRIDGED_MESSAGES_PALLET_NAME, + ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, &message_key.lane_id, message_key.nonce, ) @@ -146,8 +177,11 @@ where // insert outbound lane state if let Some(outbound_lane_data) = outbound_lane_data.as_ref().map(encode_outbound_lane_data) { - let storage_key = - storage_keys::outbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0; + let storage_key = storage_keys::outbound_lane_data_key( + ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &lane, + ) + .0; trie.insert(&storage_key, &outbound_lane_data) .map_err(|_| "TrieMut::insert has failed") .expect("TrieMut::insert should not fail in benchmarks"); @@ -172,7 +206,7 @@ where let read_proof = record_all_trie_keys::(&mdb, &root) .map_err(|_| "record_all_trie_keys has failed") .expect("record_all_trie_keys should not fail in benchmarks"); - let storage = UntrustedVecDb::try_new::>>( + let storage = UntrustedVecDb::try_new::>( StorageProof::new(read_proof), root, storage_keys, @@ -183,22 +217,23 @@ where /// Prepare storage proof of given messages delivery. /// -/// Returns state trie root and nodes with prepared messages. -pub fn prepare_message_delivery_storage_proof( +/// Returns state trie root and partial storage trie. +fn do_prepare_message_delivery_storage_proof( lane: LaneId, - inbound_lane_data: InboundLaneData>>, + inbound_lane_data: InboundLaneData>, size: StorageProofSize, -) -> (HashOf>, RawStorageProof) +) -> (HashOf, UntrustedVecDb) where - B: MessageBridge, + L: TrieConfiguration>, + HashOf: Copy + Default, { // prepare Bridged chain storage with inbound lane state - let storage_key = storage_keys::inbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0; + let storage_key = + storage_keys::inbound_lane_data_key(ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, &lane).0; let mut root = Default::default(); let mut mdb = MemoryDB::default(); { - let mut trie = - TrieDBMutBuilderV1::>>::new(&mut mdb, &mut root).build(); + let mut trie = TrieDBMutBuilder::::new(&mut mdb, &mut root).build(); let inbound_lane_data = grow_trie_leaf_value(inbound_lane_data.encode(), size); trie.insert(&storage_key, &inbound_lane_data) .map_err(|_| "TrieMut::insert has failed") @@ -206,21 +241,14 @@ where } // generate storage proof to be delivered to This chain - let storage_proof = record_all_trie_keys::>>, _>(&mdb, &root) + let read_proof = record_all_trie_keys::(&mdb, &root) .map_err(|_| "record_all_trie_keys has failed") .expect("record_all_trie_keys should not fail in benchmarks"); - - (root, storage_proof) -} - -/// Add extra data to the trie leaf value so that it'll be of given size. -pub fn grow_trie_leaf_value(mut value: Vec, size: StorageProofSize) -> Vec { - match size { - StorageProofSize::Minimal(_) => (), - StorageProofSize::HasLargeLeaf(size) if size as usize > value.len() => { - value.extend(sp_std::iter::repeat(42u8).take(size as usize - value.len())); - }, - StorageProofSize::HasLargeLeaf(_) => (), - } - value + let storage = UntrustedVecDb::try_new::>( + StorageProof::new(read_proof), + root, + vec![storage_key], + ) + .unwrap(); + (root, storage) } diff --git a/bridges/modules/messages/src/mock.rs b/bridges/modules/messages/src/tests/mock.rs similarity index 63% rename from bridges/modules/messages/src/mock.rs rename to bridges/modules/messages/src/tests/mock.rs index ec63f15b94b52..53075d33ee2ce 100644 --- a/bridges/modules/messages/src/mock.rs +++ b/bridges/modules/messages/src/tests/mock.rs @@ -17,30 +17,41 @@ // From construct_runtime macro #![allow(clippy::from_over_into)] -use crate::{Config, StoredMessagePayload}; +use crate::{ + tests::messages_generation::{ + encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, + prepare_messages_storage_proof, + }, + Config, StoredMessagePayload, +}; +use bp_header_chain::{ChainWithGrandpa, StoredHeaderData}; use bp_messages::{ calc_relayers_rewards, - source_chain::{DeliveryConfirmationPayments, OnMessagesDelivered, TargetHeaderChain}, + source_chain::{ + DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered, + }, target_chain::{ - DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch, - ProvedLaneMessages, ProvedMessages, SourceHeaderChain, + DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof, + MessageDispatch, }, - DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, - UnrewardedRelayer, UnrewardedRelayersState, VerificationError, + ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, + MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, }; -use bp_runtime::{messages::MessageDispatchResult, Size}; +use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Size, StorageProofSize}; use codec::{Decode, Encode}; use frame_support::{ derive_impl, parameter_types, weights::{constants::RocksDbWeight, Weight}, }; use scale_info::TypeInfo; -use sp_runtime::BuildStorage; -use std::{ - collections::{BTreeMap, VecDeque}, - ops::RangeInclusive, +use sp_core::H256; +use sp_runtime::{ + testing::Header as SubstrateHeader, + traits::{BlakeTwo256, ConstU32}, + BuildStorage, StateVersion, }; +use std::{collections::VecDeque, ops::RangeInclusive}; pub type AccountId = u64; pub type Balance = u64; @@ -62,6 +73,77 @@ pub type TestMessageFee = u64; pub type TestRelayer = u64; pub type TestDispatchLevelResult = (); +pub struct ThisChain; + +impl Chain for ThisChain { + const ID: ChainId = *b"ttch"; + + type BlockNumber = u64; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = SubstrateHeader; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = u64; + type Signature = sp_runtime::MultiSignature; + const STATE_VERSION: StateVersion = StateVersion::V1; + + fn max_extrinsic_size() -> u32 { + u32::MAX + } + + fn max_extrinsic_weight() -> Weight { + Weight::MAX + } +} + +impl ChainWithMessages for ThisChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages"; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128; +} + +pub struct BridgedChain; + +pub type BridgedHeaderHash = H256; +pub type BridgedChainHeader = SubstrateHeader; + +impl Chain for BridgedChain { + const ID: ChainId = *b"tbch"; + + type BlockNumber = u64; + type Hash = BridgedHeaderHash; + type Hasher = BlakeTwo256; + type Header = BridgedChainHeader; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = u64; + type Signature = sp_runtime::MultiSignature; + const STATE_VERSION: StateVersion = StateVersion::V1; + + fn max_extrinsic_size() -> u32 { + 4096 + } + + fn max_extrinsic_weight() -> Weight { + Weight::MAX + } +} + +impl ChainWithGrandpa for BridgedChain { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "WithBridgedChainBridgeGrandpa"; + const MAX_AUTHORITIES_COUNT: u32 = 16; + const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 4; + const MAX_MANDATORY_HEADER_SIZE: u32 = 4096; + const AVERAGE_HEADER_SIZE: u32 = 4096; +} + +impl ChainWithMessages for BridgedChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages"; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128; +} + type Block = frame_system::mocking::MockBlock; use crate as pallet_bridge_messages; @@ -71,6 +153,7 @@ frame_support::construct_runtime! { { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Event}, + BridgedChainGrandpa: pallet_bridge_grandpa::{Pallet, Call, Event}, Messages: pallet_bridge_messages::{Pallet, Call, Event}, } } @@ -90,10 +173,17 @@ impl pallet_balances::Config for TestRuntime { type AccountStore = System; } +impl pallet_bridge_grandpa::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type BridgedChain = BridgedChain; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<1_024>; + type HeadersToKeep = ConstU32<8>; + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; +} + parameter_types! { pub const MaxMessagesToPruneAtOnce: u64 = 10; - pub const MaxUnrewardedRelayerEntriesAtInboundLane: u64 = 16; - pub const MaxUnconfirmedMessagesAtInboundLane: u64 = 128; pub const TestBridgedChainId: bp_runtime::ChainId = *b"test"; pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID, TEST_LANE_ID_2]; } @@ -104,24 +194,23 @@ pub type TestWeightInfo = (); impl Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type WeightInfo = TestWeightInfo; + + type ThisChain = ThisChain; + type BridgedChain = BridgedChain; + type BridgedHeaderChain = BridgedChainGrandpa; + type ActiveOutboundLanes = ActiveOutboundLanes; - type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; - type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; - type MaximalOutboundPayloadSize = frame_support::traits::ConstU32; type OutboundPayload = TestPayload; type InboundPayload = TestPayload; type InboundRelayer = TestRelayer; type DeliveryPayments = TestDeliveryPayments; - type TargetHeaderChain = TestTargetHeaderChain; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; type OnMessagesDelivered = TestOnMessagesDelivered; - type SourceHeaderChain = TestSourceHeaderChain; type MessageDispatch = TestMessageDispatch; - type BridgedChainId = TestBridgedChainId; } #[cfg(feature = "runtime-benchmarks")] @@ -132,29 +221,26 @@ impl crate::benchmarking::Config<()> for TestRuntime { fn prepare_message_proof( params: crate::benchmarking::MessageProofParams, - ) -> (TestMessagesProof, Weight) { - // in mock run we only care about benchmarks correctness, not the benchmark results - // => ignore size related arguments - let (messages, total_dispatch_weight) = - params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).fold( - (Vec::new(), Weight::zero()), - |(mut messages, total_dispatch_weight), message| { - let weight = REGULAR_PAYLOAD.declared_weight; - messages.push(message); - (messages, total_dispatch_weight.saturating_add(weight)) - }, - ); - let mut proof: TestMessagesProof = Ok(messages).into(); - proof.result.as_mut().unwrap().get_mut(0).unwrap().1.lane_state = params.outbound_lane_data; - (proof, total_dispatch_weight) + ) -> (FromBridgedChainMessagesProof, Weight) { + use bp_runtime::RangeInclusiveExt; + + let dispatch_weight = + REGULAR_PAYLOAD.declared_weight * params.message_nonces.checked_len().unwrap_or(0); + ( + prepare_messages_proof( + params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).collect(), + params.outbound_lane_data, + ), + dispatch_weight, + ) } fn prepare_message_delivery_proof( params: crate::benchmarking::MessageDeliveryProofParams, - ) -> TestMessagesDeliveryProof { + ) -> FromBridgedChainMessagesDeliveryProof { // in mock run we only care about benchmarks correctness, not the benchmark results // => ignore size related arguments - TestMessagesDeliveryProof(Ok((params.lane, params.inbound_lane_data))) + prepare_messages_delivery_proof(params.lane, params.inbound_lane_data) } fn is_relayer_rewarded(_relayer: &AccountId) -> bool { @@ -168,9 +254,6 @@ impl Size for TestPayload { } } -/// Maximal outbound payload size. -pub const MAX_OUTBOUND_PAYLOAD_SIZE: u32 = 4096; - /// Account that has balance to use in tests. pub const ENDOWED_ACCOUNT: AccountId = 0xDEAD; @@ -183,9 +266,6 @@ pub const TEST_RELAYER_B: AccountId = 101; /// Account id of additional test relayer - C. pub const TEST_RELAYER_C: AccountId = 102; -/// Error that is returned by all test implementations. -pub const TEST_ERROR: &str = "Test error"; - /// Lane that we're using in tests. pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 1]); @@ -198,71 +278,6 @@ pub const TEST_LANE_ID_3: LaneId = LaneId([0, 0, 0, 3]); /// Regular message payload. pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50); -/// Payload that is rejected by `TestTargetHeaderChain`. -pub const PAYLOAD_REJECTED_BY_TARGET_CHAIN: TestPayload = message_payload(1, 50); - -/// Vec of proved messages, grouped by lane. -pub type MessagesByLaneVec = Vec<(LaneId, ProvedLaneMessages)>; - -/// Test messages proof. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] -pub struct TestMessagesProof { - pub result: Result, -} - -impl Size for TestMessagesProof { - fn size(&self) -> u32 { - 0 - } -} - -impl From, ()>> for TestMessagesProof { - fn from(result: Result, ()>) -> Self { - Self { - result: result.map(|messages| { - let mut messages_by_lane: BTreeMap> = - BTreeMap::new(); - for message in messages { - messages_by_lane.entry(message.key.lane_id).or_default().messages.push(message); - } - messages_by_lane.into_iter().collect() - }), - } - } -} - -/// Messages delivery proof used in tests. -#[derive(Debug, Encode, Decode, Eq, Clone, PartialEq, TypeInfo)] -pub struct TestMessagesDeliveryProof(pub Result<(LaneId, InboundLaneData), ()>); - -impl Size for TestMessagesDeliveryProof { - fn size(&self) -> u32 { - 0 - } -} - -/// Target header chain that is used in tests. -#[derive(Debug, Default)] -pub struct TestTargetHeaderChain; - -impl TargetHeaderChain for TestTargetHeaderChain { - type MessagesDeliveryProof = TestMessagesDeliveryProof; - - fn verify_message(payload: &TestPayload) -> Result<(), VerificationError> { - if *payload == PAYLOAD_REJECTED_BY_TARGET_CHAIN { - Err(VerificationError::Other(TEST_ERROR)) - } else { - Ok(()) - } - } - - fn verify_messages_delivery_proof( - proof: Self::MessagesDeliveryProof, - ) -> Result<(LaneId, InboundLaneData), VerificationError> { - proof.0.map_err(|_| VerificationError::Other(TEST_ERROR)) - } -} - /// Reward payments at the target chain during delivery transaction. #[derive(Debug, Default)] pub struct TestDeliveryPayments; @@ -323,24 +338,6 @@ impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayment } } -/// Source header chain that is used in tests. -#[derive(Debug)] -pub struct TestSourceHeaderChain; - -impl SourceHeaderChain for TestSourceHeaderChain { - type MessagesProof = TestMessagesProof; - - fn verify_messages_proof( - proof: Self::MessagesProof, - _messages_count: u32, - ) -> Result, VerificationError> { - proof - .result - .map(|proof| proof.into_iter().collect()) - .map_err(|_| VerificationError::Other(TEST_ERROR)) - } -} - /// Test message dispatcher. #[derive(Debug)] pub struct TestMessageDispatch; @@ -459,3 +456,73 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub fn run_test(test: impl FnOnce() -> T) -> T { new_test_ext().execute_with(test) } + +/// Prepare valid storage proof for given messages and insert appropriate header to the +/// bridged header chain. +/// +/// Since this function changes the runtime storage, you can't "inline" it in the +/// `asset_noop` macro calls. +pub fn prepare_messages_proof( + messages: Vec, + outbound_lane_data: Option, +) -> FromBridgedChainMessagesProof { + // first - let's generate storage proof + let lane = messages.first().unwrap().key.lane_id; + let nonces_start = messages.first().unwrap().key.nonce; + let nonces_end = messages.last().unwrap().key.nonce; + let (storage_root, storage) = prepare_messages_storage_proof::( + TEST_LANE_ID, + nonces_start..=nonces_end, + outbound_lane_data, + StorageProofSize::Minimal(0), + |nonce| messages[(nonce - nonces_start) as usize].payload.clone(), + encode_all_messages, + encode_lane_data, + false, + false, + ); + + // let's now insert bridged chain header into the storage + let bridged_header_hash = Default::default(); + pallet_bridge_grandpa::ImportedHeaders::::insert( + bridged_header_hash, + StoredHeaderData { number: 0, state_root: storage_root }, + ); + + FromBridgedChainMessagesProof:: { + bridged_header_hash, + storage, + lane, + nonces_start, + nonces_end, + } +} + +/// Prepare valid storage proof for given messages and insert appropriate header to the +/// bridged header chain. +/// +/// Since this function changes the runtime storage, you can't "inline" it in the +/// `asset_noop` macro calls. +pub fn prepare_messages_delivery_proof( + lane: LaneId, + inbound_lane_data: InboundLaneData, +) -> FromBridgedChainMessagesDeliveryProof { + // first - let's generate storage proof + let (storage_root, storage_proof) = prepare_message_delivery_storage_proof::< + BridgedChain, + ThisChain, + >(lane, inbound_lane_data, StorageProofSize::Minimal(0)); + + // let's now insert bridged chain header into the storage + let bridged_header_hash = Default::default(); + pallet_bridge_grandpa::ImportedHeaders::::insert( + bridged_header_hash, + StoredHeaderData { number: 0, state_root: storage_root }, + ); + + FromBridgedChainMessagesDeliveryProof:: { + bridged_header_hash, + storage_proof, + lane, + } +} diff --git a/bridges/modules/messages/src/tests/mod.rs b/bridges/modules/messages/src/tests/mod.rs new file mode 100644 index 0000000000000..c3bde5fc27584 --- /dev/null +++ b/bridges/modules/messages/src/tests/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tests and test helpers for messages pallet. + +#![cfg(any(feature = "test-helpers", test))] + +#[cfg(test)] +pub(crate) mod mock; +#[cfg(test)] +mod pallet_tests; + +pub mod messages_generation; diff --git a/bridges/modules/messages/src/tests/pallet_tests.rs b/bridges/modules/messages/src/tests/pallet_tests.rs new file mode 100644 index 0000000000000..be52d12cc3909 --- /dev/null +++ b/bridges/modules/messages/src/tests/pallet_tests.rs @@ -0,0 +1,1082 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Pallet-level tests. + +use crate::{ + outbound_lane, + outbound_lane::ReceptionConfirmationError, + tests::mock::{self, RuntimeEvent as TestEvent, *}, + weights_ext::WeightInfoExt, + Call, Config, Error, Event, InboundLanes, MaybeOutboundLanesCount, OutboundLanes, + OutboundMessages, Pallet, PalletOperatingMode, PalletOwner, RuntimeInboundLaneStorage, + StoredInboundLaneData, +}; + +use bp_messages::{ + source_chain::{FromBridgedChainMessagesDeliveryProof, MessagesBridge}, + target_chain::FromBridgedChainMessagesProof, + BridgeMessagesCall, ChainWithMessages, DeliveredMessages, InboundLaneData, + InboundMessageDetails, LaneId, MessageKey, MessageNonce, MessagesOperatingMode, + OutboundLaneData, OutboundMessageDetails, UnrewardedRelayer, UnrewardedRelayersState, + VerificationError, +}; +use bp_runtime::{BasicOperatingMode, PreComputedSize, RangeInclusiveExt, Size}; +use bp_test_utils::generate_owned_bridge_module_tests; +use codec::Encode; +use frame_support::{ + assert_noop, assert_ok, + dispatch::Pays, + storage::generator::{StorageMap, StorageValue}, + traits::Hooks, + weights::Weight, +}; +use frame_system::{EventRecord, Pallet as System, Phase}; +use sp_core::Get; +use sp_runtime::DispatchError; + +fn get_ready_for_events() { + System::::set_block_number(1); + System::::reset_events(); +} + +fn send_regular_message(lane_id: LaneId) { + get_ready_for_events(); + + let outbound_lane = outbound_lane::(lane_id); + let message_nonce = outbound_lane.data().latest_generated_nonce + 1; + let prev_enqueued_messages = outbound_lane.data().queued_messages().saturating_len(); + let valid_message = Pallet::::validate_message(lane_id, ®ULAR_PAYLOAD) + .expect("validate_message has failed"); + let artifacts = Pallet::::send_message(valid_message); + assert_eq!(artifacts.enqueued_messages, prev_enqueued_messages + 1); + + // check event with assigned nonce + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Messages(Event::MessageAccepted { lane_id, nonce: message_nonce }), + topics: vec![], + }], + ); +} + +fn receive_messages_delivery_proof() { + System::::set_block_number(1); + System::::reset_events(); + + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![UnrewardedRelayer { + relayer: 0, + messages: DeliveredMessages::new(1), + }] + .into(), + }, + ), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + )); + + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Messages(Event::MessagesDelivered { + lane_id: TEST_LANE_ID, + messages: DeliveredMessages::new(1), + }), + topics: vec![], + }], + ); +} + +#[test] +fn pallet_rejects_transactions_if_halted() { + run_test(|| { + // send message first to be able to check that delivery_proof fails later + send_regular_message(TEST_LANE_ID); + + PalletOperatingMode::::put(MessagesOperatingMode::Basic( + BasicOperatingMode::Halted, + )); + + assert_noop!( + Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), + Error::::NotOperatingNormally, + ); + + let messages_proof = prepare_messages_proof(vec![message(2, REGULAR_PAYLOAD)], None); + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + messages_proof, + 1, + REGULAR_PAYLOAD.declared_weight, + ), + Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), + ); + + let delivery_proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(), + }, + ); + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + delivery_proof, + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + ), + Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), + ); + }); +} + +#[test] +fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { + run_test(|| { + // send message first to be able to check that delivery_proof fails later + send_regular_message(TEST_LANE_ID); + + PalletOperatingMode::::put( + MessagesOperatingMode::RejectingOutboundMessages, + ); + + assert_noop!( + Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), + Error::::NotOperatingNormally, + ); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None), + 1, + REGULAR_PAYLOAD.declared_weight, + ),); + + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(), + }, + ), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + )); + }); +} + +#[test] +fn send_message_works() { + run_test(|| { + send_regular_message(TEST_LANE_ID); + }); +} + +#[test] +fn send_message_rejects_too_large_message() { + run_test(|| { + let mut message_payload = message_payload(1, 0); + // the payload isn't simply extra, so it'll definitely overflow + // `max_outbound_payload_size` if we add `max_outbound_payload_size` bytes to extra + let max_outbound_payload_size = BridgedChain::maximal_incoming_message_size(); + message_payload + .extra + .extend_from_slice(&vec![0u8; max_outbound_payload_size as usize]); + assert_noop!( + Pallet::::validate_message(TEST_LANE_ID, &message_payload.clone(),), + Error::::MessageRejectedByPallet(VerificationError::MessageTooLarge), + ); + + // let's check that we're able to send `max_outbound_payload_size` messages + while message_payload.encoded_size() as u32 > max_outbound_payload_size { + message_payload.extra.pop(); + } + assert_eq!(message_payload.encoded_size() as u32, max_outbound_payload_size); + + let valid_message = + Pallet::::validate_message(TEST_LANE_ID, &message_payload) + .expect("validate_message has failed"); + Pallet::::send_message(valid_message); + }) +} + +#[test] +fn receive_messages_proof_works() { + run_test(|| { + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None), + 1, + REGULAR_PAYLOAD.declared_weight, + )); + + assert_eq!(InboundLanes::::get(TEST_LANE_ID).0.last_delivered_nonce(), 1); + + assert!(TestDeliveryPayments::is_reward_paid(1)); + }); +} + +#[test] +fn receive_messages_proof_updates_confirmed_message_nonce() { + run_test(|| { + // say we have received 10 messages && last confirmed message is 8 + InboundLanes::::insert( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 8, + relayers: vec![ + unrewarded_relayer(9, 9, TEST_RELAYER_A), + unrewarded_relayer(10, 10, TEST_RELAYER_B), + ] + .into(), + }, + ); + assert_eq!( + inbound_unrewarded_relayers_state(TEST_LANE_ID), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 1, + total_messages: 2, + last_delivered_nonce: 10, + }, + ); + + // message proof includes outbound lane state with latest confirmed message updated to 9 + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + prepare_messages_proof( + vec![message(11, REGULAR_PAYLOAD)], + Some(OutboundLaneData { latest_received_nonce: 9, ..Default::default() }), + ), + 1, + REGULAR_PAYLOAD.declared_weight, + )); + + assert_eq!( + InboundLanes::::get(TEST_LANE_ID).0, + InboundLaneData { + last_confirmed_nonce: 9, + relayers: vec![ + unrewarded_relayer(10, 10, TEST_RELAYER_B), + unrewarded_relayer(11, 11, TEST_RELAYER_A) + ] + .into(), + }, + ); + assert_eq!( + inbound_unrewarded_relayers_state(TEST_LANE_ID), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 1, + total_messages: 2, + last_delivered_nonce: 11, + }, + ); + }); +} + +#[test] +fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() { + run_test(|| { + let proof = prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None); + let mut declared_weight = REGULAR_PAYLOAD.declared_weight; + *declared_weight.ref_time_mut() -= 1; + + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof, + 1, + declared_weight, + ), + Error::::InsufficientDispatchWeight + ); + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); + }); +} + +#[test] +fn receive_messages_proof_rejects_invalid_proof() { + run_test(|| { + let mut proof = prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None); + proof.nonces_end += 1; + + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof, + 1, + Weight::zero(), + ), + Error::::InvalidMessagesProof, + ); + }); +} + +#[test] +fn receive_messages_proof_rejects_proof_with_too_many_messages() { + run_test(|| { + let proof = prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None); + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof, + u32::MAX, + Weight::zero(), + ), + Error::::TooManyMessagesInTheProof, + ); + }); +} + +#[test] +fn receive_messages_delivery_proof_works() { + run_test(|| { + send_regular_message(TEST_LANE_ID); + receive_messages_delivery_proof(); + + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 1,); + }); +} + +#[test] +fn receive_messages_delivery_proof_rewards_relayers() { + run_test(|| { + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + + // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A + let single_message_delivery_proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(), + ..Default::default() + }, + ); + let single_message_delivery_proof_size = single_message_delivery_proof.size(); + let result = Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + single_message_delivery_proof, + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + ); + assert_ok!(result); + assert_eq!( + result.unwrap().actual_weight.unwrap(), + TestWeightInfo::receive_messages_delivery_proof_weight( + &PreComputedSize(single_message_delivery_proof_size as _), + &UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }, + ) + ); + assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); + assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); + + // this reports delivery of both message 1 and message 2 => reward is paid only to + // TEST_RELAYER_B + let two_messages_delivery_proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B), + ] + .into(), + ..Default::default() + }, + ); + let two_messages_delivery_proof_size = two_messages_delivery_proof.size(); + let result = Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + two_messages_delivery_proof, + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 1, + total_messages: 2, + last_delivered_nonce: 2, + }, + ); + assert_ok!(result); + // even though the pre-dispatch weight was for two messages, the actual weight is + // for single message only + assert_eq!( + result.unwrap().actual_weight.unwrap(), + TestWeightInfo::receive_messages_delivery_proof_weight( + &PreComputedSize(two_messages_delivery_proof_size as _), + &UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }, + ) + ); + assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1)); + assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1)); + assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0))); + }); +} + +#[test] +fn receive_messages_delivery_proof_rejects_invalid_proof() { + run_test(|| { + let mut proof = prepare_messages_delivery_proof(TEST_LANE_ID, Default::default()); + proof.lane = bp_messages::LaneId([42, 42, 42, 42]); + + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + proof, + Default::default(), + ), + Error::::InvalidMessagesDeliveryProof, + ); + }); +} + +#[test] +fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() { + run_test(|| { + // when number of relayers entries is invalid + let proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B), + ] + .into(), + ..Default::default() + }, + ); + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + proof, + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 2, + last_delivered_nonce: 2, + ..Default::default() + }, + ), + Error::::InvalidUnrewardedRelayersState, + ); + + // when number of messages is invalid + let proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B), + ] + .into(), + ..Default::default() + }, + ); + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + proof, + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + total_messages: 1, + last_delivered_nonce: 2, + ..Default::default() + }, + ), + Error::::InvalidUnrewardedRelayersState, + ); + + // when last delivered nonce is invalid + let proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B), + ] + .into(), + ..Default::default() + }, + ); + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + proof, + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + total_messages: 2, + last_delivered_nonce: 8, + ..Default::default() + }, + ), + Error::::InvalidUnrewardedRelayersState, + ); + }); +} + +#[test] +fn receive_messages_accepts_single_message_with_invalid_payload() { + run_test(|| { + let mut invalid_message = message(1, REGULAR_PAYLOAD); + invalid_message.payload = Vec::new(); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + prepare_messages_proof(vec![invalid_message], None), + 1, + Weight::zero(), /* weight may be zero in this case (all messages are + * improperly encoded) */ + ),); + + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 1,); + }); +} + +#[test] +fn receive_messages_accepts_batch_with_message_with_invalid_payload() { + run_test(|| { + let mut invalid_message = message(2, REGULAR_PAYLOAD); + invalid_message.payload = Vec::new(); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + prepare_messages_proof( + vec![message(1, REGULAR_PAYLOAD), invalid_message, message(3, REGULAR_PAYLOAD),], + None + ), + 3, + REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight, + ),); + + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 3,); + }); +} + +#[test] +fn actual_dispatch_weight_does_not_overlow() { + run_test(|| { + let message1 = message(1, message_payload(0, u64::MAX / 2)); + let message2 = message(2, message_payload(0, u64::MAX / 2)); + let message3 = message(3, message_payload(0, u64::MAX / 2)); + + let proof = prepare_messages_proof(vec![message1, message2, message3], None); + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + // this may cause overflow if source chain storage is invalid + proof, + 3, + Weight::MAX, + ), + Error::::InsufficientDispatchWeight + ); + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); + }); +} + +#[test] +fn ref_time_refund_from_receive_messages_proof_works() { + run_test(|| { + fn submit_with_unspent_weight( + nonce: MessageNonce, + unspent_weight: u64, + ) -> (Weight, Weight) { + let mut payload = REGULAR_PAYLOAD; + *payload.dispatch_result.unspent_weight.ref_time_mut() = unspent_weight; + let proof = prepare_messages_proof(vec![message(nonce, payload)], None); + let messages_count = 1; + let pre_dispatch_weight = + ::WeightInfo::receive_messages_proof_weight( + &proof, + messages_count, + REGULAR_PAYLOAD.declared_weight, + ); + let result = Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof, + messages_count, + REGULAR_PAYLOAD.declared_weight, + ) + .expect("delivery has failed"); + let post_dispatch_weight = + result.actual_weight.expect("receive_messages_proof always returns Some"); + + // message delivery transactions are never free + assert_eq!(result.pays_fee, Pays::Yes); + + (pre_dispatch_weight, post_dispatch_weight) + } + + // when dispatch is returning `unspent_weight < declared_weight` + let (pre, post) = submit_with_unspent_weight(1, 1); + assert_eq!(post.ref_time(), pre.ref_time() - 1); + + // when dispatch is returning `unspent_weight = declared_weight` + let (pre, post) = submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight.ref_time()); + assert_eq!(post.ref_time(), pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time()); + + // when dispatch is returning `unspent_weight > declared_weight` + let (pre, post) = + submit_with_unspent_weight(3, REGULAR_PAYLOAD.declared_weight.ref_time() + 1); + assert_eq!(post.ref_time(), pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time()); + + // when there's no unspent weight + let (pre, post) = submit_with_unspent_weight(4, 0); + assert_eq!(post.ref_time(), pre.ref_time()); + + // when dispatch is returning `unspent_weight < declared_weight` + let (pre, post) = submit_with_unspent_weight(5, 1); + assert_eq!(post.ref_time(), pre.ref_time() - 1); + }); +} + +#[test] +fn proof_size_refund_from_receive_messages_proof_works() { + run_test(|| { + let max_entries = BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize; + + // if there's maximal number of unrewarded relayer entries at the inbound lane, then + // `proof_size` is unchanged in post-dispatch weight + let proof = prepare_messages_proof(vec![message(101, REGULAR_PAYLOAD)], None); + let messages_count = 1; + let pre_dispatch_weight = + ::WeightInfo::receive_messages_proof_weight( + &proof, + messages_count, + REGULAR_PAYLOAD.declared_weight, + ); + InboundLanes::::insert( + TEST_LANE_ID, + StoredInboundLaneData(InboundLaneData { + relayers: vec![ + UnrewardedRelayer { + relayer: 42, + messages: DeliveredMessages { begin: 0, end: 100 } + }; + max_entries + ] + .into(), + last_confirmed_nonce: 0, + }), + ); + let post_dispatch_weight = Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof.clone(), + messages_count, + REGULAR_PAYLOAD.declared_weight, + ) + .unwrap() + .actual_weight + .unwrap(); + assert_eq!(post_dispatch_weight.proof_size(), pre_dispatch_weight.proof_size()); + + // if count of unrewarded relayer entries is less than maximal, then some `proof_size` + // must be refunded + InboundLanes::::insert( + TEST_LANE_ID, + StoredInboundLaneData(InboundLaneData { + relayers: vec![ + UnrewardedRelayer { + relayer: 42, + messages: DeliveredMessages { begin: 0, end: 100 } + }; + max_entries - 1 + ] + .into(), + last_confirmed_nonce: 0, + }), + ); + let post_dispatch_weight = Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof, + messages_count, + REGULAR_PAYLOAD.declared_weight, + ) + .unwrap() + .actual_weight + .unwrap(); + assert!( + post_dispatch_weight.proof_size() < pre_dispatch_weight.proof_size(), + "Expected post-dispatch PoV {} to be less than pre-dispatch PoV {}", + post_dispatch_weight.proof_size(), + pre_dispatch_weight.proof_size(), + ); + }); +} + +#[test] +fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messages_than_expected() +{ + run_test(|| { + // send message first to be able to check that delivery_proof fails later + send_regular_message(TEST_LANE_ID); + + // 1) InboundLaneData declares that the `last_confirmed_nonce` is 1; + // 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` returns + // `last_confirmed_nonce`; + // 3) it means that we're going to confirm delivery of messages 1..=1; + // 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and numer of + // actually confirmed messages is `1`. + let proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { last_confirmed_nonce: 1, relayers: Default::default() }, + ); + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + proof, + UnrewardedRelayersState { last_delivered_nonce: 1, ..Default::default() }, + ), + Error::::ReceptionConfirmation( + ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected + ), + ); + }); +} + +#[test] +fn storage_keys_computed_properly() { + assert_eq!( + PalletOperatingMode::::storage_value_final_key().to_vec(), + bp_messages::storage_keys::operating_mode_key("Messages").0, + ); + + assert_eq!( + OutboundMessages::::storage_map_final_key(MessageKey { + lane_id: TEST_LANE_ID, + nonce: 42 + }), + bp_messages::storage_keys::message_key("Messages", &TEST_LANE_ID, 42).0, + ); + + assert_eq!( + OutboundLanes::::storage_map_final_key(TEST_LANE_ID), + bp_messages::storage_keys::outbound_lane_data_key("Messages", &TEST_LANE_ID).0, + ); + + assert_eq!( + InboundLanes::::storage_map_final_key(TEST_LANE_ID), + bp_messages::storage_keys::inbound_lane_data_key("Messages", &TEST_LANE_ID).0, + ); +} + +#[test] +fn inbound_message_details_works() { + run_test(|| { + assert_eq!( + Pallet::::inbound_message_data( + TEST_LANE_ID, + REGULAR_PAYLOAD.encode(), + OutboundMessageDetails { nonce: 0, dispatch_weight: Weight::zero(), size: 0 }, + ), + InboundMessageDetails { dispatch_weight: REGULAR_PAYLOAD.declared_weight }, + ); + }); +} + +#[test] +fn on_idle_callback_respects_remaining_weight() { + run_test(|| { + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 4, + relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)].into(), + }, + ), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 4, + total_messages: 4, + last_delivered_nonce: 4, + }, + )); + + // all 4 messages may be pruned now + assert_eq!(outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, 4); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); + System::::set_block_number(2); + + // if passed wight is too low to do anything + let dbw = DbWeight::get(); + assert_eq!(Pallet::::on_idle(0, dbw.reads_writes(1, 1)), Weight::zero(),); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); + + // if passed wight is enough to prune single message + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(1, 2)), + dbw.reads_writes(1, 2), + ); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); + + // if passed wight is enough to prune two more messages + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(1, 3)), + dbw.reads_writes(1, 3), + ); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 4); + + // if passed wight is enough to prune many messages + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(100, 100)), + dbw.reads_writes(1, 2), + ); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 5); + }); +} + +#[test] +fn on_idle_callback_is_rotating_lanes_to_prune() { + run_test(|| { + // send + receive confirmation for lane 1 + send_regular_message(TEST_LANE_ID); + receive_messages_delivery_proof(); + // send + receive confirmation for lane 2 + send_regular_message(TEST_LANE_ID_2); + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + prepare_messages_delivery_proof( + TEST_LANE_ID_2, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(), + }, + ), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + )); + + // nothing is pruned yet + assert_eq!(outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, 1); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().latest_received_nonce, + 1 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, + 1 + ); + + // in block#2.on_idle lane messages of lane 1 are pruned + let dbw = DbWeight::get(); + System::::set_block_number(2); + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(100, 100)), + dbw.reads_writes(1, 2), + ); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, + 1 + ); + + // in block#3.on_idle lane messages of lane 2 are pruned + System::::set_block_number(3); + + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(100, 100)), + dbw.reads_writes(1, 2), + ); + assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, + 2 + ); + }); +} + +#[test] +fn outbound_message_from_unconfigured_lane_is_rejected() { + run_test(|| { + assert_noop!( + Pallet::::validate_message(TEST_LANE_ID_3, ®ULAR_PAYLOAD,), + Error::::InactiveOutboundLane, + ); + }); +} + +#[test] +fn test_bridge_messages_call_is_correctly_defined() { + run_test(|| { + let account_id = 1; + let message_proof = prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None); + let message_delivery_proof = prepare_messages_delivery_proof( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![UnrewardedRelayer { + relayer: 0, + messages: DeliveredMessages::new(1), + }] + .into(), + }, + ); + let unrewarded_relayer_state = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + last_delivered_nonce: 1, + ..Default::default() + }; + + let direct_receive_messages_proof_call = Call::::receive_messages_proof { + relayer_id_at_bridged_chain: account_id, + proof: message_proof.clone(), + messages_count: 1, + dispatch_weight: REGULAR_PAYLOAD.declared_weight, + }; + let indirect_receive_messages_proof_call = BridgeMessagesCall::< + AccountId, + FromBridgedChainMessagesProof, + FromBridgedChainMessagesDeliveryProof, + >::receive_messages_proof { + relayer_id_at_bridged_chain: account_id, + proof: message_proof, + messages_count: 1, + dispatch_weight: REGULAR_PAYLOAD.declared_weight, + }; + assert_eq!( + direct_receive_messages_proof_call.encode(), + indirect_receive_messages_proof_call.encode() + ); + + let direct_receive_messages_delivery_proof_call = + Call::::receive_messages_delivery_proof { + proof: message_delivery_proof.clone(), + relayers_state: unrewarded_relayer_state.clone(), + }; + let indirect_receive_messages_delivery_proof_call = BridgeMessagesCall::< + AccountId, + FromBridgedChainMessagesProof, + FromBridgedChainMessagesDeliveryProof, + >::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state: unrewarded_relayer_state, + }; + assert_eq!( + direct_receive_messages_delivery_proof_call.encode(), + indirect_receive_messages_delivery_proof_call.encode() + ); + }); +} + +generate_owned_bridge_module_tests!( + MessagesOperatingMode::Basic(BasicOperatingMode::Normal), + MessagesOperatingMode::Basic(BasicOperatingMode::Halted) +); + +#[test] +fn inbound_storage_extra_proof_size_bytes_works() { + fn relayer_entry() -> UnrewardedRelayer { + UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100 } } + } + + fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { + RuntimeInboundLaneStorage { + lane_id: Default::default(), + cached_data: Some(InboundLaneData { + relayers: vec![relayer_entry(); relayer_entries].into(), + last_confirmed_nonce: 0, + }), + _phantom: Default::default(), + } + } + + let max_entries = BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize; + + // when we have exactly `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` unrewarded relayers + assert_eq!(storage(max_entries).extra_proof_size_bytes(), 0); + + // when we have less than `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` unrewarded relayers + assert_eq!( + storage(max_entries - 1).extra_proof_size_bytes(), + relayer_entry().encode().len() as u64 + ); + assert_eq!( + storage(max_entries - 2).extra_proof_size_bytes(), + 2 * relayer_entry().encode().len() as u64 + ); + + // when we have more than `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` unrewarded relayers + // (shall not happen in practice) + assert_eq!(storage(max_entries + 1).extra_proof_size_bytes(), 0); +} + +#[test] +fn maybe_outbound_lanes_count_returns_correct_value() { + assert_eq!( + MaybeOutboundLanesCount::::get(), + Some(mock::ActiveOutboundLanes::get().len() as u32) + ); +} diff --git a/bridges/modules/messages/src/weights.rs b/bridges/modules/messages/src/weights.rs index 660a6d4aa9e43..5300261209965 100644 --- a/bridges/modules/messages/src/weights.rs +++ b/bridges/modules/messages/src/weights.rs @@ -81,10 +81,10 @@ impl WeightInfo for BridgeWeight { /// 51655, mode: MaxEncodedLen) fn receive_single_message_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `490` + // Measured: `428` // Estimated: `52645` - // Minimum execution time: 34_644 nanoseconds. - Weight::from_parts(36_135_000, 52645) + // Minimum execution time: 32_573 nanoseconds. + Weight::from_parts(35_227_000, 52645) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -490,12 +490,12 @@ impl WeightInfo for () { /// The range of component `n` is `[128, 2048]`. fn receive_single_message_n_bytes_proof_with_dispatch(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `490` + // Measured: `428` // Estimated: `52645` - // Minimum execution time: 75_228 nanoseconds. - Weight::from_parts(62_255_691, 52645) - // Standard Error: 2_005 - .saturating_add(Weight::from_parts(353_141, 0).saturating_mul(n.into())) + // Minimum execution time: 76_504 nanoseconds. + Weight::from_parts(75_331_522, 52645) + // Standard Error: 1_440 + .saturating_add(Weight::from_parts(302_158, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/bridges/modules/messages/src/weights_ext.rs b/bridges/modules/messages/src/weights_ext.rs index 3fc23b3ba7403..cb0bcd3d3da4e 100644 --- a/bridges/modules/messages/src/weights_ext.rs +++ b/bridges/modules/messages/src/weights_ext.rs @@ -450,7 +450,7 @@ impl WeightInfoExt for crate::weights::BridgeWeight #[cfg(test)] mod tests { use super::*; - use crate::{mock::TestRuntime, weights::BridgeWeight}; + use crate::{tests::mock::TestRuntime, weights::BridgeWeight}; #[test] fn ensure_default_weights_are_correct() { diff --git a/bridges/modules/relayers/src/payment_adapter.rs b/bridges/modules/relayers/src/payment_adapter.rs index b2d9c676bddc4..f75c409aca4f3 100644 --- a/bridges/modules/relayers/src/payment_adapter.rs +++ b/bridges/modules/relayers/src/payment_adapter.rs @@ -23,6 +23,7 @@ use bp_messages::{ LaneId, MessageNonce, }; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_runtime::Chain; use frame_support::{sp_runtime::SaturatedConversion, traits::Get}; use sp_arithmetic::traits::{Saturating, Zero}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; @@ -57,7 +58,7 @@ where relayers_rewards, RewardsAccountParams::new( lane_id, - T::BridgedChainId::get(), + T::BridgedChain::ID, RewardsAccountOwner::BridgedChain, ), DeliveryReward::get(), diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index bfbcc4c31a843..879de67d54200 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -20,18 +20,18 @@ use crate as pallet_xcm_bridge_hub; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - LaneId, + ChainWithMessages, LaneId, MessageNonce, }; -use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, UnderlyingChainProvider}; +use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf}; use bridge_runtime_common::{ - messages::{ - source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, - BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages, - }, + // messages::{ + // source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, + // BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages, + // }, messages_xcm_extension::{SenderAndLane, XcmBlobHauler}, }; use codec::Encode; -use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::RuntimeDbWeight}; +use frame_support::{derive_impl, parameter_types, weights::RuntimeDbWeight}; use sp_core::H256; use sp_runtime::{ testing::Header as SubstrateHeader, @@ -85,20 +85,18 @@ impl pallet_bridge_messages::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type WeightInfo = TestMessagesWeights; - type BridgedChainId = (); type ActiveOutboundLanes = ActiveOutboundLanes; - type MaxUnrewardedRelayerEntriesAtInboundLane = (); - type MaxUnconfirmedMessagesAtInboundLane = (); - type MaximalOutboundPayloadSize = ConstU32<2048>; type OutboundPayload = Vec; type InboundPayload = Vec; type InboundRelayer = (); type DeliveryPayments = (); - type TargetHeaderChain = TargetHeaderChainAdapter; type DeliveryConfirmationPayments = (); type OnMessagesDelivered = (); - type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = TestMessageDispatch; + + type ThisChain = ThisUnderlyingChain; + type BridgedChain = BridgedUnderlyingChain; + type BridgedHeaderChain = BridgedHeaderChain; } pub struct TestMessagesWeights; @@ -192,9 +190,9 @@ impl XcmBlobHauler for TestXcmBlobHauler { type UncongestedMessage = (); } -pub struct ThisChain; +pub struct ThisUnderlyingChain; -impl Chain for ThisChain { +impl Chain for ThisUnderlyingChain { const ID: ChainId = *b"tuch"; type BlockNumber = u64; type Hash = H256; @@ -216,12 +214,19 @@ impl Chain for ThisChain { } } -pub struct BridgedChain; +impl ChainWithMessages for ThisUnderlyingChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = ""; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000; +} + +pub struct BridgedUnderlyingChain; pub type BridgedHeaderHash = H256; pub type BridgedChainHeader = SubstrateHeader; -impl Chain for BridgedChain { - const ID: ChainId = *b"tuch"; +impl Chain for BridgedUnderlyingChain { + const ID: ChainId = *b"bgdc"; type BlockNumber = u64; type Hash = BridgedHeaderHash; type Hasher = BlakeTwo256; @@ -242,6 +247,12 @@ impl Chain for BridgedChain { } } +impl ChainWithMessages for BridgedUnderlyingChain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = ""; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000; +} + /// Test message dispatcher. pub struct TestMessageDispatch; @@ -270,42 +281,15 @@ impl MessageDispatch for TestMessageDispatch { } } -pub struct WrappedThisChain; -impl UnderlyingChainProvider for WrappedThisChain { - type Chain = ThisChain; -} -impl ThisChainWithMessages for WrappedThisChain { - type RuntimeOrigin = RuntimeOrigin; -} - -pub struct WrappedBridgedChain; -impl UnderlyingChainProvider for WrappedBridgedChain { - type Chain = BridgedChain; -} -impl BridgedChainWithMessages for WrappedBridgedChain {} - pub struct BridgedHeaderChain; -impl bp_header_chain::HeaderChain for BridgedHeaderChain { +impl bp_header_chain::HeaderChain for BridgedHeaderChain { fn finalized_header_state_root( - _hash: HashOf, - ) -> Option> { + _hash: HashOf, + ) -> Option> { unreachable!() } } -/// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from -/// `BridgedChain`. -#[derive(Debug, PartialEq, Eq)] -pub struct OnThisChainBridge; - -impl MessageBridge for OnThisChainBridge { - const BRIDGED_MESSAGES_PALLET_NAME: &'static str = ""; - - type ThisChain = WrappedThisChain; - type BridgedChain = WrappedBridgedChain; - type BridgedHeaderChain = BridgedHeaderChain; -} - /// Run pallet test. pub fn run_test(test: impl FnOnce() -> T) -> T { sp_io::TestExternalities::new( diff --git a/bridges/primitives/messages/src/lib.rs b/bridges/primitives/messages/src/lib.rs index 6914b42b65960..740e7c1dea09e 100644 --- a/bridges/primitives/messages/src/lib.rs +++ b/bridges/primitives/messages/src/lib.rs @@ -21,8 +21,8 @@ use bp_header_chain::HeaderChainError; use bp_runtime::{ - messages::MessageDispatchResult, BasicOperatingMode, Chain, OperatingMode, RangeInclusiveExt, - StorageProofError, UnderlyingChainOf, UnderlyingChainProvider, VecDbError, + messages::MessageDispatchResult, AccountIdOf, BasicOperatingMode, Chain, HashOf, OperatingMode, + RangeInclusiveExt, UnderlyingChainOf, UnderlyingChainProvider, VecDbError, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::PalletError; @@ -48,11 +48,56 @@ pub trait ChainWithMessages: Chain { const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str; /// Maximal number of unrewarded relayers in a single confirmation transaction at this - /// `ChainWithMessages`. + /// `ChainWithMessages`. Unrewarded means that the relayer has delivered messages, but + /// either confirmations haven't been delivered back to the source chain, or we haven't + /// received reward confirmations yet. + /// + /// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep + /// in mind that the same relayer account may take several (non-consecutive) entries in this + /// set. const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce; /// Maximal number of unconfirmed messages in a single confirmation transaction at this - /// `ChainWithMessages`. + /// `ChainWithMessages`. Unconfirmed means that the + /// message has been delivered, but either confirmations haven't been delivered back to the + /// source chain, or we haven't received reward confirmations for these messages yet. + /// + /// This constant limits difference between last message from last entry of the + /// `InboundLaneData::relayers` and first message at the first entry. + /// + /// There is no point of making this parameter lesser than + /// `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX`, because then maximal number of relayer entries + /// will be limited by maximal number of messages. + /// + /// This value also represents maximal number of messages in single delivery transaction. + /// Transaction that is declaring more messages than this value, will be rejected. Even if + /// these messages are from different lanes. const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce; + + /// Return maximal dispatch weight of the message we're able to receive. + fn maximal_incoming_message_dispatch_weight() -> Weight { + // we leave 1/2 of `max_extrinsic_weight` for the delivery transaction itself + Self::max_extrinsic_weight() / 2 + } + + /// Return maximal size of the message we're able to receive. + fn maximal_incoming_message_size() -> u32 { + maximal_incoming_message_size(Self::max_extrinsic_size()) + } +} + +/// Return maximal size of the message the chain with `max_extrinsic_size` is able to receive. +pub fn maximal_incoming_message_size(max_extrinsic_size: u32) -> u32 { + // The maximal size of extrinsic at Substrate-based chain depends on the + // `frame_system::Config::MaximumBlockLength` and + // `frame_system::Config::AvailableBlockRatio` constants. This check is here to be sure that + // the lane won't stuck because message is too large to fit into delivery transaction. + // + // **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not + // the message itself. The proof is always larger than the message. But unless chain state + // is enormously large, it should be several dozens/hundreds of bytes. The delivery + // transaction also contains signatures and signed extensions. Because of this, we reserve + // 1/3 of the the maximal extrinsic size for this data. + max_extrinsic_size / 3 * 2 } impl ChainWithMessages for T @@ -435,7 +480,7 @@ where AccountId: sp_std::cmp::Ord, { // remember to reward relayers that have delivered messages - // this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain + // this loop is bounded by `T::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` on the bridged chain let mut relayers_rewards = RelayersRewards::new(); for entry in messages_relayers { let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); @@ -447,6 +492,13 @@ where relayers_rewards } +/// The `BridgeMessagesCall` used by a chain. +pub type BridgeMessagesCallOf = BridgeMessagesCall< + AccountIdOf, + target_chain::FromBridgedChainMessagesProof>, + source_chain::FromBridgedChainMessagesDeliveryProof>, +>; + /// A minimized version of `pallet-bridge-messages::Call` that can be used without a runtime. #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] #[allow(non_camel_case_types)] @@ -481,7 +533,7 @@ pub enum VerificationError { /// Error returned by the bridged header chain. HeaderChain(HeaderChainError), /// Error returned while reading/decoding inbound lane data from the storage proof. - InboundLaneStorage(StorageProofError), + InboundLaneStorage(VecDbError), /// The declared message weight is incorrect. InvalidMessageWeight, /// Declared messages count doesn't match actual value. @@ -492,8 +544,6 @@ pub enum VerificationError { MessageTooLarge, /// Error returned while reading/decoding outbound lane data from the `VecDb`. OutboundLaneStorage(VecDbError), - /// Storage proof related error. - StorageProof(StorageProofError), /// `VecDb` related error. VecDb(VecDbError), /// Custom error diff --git a/bridges/primitives/messages/src/source_chain.rs b/bridges/primitives/messages/src/source_chain.rs index eb2e14159abcf..281ea77151f9f 100644 --- a/bridges/primitives/messages/src/source_chain.rs +++ b/bridges/primitives/messages/src/source_chain.rs @@ -16,11 +16,10 @@ //! Primitives of messages module, that are used on the source chain. -use crate::{InboundLaneData, LaneId, MessageNonce, UnrewardedRelayer, VerificationError}; +use crate::{LaneId, MessageNonce, UnrewardedRelayer}; -use bp_runtime::{RawStorageProof, Size}; +use bp_runtime::{Size, UntrustedVecDb}; use codec::{Decode, Encode}; -use frame_support::Parameter; use scale_info::TypeInfo; use sp_core::RuntimeDebug; use sp_std::{ @@ -44,58 +43,20 @@ pub struct FromBridgedChainMessagesDeliveryProof { /// Hash of the bridge header the proof is for. pub bridged_header_hash: BridgedHeaderHash, /// Storage trie proof generated for [`Self::bridged_header_hash`]. - pub storage_proof: RawStorageProof, + pub storage_proof: UntrustedVecDb, /// Lane id of which messages were delivered and the proof is for. pub lane: LaneId, } impl Size for FromBridgedChainMessagesDeliveryProof { fn size(&self) -> u32 { - u32::try_from( - self.storage_proof - .iter() - .fold(0usize, |sum, node| sum.saturating_add(node.len())), - ) - .unwrap_or(u32::MAX) + self.storage_proof.size() } } /// Number of messages, delivered by relayers. pub type RelayersRewards = BTreeMap; -/// Target chain API. Used by source chain to verify target chain proofs. -/// -/// All implementations of this trait should only work with finalized data that -/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane -/// that's stuck) and/or processing messages without paying fees. -/// -/// The `Payload` type here means the payload of the message that is sent from the -/// source chain to the target chain. The `AccountId` type here means the account -/// type used by the source chain. -pub trait TargetHeaderChain { - /// Proof that messages have been received by target chain. - type MessagesDeliveryProof: Parameter + Size; - - /// Verify message payload before we accept it. - /// - /// **CAUTION**: this is very important function. Incorrect implementation may lead - /// to stuck lanes and/or relayers loses. - /// - /// The proper implementation must ensure that the delivery-transaction with this - /// payload would (at least) be accepted into target chain transaction pool AND - /// eventually will be successfully mined. The most obvious incorrect implementation - /// example would be implementation for BTC chain that accepts payloads larger than - /// 1MB. BTC nodes aren't accepting transactions that are larger than 1MB, so relayer - /// will be unable to craft valid transaction => this (and all subsequent) messages will - /// never be delivered. - fn verify_message(payload: &Payload) -> Result<(), VerificationError>; - - /// Verify messages delivery proof and return lane && nonce of the latest received message. - fn verify_messages_delivery_proof( - proof: Self::MessagesDeliveryProof, - ) -> Result<(LaneId, InboundLaneData), VerificationError>; -} - /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. pub trait DeliveryConfirmationPayments { @@ -175,28 +136,10 @@ pub trait MessagesBridge { fn send_message(message: Self::SendMessageArgs) -> SendMessageArtifacts; } -/// Structure that may be used in place of `TargetHeaderChain` and -/// `MessageDeliveryAndDispatchPayment` on chains, where outbound messages are forbidden. +/// Structure that may be used in place `MessageDeliveryAndDispatchPayment` on chains, +/// where outbound messages are forbidden. pub struct ForbidOutboundMessages; -/// Error message that is used in `ForbidOutboundMessages` implementation. -const ALL_OUTBOUND_MESSAGES_REJECTED: &str = - "This chain is configured to reject all outbound messages"; - -impl TargetHeaderChain for ForbidOutboundMessages { - type MessagesDeliveryProof = (); - - fn verify_message(_payload: &Payload) -> Result<(), VerificationError> { - Err(VerificationError::Other(ALL_OUTBOUND_MESSAGES_REJECTED)) - } - - fn verify_messages_delivery_proof( - _proof: Self::MessagesDeliveryProof, - ) -> Result<(LaneId, InboundLaneData), VerificationError> { - Err(VerificationError::Other(ALL_OUTBOUND_MESSAGES_REJECTED)) - } -} - impl DeliveryConfirmationPayments for ForbidOutboundMessages { type Error = &'static str; diff --git a/bridges/primitives/messages/src/target_chain.rs b/bridges/primitives/messages/src/target_chain.rs index 1178f4e44ce76..7f678cd13a0fe 100644 --- a/bridges/primitives/messages/src/target_chain.rs +++ b/bridges/primitives/messages/src/target_chain.rs @@ -16,13 +16,11 @@ //! Primitives of messages module, that are used on the target chain. -use crate::{ - LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, -}; +use crate::{LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData}; use bp_runtime::{messages::MessageDispatchResult, Size, UntrustedVecDb}; use codec::{Decode, Encode, Error as CodecError}; -use frame_support::{weights::Weight, Parameter}; +use frame_support::weights::Weight; use scale_info::TypeInfo; use sp_core::RuntimeDebug; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, marker::PhantomData, prelude::*}; @@ -87,33 +85,6 @@ pub struct DispatchMessage { pub data: DispatchMessageData, } -/// Source chain API. Used by target chain, to verify source chain proofs. -/// -/// All implementations of this trait should only work with finalized data that -/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane -/// that's stuck) and/or processing messages without paying fees. -pub trait SourceHeaderChain { - /// Proof that messages are sent from source chain. This may also include proof - /// of corresponding outbound lane states. - type MessagesProof: Parameter + Size; - - /// Verify messages proof and return proved messages. - /// - /// Returns error if either proof is incorrect, or the number of messages in the proof - /// is not matching the `messages_count`. - /// - /// Messages vector is required to be sorted by nonce within each lane. Out-of-order - /// messages will be rejected. - /// - /// The `messages_count` argument verification (sane limits) is supposed to be made - /// outside this function. This function only verifies that the proof declares exactly - /// `messages_count` messages. - fn verify_messages_proof( - proof: Self::MessagesProof, - messages_count: u32, - ) -> Result, VerificationError>; -} - /// Called when inbound message is received. pub trait MessageDispatch { /// Decoded message payload type. Valid message may contain invalid payload. In this case @@ -199,29 +170,12 @@ impl DeliveryPayments for () { } } -/// Structure that may be used in place of `SourceHeaderChain` and `MessageDispatch` on chains, +/// Structure that may be used in place of `MessageDispatch` on chains, /// where inbound messages are forbidden. pub struct ForbidInboundMessages( PhantomData<(MessagesProof, DispatchPayload)>, ); -/// Error message that is used in `ForbidInboundMessages` implementation. -const ALL_INBOUND_MESSAGES_REJECTED: &str = - "This chain is configured to reject all inbound messages"; - -impl SourceHeaderChain - for ForbidInboundMessages -{ - type MessagesProof = MessagesProof; - - fn verify_messages_proof( - _proof: Self::MessagesProof, - _messages_count: u32, - ) -> Result, VerificationError> { - Err(VerificationError::Other(ALL_INBOUND_MESSAGES_REJECTED)) - } -} - impl MessageDispatch for ForbidInboundMessages { diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index a5477f0024702..b9d29fe06b20d 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -41,7 +41,7 @@ pub use chain::{ pub use frame_support::storage::storage_prefix as storage_value_final_key; use num_traits::{CheckedAdd, CheckedSub, One, SaturatingAdd, Zero}; pub use storage_proof::{ - record_all_keys as record_all_trie_keys, Error as StorageProofError, + grow_trie_leaf_value, record_all_keys as record_all_trie_keys, Error as StorageProofError, ProofSize as StorageProofSize, RawStorageProof, StorageProofChecker, }; pub use storage_types::BoundedStorageValue; diff --git a/bridges/primitives/runtime/src/storage_proof.rs b/bridges/primitives/runtime/src/storage_proof.rs index 1b706aa66c16f..3b1b31e7012d6 100644 --- a/bridges/primitives/runtime/src/storage_proof.rs +++ b/bridges/primitives/runtime/src/storage_proof.rs @@ -43,6 +43,18 @@ pub enum ProofSize { HasLargeLeaf(u32), } +/// Add extra data to the trie leaf value so that it'll be of given size. +pub fn grow_trie_leaf_value(mut value: Vec, size: ProofSize) -> Vec { + match size { + ProofSize::Minimal(_) => (), + ProofSize::HasLargeLeaf(size) if size as usize > value.len() => { + value.extend(sp_std::iter::repeat(42u8).take(size as usize - value.len())); + }, + ProofSize::HasLargeLeaf(_) => (), + } + value +} + /// This struct is used to read storage values from a subset of a Merklized database. The "proof" /// is a subset of the nodes in the Merkle structure of the database, so that it provides /// authentication against a known Merkle root as well as the values in the diff --git a/bridges/relays/lib-substrate-relay/src/messages/mod.rs b/bridges/relays/lib-substrate-relay/src/messages/mod.rs index 0fbace0aa8f54..c8dc826f9c0ab 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/mod.rs @@ -27,7 +27,6 @@ use crate::{ use async_std::sync::Arc; use bp_messages::{ - source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, ChainWithMessages as _, LaneId, MessageNonce, }; use bp_runtime::{ @@ -310,9 +309,7 @@ where P: SubstrateMessageLane, R: BridgeMessagesConfig>, I: 'static, - R::SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain< - MessagesProof = FromBridgedChainMessagesProof>, - >, + R::BridgedChain: bp_runtime::Chain>, CallOf: From> + GetDispatchInfo, { fn build_receive_messages_proof_call( @@ -408,11 +405,7 @@ where P: SubstrateMessageLane, R: BridgeMessagesConfig, I: 'static, - R::TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain< - R::OutboundPayload, - R::AccountId, - MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof>, - >, + R::BridgedChain: bp_runtime::Chain>, CallOf: From> + GetDispatchInfo, { fn build_receive_messages_delivery_proof_call( diff --git a/bridges/relays/lib-substrate-relay/src/messages/target.rs b/bridges/relays/lib-substrate-relay/src/messages/target.rs index 686f57e84d838..b5a3e94f34775 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/target.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/target.rs @@ -37,6 +37,7 @@ use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, storage_keys::inbound_lane_data_key, ChainWithMessages as _, InboundLaneData, LaneId, MessageNonce, UnrewardedRelayersState, }; +use bp_runtime::{HasherOf, UntrustedVecDb}; use messages_relay::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState}, @@ -47,7 +48,8 @@ use relay_substrate_client::{ }; use relay_utils::relay_loop::Client as RelayClient; use sp_core::Pair; -use std::ops::RangeInclusive; +use sp_runtime::traits::Header; +use std::{convert::TryFrom, ops::RangeInclusive}; /// Message receiving proof returned by the target Substrate node. pub type SubstrateMessagesDeliveryProof = @@ -231,19 +233,24 @@ where SubstrateError, > { let (id, relayers_state) = self.unrewarded_relayers_state(id).await?; - let inbound_data_key = bp_messages::storage_keys::inbound_lane_data_key( + let storage_keys = vec![inbound_lane_data_key( P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME, &self.lane_id, - ); - let proof = self - .target_client - .prove_storage(id.hash(), vec![inbound_data_key]) - .await? - .into_iter_nodes() - .collect(); + )]; + + let root = *self.target_client.header_by_hash(id.hash()).await?.state_root(); + let read_proof = self.target_client.prove_storage(id.hash(), storage_keys.clone()).await?; + let storage_proof = + UntrustedVecDb::try_new::>(read_proof, root, storage_keys) + .map_err(|e| { + SubstrateError::Custom(format!( + "Error generating messages delivery confirmation storage: {:?}", + e + )) + })?; let proof = FromBridgedChainMessagesDeliveryProof { bridged_header_hash: id.1, - storage_proof: proof, + storage_proof, lane: self.lane_id, }; Ok((id, (relayers_state, proof)))