diff --git a/cumulus/Cargo.lock b/cumulus/Cargo.lock index e18bf94d7c4d..13016948621b 100644 --- a/cumulus/Cargo.lock +++ b/cumulus/Cargo.lock @@ -1459,6 +1459,16 @@ dependencies = [ "sp-std", ] +[[package]] +name = "bp-xcm-bridge-hub-router" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", +] + [[package]] name = "bridge-hub-kusama-runtime" version = "0.1.0" @@ -1750,6 +1760,7 @@ dependencies = [ "bp-relayers", "bp-runtime", "bp-test-utils", + "bp-xcm-bridge-hub-router", "frame-support", "frame-system", "hash-db", diff --git a/cumulus/bridges/bin/runtime-common/Cargo.toml b/cumulus/bridges/bin/runtime-common/Cargo.toml index 9e8ad9317e71..5fb75f3887f1 100644 --- a/cumulus/bridges/bin/runtime-common/Cargo.toml +++ b/cumulus/bridges/bin/runtime-common/Cargo.toml @@ -21,6 +21,7 @@ bp-parachains = { path = "../../primitives/parachains", default-features = false bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } bp-relayers = { path = "../../primitives/relayers", default-features = false } bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false } @@ -55,6 +56,7 @@ std = [ "bp-parachains/std", "bp-polkadot-core/std", "bp-runtime/std", + "bp-xcm-bridge-hub-router/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/cumulus/bridges/bin/runtime-common/src/messages_call_ext.rs b/cumulus/bridges/bin/runtime-common/src/messages_call_ext.rs index 8b4a50a0f30f..0a1d7243620c 100644 --- a/cumulus/bridges/bin/runtime-common/src/messages_call_ext.rs +++ b/cumulus/bridges/bin/runtime-common/src/messages_call_ext.rs @@ -17,7 +17,7 @@ use crate::messages::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, }; -use bp_messages::{InboundLaneData, LaneId, MessageNonce}; +use bp_messages::{target_chain::MessageDispatch, InboundLaneData, LaneId, MessageNonce}; use frame_support::{ dispatch::CallableCallFor, traits::{Get, IsSubType}, @@ -77,7 +77,12 @@ impl ReceiveMessagesProofInfo { /// /// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed /// messages and/or unrewarded relayers. - fn is_obsolete(&self) -> bool { + fn is_obsolete(&self, is_dispatcher_active: bool) -> bool { + // if dispatcher is inactive, we don't accept any delivery transactions + if !is_dispatcher_active { + return true + } + // transactions with zero bundled nonces are not allowed, unless they're message // delivery transactions, which brings reward confirmations required to unblock // the lane @@ -275,7 +280,9 @@ impl< fn check_obsolete_call(&self) -> TransactionValidity { match self.call_info() { - Some(CallInfo::ReceiveMessagesProof(proof_info)) if proof_info.is_obsolete() => { + Some(CallInfo::ReceiveMessagesProof(proof_info)) + if proof_info.is_obsolete(T::MessageDispatch::is_active()) => + { log::trace!( target: pallet_bridge_messages::LOG_TARGET, "Rejecting obsolete messages delivery transaction: {:?}", @@ -327,8 +334,8 @@ mod tests { }, messages_call_ext::MessagesCallSubType, mock::{ - MaxUnconfirmedMessagesAtInboundLane, MaxUnrewardedRelayerEntriesAtInboundLane, - TestRuntime, ThisChainRuntimeCall, + DummyMessageDispatch, MaxUnconfirmedMessagesAtInboundLane, + MaxUnrewardedRelayerEntriesAtInboundLane, TestRuntime, ThisChainRuntimeCall, }, }; use bp_messages::{DeliveredMessages, UnrewardedRelayer, UnrewardedRelayersState}; @@ -435,6 +442,18 @@ mod tests { }); } + #[test] + fn extension_reject_call_when_dispatcher_is_inactive() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best delivered is message#10 and we're trying to deliver message 11..=15 + // => tx is accepted, but we have inactive dispatcher, so... + deliver_message_10(); + + DummyMessageDispatch::deactivate(); + assert!(!validate_message_delivery(11, 15)); + }); + } + #[test] fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots( ) { diff --git a/cumulus/bridges/bin/runtime-common/src/messages_xcm_extension.rs b/cumulus/bridges/bin/runtime-common/src/messages_xcm_extension.rs index 0317d745c15c..44e554ecb24f 100644 --- a/cumulus/bridges/bin/runtime-common/src/messages_xcm_extension.rs +++ b/cumulus/bridges/bin/runtime-common/src/messages_xcm_extension.rs @@ -22,16 +22,22 @@ //! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue` use bp_messages::{ - source_chain::MessagesBridge, + source_chain::{MessagesBridge, OnMessagesDelivered}, target_chain::{DispatchMessage, MessageDispatch}, - LaneId, + LaneId, MessageNonce, }; use bp_runtime::messages::MessageDispatchResult; +use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; use codec::{Decode, Encode}; -use frame_support::{dispatch::Weight, CloneNoBound, EqNoBound, PartialEqNoBound}; -use pallet_bridge_messages::WeightInfoExt as MessagesPalletWeights; +use frame_support::{dispatch::Weight, traits::Get, CloneNoBound, EqNoBound, PartialEqNoBound}; +use pallet_bridge_messages::{ + Config as MessagesConfig, OutboundLanesCongestedSignals, Pallet as MessagesPallet, + WeightInfoExt as MessagesPalletWeights, +}; use scale_info::TypeInfo; use sp_runtime::SaturatedConversion; +use sp_std::{fmt::Debug, marker::PhantomData}; +use xcm::prelude::*; use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob, HaulBlobError}; /// Plain "XCM" payload, which we transfer through bridge @@ -46,16 +52,25 @@ pub enum XcmBlobMessageDispatchResult { } /// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages -pub struct XcmBlobMessageDispatch { - _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights)>, +/// +/// It needs to be used at the target bridge hub. +pub struct XcmBlobMessageDispatch { + _marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, Channel)>, } -impl MessageDispatch - for XcmBlobMessageDispatch +impl< + BlobDispatcher: DispatchBlob, + Weights: MessagesPalletWeights, + Channel: XcmChannelStatusProvider, + > MessageDispatch for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; + fn is_active() -> bool { + !Channel::is_congested() + } + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload { Ok(ref payload) => { @@ -106,40 +121,374 @@ impl MessageDispat } } +/// A pair of sending chain location and message lane, used by this chain to send messages +/// over the bridge. +pub struct SenderAndLane { + /// Sending chain relative location. + pub location: MultiLocation, + /// Message lane, used by the sending chain. + pub lane: LaneId, +} + +impl SenderAndLane { + /// Create new object using provided location and lane. + pub fn new(location: MultiLocation, lane: LaneId) -> Self { + SenderAndLane { location, lane } + } +} + /// [`XcmBlobHauler`] is responsible for sending messages to the bridge "point-to-point link" from /// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`]. pub trait XcmBlobHauler { - /// Runtime message sender adapter. - type MessageSender: MessagesBridge; + /// Runtime that has messages pallet deployed. + type Runtime: MessagesConfig; + /// Instance of the messages pallet that is used to send messages. + type MessagesInstance: 'static; + /// Returns lane used by this hauler. + type SenderAndLane: Get; + + /// Actual XCM message sender (`HRMP` or `UMP`) to the source chain + /// location (`Self::SenderAndLane::get().location`). + type ToSourceChainSender: SendXcm; + /// An XCM message that is sent to the sending chain when the bridge queue becomes congested. + type CongestedMessage: Get>>; + /// An XCM message that is sent to the sending chain when the bridge queue becomes not + /// congested. + type UncongestedMessage: Get>>; - /// Return message lane (as "point-to-point link") used to deliver XCM messages. - fn xcm_lane() -> LaneId; + /// Returns `true` if we want to handle congestion. + fn supports_congestion_detection() -> bool { + Self::CongestedMessage::get().is_some() || Self::UncongestedMessage::get().is_some() + } } -/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and -/// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed. +/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`pallet_bridge_messages`] and +/// makes sure that XCM blob is sent to the outbound lane to be relayed. +/// +/// It needs to be used at the source bridge hub. pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData); -impl HaulBlob for XcmBlobHaulerAdapter { + +impl HaulBlob for XcmBlobHaulerAdapter +where + H::Runtime: MessagesConfig, +{ fn haul_blob(blob: sp_std::prelude::Vec) -> Result<(), HaulBlobError> { - let lane = H::xcm_lane(); - H::MessageSender::send_message(lane, blob) - .map(|artifacts| (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256)) - .map(|result| { + let sender_and_lane = H::SenderAndLane::get(); + MessagesPallet::::send_message(sender_and_lane.lane, blob) + .map(|artifacts| { log::info!( target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - ok: {:?} on lane: {:?}", - result, - lane - ) + "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", + artifacts.nonce, + sender_and_lane.lane, + artifacts.enqueued_messages, + ); + + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_message_enqueued( + &sender_and_lane, + artifacts.enqueued_messages, + ); }) .map_err(|error| { log::error!( target: crate::LOG_TARGET_BRIDGE_DISPATCH, "haul_blob result - error: {:?} on lane: {:?}", error, - lane + sender_and_lane.lane, ); HaulBlobError::Transport("MessageSenderError") }) } } + +impl OnMessagesDelivered for XcmBlobHaulerAdapter { + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { + let sender_and_lane = H::SenderAndLane::get(); + if sender_and_lane.lane != lane { + return + } + + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_messages_delivered( + &sender_and_lane, + enqueued_messages, + ); + } +} + +/// Manager of local XCM queues (and indirectly - underlying transport channels) that +/// controls the queue state. +/// +/// It needs to be used at the source bridge hub. +pub struct LocalXcmQueueManager(PhantomData); + +/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we +/// send a "congestion" XCM message to the sending chain. +const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192; + +/// After we have sent "congestion" XCM message to the sending chain, we wait until number +/// of messages in the outbound bridge queue drops to this count, before sending `uncongestion` +/// XCM message. +const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024; + +impl LocalXcmQueueManager { + /// Must be called whenever we push a message to the bridge lane. + pub fn on_bridge_message_enqueued( + sender_and_lane: &SenderAndLane, + enqueued_messages: MessageNonce, + ) { + // skip if we dont want to handle congestion + if !H::supports_congestion_detection() { + return + } + + // if we have already sent the congestion signal, we don't want to do anything + if Self::is_congested_signal_sent(sender_and_lane.lane) { + return + } + + // if the bridge queue is not congested, we don't want to do anything + let is_congested = enqueued_messages > OUTBOUND_LANE_CONGESTED_THRESHOLD; + if !is_congested { + return + } + + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Sending 'congested' XCM message to {:?} to avoid overloading lane {:?}: there are\ + {} messages queued at the bridge queue", + sender_and_lane.location, + sender_and_lane.lane, + enqueued_messages, + ); + + if let Err(e) = Self::send_congested_signal(sender_and_lane) { + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Failed to send the 'congested' XCM message to {:?}: {:?}", + sender_and_lane.location, + e, + ); + } + } + + /// Must be called whenever we receive a message delivery confirmation. + pub fn on_bridge_messages_delivered( + sender_and_lane: &SenderAndLane, + enqueued_messages: MessageNonce, + ) { + // skip if we dont want to handle congestion + if !H::supports_congestion_detection() { + return + } + + // if we have not sent the congestion signal before, we don't want to do anything + if !Self::is_congested_signal_sent(sender_and_lane.lane) { + return + } + + // if the bridge queue is still congested, we don't want to do anything + let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD; + if is_congested { + return + } + + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Sending 'uncongested' XCM message to {:?}. Lane {:?}: there are\ + {} messages queued at the bridge queue", + sender_and_lane.location, + sender_and_lane.lane, + enqueued_messages, + ); + + if let Err(e) = Self::send_uncongested_signal(sender_and_lane) { + log::info!( + target: crate::LOG_TARGET_BRIDGE_DISPATCH, + "Failed to send the 'uncongested' XCM message to {:?}: {:?}", + sender_and_lane.location, + e, + ); + } + } + + /// Returns true if we have sent "congested" signal to the `sending_chain_location`. + fn is_congested_signal_sent(lane: LaneId) -> bool { + OutboundLanesCongestedSignals::::get(lane) + } + + /// Send congested signal to the `sending_chain_location`. + fn send_congested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { + if let Some(msg) = H::CongestedMessage::get() { + send_xcm::(sender_and_lane.location, msg)?; + OutboundLanesCongestedSignals::::insert( + sender_and_lane.lane, + true, + ); + } + Ok(()) + } + + /// Send `uncongested` signal to the `sending_chain_location`. + fn send_uncongested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { + if let Some(msg) = H::UncongestedMessage::get() { + send_xcm::(sender_and_lane.location, msg)?; + OutboundLanesCongestedSignals::::remove( + sender_and_lane.lane, + ); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + + use bp_messages::OutboundLaneData; + use frame_support::parameter_types; + use pallet_bridge_messages::OutboundLanes; + + parameter_types! { + pub TestSenderAndLane: SenderAndLane = SenderAndLane { + location: MultiLocation::new(1, X1(Parachain(1000))), + lane: TEST_LANE_ID, + }; + pub DummyXcmMessage: Xcm<()> = Xcm::new(); + } + + struct DummySendXcm; + + impl DummySendXcm { + fn messages_sent() -> u32 { + frame_support::storage::unhashed::get(b"DummySendXcm").unwrap_or(0) + } + } + + impl SendXcm for DummySendXcm { + type Ticket = (); + + fn validate( + _destination: &mut Option, + _message: &mut Option>, + ) -> SendResult { + Ok(((), Default::default())) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + let messages_sent: u32 = Self::messages_sent(); + frame_support::storage::unhashed::put(b"DummySendXcm", &(messages_sent + 1)); + Ok(XcmHash::default()) + } + } + + struct TestBlobHauler; + + impl XcmBlobHauler for TestBlobHauler { + type Runtime = TestRuntime; + type MessagesInstance = (); + type SenderAndLane = TestSenderAndLane; + + type ToSourceChainSender = DummySendXcm; + type CongestedMessage = DummyXcmMessage; + type UncongestedMessage = DummyXcmMessage; + } + + type TestBlobHaulerAdapter = XcmBlobHaulerAdapter; + + fn fill_up_lane_to_congestion() { + OutboundLanes::::insert( + TEST_LANE_ID, + OutboundLaneData { + oldest_unpruned_nonce: 0, + latest_received_nonce: 0, + latest_generated_nonce: OUTBOUND_LANE_CONGESTED_THRESHOLD, + }, + ); + } + + #[test] + fn congested_signal_is_not_sent_twice() { + run_test(|| { + fill_up_lane_to_congestion(); + + // next sent message leads to congested signal + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + + // next sent message => we don't sent another congested signal + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + }); + } + + #[test] + fn congested_signal_is_not_sent_when_outbound_lane_is_not_congested() { + run_test(|| { + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 0); + }); + } + + #[test] + fn congested_signal_is_sent_when_outbound_lane_is_congested() { + run_test(|| { + fill_up_lane_to_congestion(); + + // next sent message leads to congested signal + TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + assert!(LocalXcmQueueManager::::is_congested_signal_sent(TEST_LANE_ID)); + }); + } + + #[test] + fn uncongested_signal_is_not_sent_when_messages_are_delivered_at_other_lane() { + run_test(|| { + LocalXcmQueueManager::::send_congested_signal(&TestSenderAndLane::get()).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + + // when we receive a delivery report for other lane, we don't send an uncongested signal + TestBlobHaulerAdapter::on_messages_delivered(LaneId([42, 42, 42, 42]), 0); + assert_eq!(DummySendXcm::messages_sent(), 1); + }); + } + + #[test] + fn uncongested_signal_is_not_sent_when_we_havent_send_congested_signal_before() { + run_test(|| { + TestBlobHaulerAdapter::on_messages_delivered(TEST_LANE_ID, 0); + assert_eq!(DummySendXcm::messages_sent(), 0); + }); + } + + #[test] + fn uncongested_signal_is_not_sent_if_outbound_lane_is_still_congested() { + run_test(|| { + LocalXcmQueueManager::::send_congested_signal(&TestSenderAndLane::get()).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + + TestBlobHaulerAdapter::on_messages_delivered( + TEST_LANE_ID, + OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1, + ); + assert_eq!(DummySendXcm::messages_sent(), 1); + }); + } + + #[test] + fn uncongested_signal_is_sent_if_outbound_lane_is_uncongested() { + run_test(|| { + LocalXcmQueueManager::::send_congested_signal(&TestSenderAndLane::get()).unwrap(); + assert_eq!(DummySendXcm::messages_sent(), 1); + + TestBlobHaulerAdapter::on_messages_delivered( + TEST_LANE_ID, + OUTBOUND_LANE_UNCONGESTED_THRESHOLD, + ); + assert_eq!(DummySendXcm::messages_sent(), 2); + }); + } +} diff --git a/cumulus/bridges/bin/runtime-common/src/mock.rs b/cumulus/bridges/bin/runtime-common/src/mock.rs index 6b5edabc886b..9c41d17fa995 100644 --- a/cumulus/bridges/bin/runtime-common/src/mock.rs +++ b/cumulus/bridges/bin/runtime-common/src/mock.rs @@ -33,10 +33,15 @@ use crate::messages::{ }; use bp_header_chain::{ChainWithGrandpa, HeaderChain}; -use bp_messages::{target_chain::ForbidInboundMessages, LaneId, MessageNonce}; +use bp_messages::{ + target_chain::{DispatchMessage, MessageDispatch}, + LaneId, MessageNonce, +}; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::PayRewardFromAccount; -use bp_runtime::{Chain, ChainId, Parachain, UnderlyingChainProvider}; +use bp_runtime::{ + messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider, +}; use codec::{Decode, Encode}; use frame_support::{ parameter_types, @@ -245,9 +250,10 @@ impl pallet_bridge_messages::Config for TestRuntime { (), ConstU64<100_000>, >; + type OnMessagesDelivered = (); type SourceHeaderChain = SourceHeaderChainAdapter; - type MessageDispatch = ForbidInboundMessages<(), FromBridgedChainMessagePayload>; + type MessageDispatch = DummyMessageDispatch; type BridgedChainId = BridgedChainId; } @@ -259,6 +265,34 @@ impl pallet_bridge_relayers::Config for TestRuntime { type WeightInfo = (); } +/// Dummy message dispatcher. +pub struct DummyMessageDispatch; + +impl DummyMessageDispatch { + pub fn deactivate() { + frame_support::storage::unhashed::put(&b"inactive"[..], &false); + } +} + +impl MessageDispatch for DummyMessageDispatch { + type DispatchPayload = Vec; + type DispatchLevelResult = (); + + fn is_active() -> bool { + frame_support::storage::unhashed::take::(&b"inactive"[..]) != Some(false) + } + + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + Weight::zero() + } + + fn dispatch( + _: DispatchMessage, + ) -> MessageDispatchResult { + MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } + } +} + /// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from /// `BridgedChain`. #[derive(Debug, PartialEq, Eq)] diff --git a/cumulus/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/cumulus/bridges/bin/runtime-common/src/refund_relayer_extension.rs index c54198373167..f611686420c2 100644 --- a/cumulus/bridges/bin/runtime-common/src/refund_relayer_extension.rs +++ b/cumulus/bridges/bin/runtime-common/src/refund_relayer_extension.rs @@ -494,8 +494,7 @@ where }; // compute total number of messages in transaction - let bundled_messages = - parsed_call.messages_call_info().bundled_messages().checked_len().unwrap_or(0); + let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len(); // a quick check to avoid invalid high-priority transactions if bundled_messages > Runtime::MaxUnconfirmedMessagesAtInboundLane::get() { diff --git a/cumulus/bridges/modules/grandpa/src/lib.rs b/cumulus/bridges/modules/grandpa/src/lib.rs index 259a6c0f36b7..425712ad9a20 100644 --- a/cumulus/bridges/modules/grandpa/src/lib.rs +++ b/cumulus/bridges/modules/grandpa/src/lib.rs @@ -40,10 +40,10 @@ pub use storage_types::StoredAuthoritySet; use bp_header_chain::{ justification::GrandpaJustification, AuthoritySet, ChainWithGrandpa, GrandpaConsensusLogReader, - HeaderChain, HeaderGrandpaInfo, InitializationData, StoredHeaderData, StoredHeaderDataBuilder, + HeaderChain, InitializationData, StoredHeaderData, StoredHeaderDataBuilder, + StoredHeaderGrandpaInfo, }; use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule}; -use finality_grandpa::voter_set::VoterSet; use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound}; use sp_runtime::{ traits::{Header as HeaderT, Zero}, @@ -241,9 +241,9 @@ pub mod pallet { Self::deposit_event(Event::UpdatedBestFinalizedHeader { number, hash, - grandpa_info: HeaderGrandpaInfo { - justification, - authority_set: maybe_new_authority_set, + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification, + new_verification_context: maybe_new_authority_set, }, }); @@ -411,7 +411,7 @@ pub mod pallet { number: BridgedBlockNumber, hash: BridgedBlockHash, /// The Grandpa info associated to the new best finalized header. - grandpa_info: HeaderGrandpaInfo>, + grandpa_info: StoredHeaderGrandpaInfo>, }, } @@ -505,14 +505,9 @@ pub mod pallet { ) -> Result<(), sp_runtime::DispatchError> { use bp_header_chain::justification::verify_justification; - let voter_set = - VoterSet::new(authority_set.authorities).ok_or(>::InvalidAuthoritySet)?; - let set_id = authority_set.set_id; - Ok(verify_justification::>( (hash, number), - set_id, - &voter_set, + &authority_set.try_into().map_err(|_| >::InvalidAuthoritySet)?, justification, ) .map_err(|e| { @@ -617,7 +612,7 @@ where ::RuntimeEvent: TryInto>, { /// Get the GRANDPA justifications accepted in the current block. - pub fn synced_headers_grandpa_info() -> Vec>> { + pub fn synced_headers_grandpa_info() -> Vec>> { frame_system::Pallet::::read_events_no_consensus() .filter_map(|event| { if let Event::::UpdatedBestFinalizedHeader { grandpa_info, .. } = @@ -934,9 +929,9 @@ mod tests { event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader { number: *header.number(), hash: header.hash(), - grandpa_info: HeaderGrandpaInfo { - justification: justification.clone(), - authority_set: None, + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification.clone(), + new_verification_context: None, }, }), topics: vec![], @@ -944,7 +939,10 @@ mod tests { ); assert_eq!( Pallet::::synced_headers_grandpa_info(), - vec![HeaderGrandpaInfo { justification, authority_set: None }] + vec![StoredHeaderGrandpaInfo { + finality_proof: justification, + new_verification_context: None + }] ); }) } @@ -1075,9 +1073,11 @@ mod tests { event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader { number: *header.number(), hash: header.hash(), - grandpa_info: HeaderGrandpaInfo { - justification: justification.clone(), - authority_set: Some(>::get().into()), + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification.clone(), + new_verification_context: Some( + >::get().into() + ), }, }), topics: vec![], @@ -1085,9 +1085,11 @@ mod tests { ); assert_eq!( Pallet::::synced_headers_grandpa_info(), - vec![HeaderGrandpaInfo { - justification, - authority_set: Some(>::get().into()), + vec![StoredHeaderGrandpaInfo { + finality_proof: justification, + new_verification_context: Some( + >::get().into() + ), }] ); }) diff --git a/cumulus/bridges/modules/messages/src/lib.rs b/cumulus/bridges/modules/messages/src/lib.rs index 91ab3c965b93..67c6fb23cf16 100644 --- a/cumulus/bridges/modules/messages/src/lib.rs +++ b/cumulus/bridges/modules/messages/src/lib.rs @@ -53,7 +53,8 @@ use crate::{ use bp_messages::{ source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, SendMessageArtifacts, TargetHeaderChain, + DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, + SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, @@ -63,7 +64,9 @@ use bp_messages::{ MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayersState, VerificationError, }; -use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, PreComputedSize, Size}; +use bp_runtime::{ + BasicOperatingMode, ChainId, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, +}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound}; use sp_runtime::traits::UniqueSaturatedFrom; @@ -156,6 +159,8 @@ pub mod pallet { type LaneMessageVerifier: LaneMessageVerifier; /// Delivery confirmation payments. type DeliveryConfirmationPayments: DeliveryConfirmationPayments; + /// Delivery confirmation callback. + type OnMessagesDelivered: OnMessagesDelivered; // Types that are used by inbound_lane (on target chain). @@ -281,6 +286,9 @@ pub mod pallet { Error::::TooManyMessagesInTheProof ); + // if message dispatcher is currently inactive, we won't accept any messages + ensure!(T::MessageDispatch::is_active(), Error::::MessageDispatchInactive); + // why do we need to know the weight of this (`receive_messages_proof`) call? Because // we may want to return some funds for not-dispatching (or partially dispatching) some // messages to the call origin (relayer). And this is done by returning actual weight @@ -487,6 +495,12 @@ pub mod pallet { lane_id, ); + // notify others about messages delivery + T::OnMessagesDelivered::on_messages_delivered( + lane_id, + lane.data().queued_messages().saturating_len(), + ); + // because of lags, the inbound lane state (`lane_data`) may have entries for // already rewarded relayers and messages (if all entries are duplicated, then // this transaction must be filtered out by our signed extension) @@ -518,6 +532,8 @@ pub mod pallet { NotOperatingNormally, /// The outbound lane is inactive. InactiveOutboundLane, + /// The inbound message dispatcher is inactive. + MessageDispatchInactive, /// Message has been treated as invalid by chain verifier. MessageRejectedByChainVerifier(VerificationError), /// Message has been treated as invalid by lane verifier. @@ -580,6 +596,25 @@ pub mod pallet { MaxValues = MaybeOutboundLanesCount, >; + /// Map of lane id => is congested signal sent. It is managed by the + /// `bridge_runtime_common::LocalXcmQueueManager`. + /// + /// **bridges-v1**: this map is a temporary hack and will be dropped in the `v2`. We can emulate + /// a storage map using `sp_io::unhashed` storage functions, but then benchmarks are not + /// accounting its `proof_size`, so it is missing from the final weights. So we need to make it + /// a map inside some pallet. We could use a simply value instead of map here, because + /// in `v1` we'll only have a single lane. But in the case of adding another lane before `v2`, + /// it'll be easier to deal with the isolated storage map instead. + #[pallet::storage] + pub type OutboundLanesCongestedSignals, I: 'static = ()> = StorageMap< + Hasher = Blake2_128Concat, + Key = LaneId, + Value = bool, + QueryKind = ValueQuery, + OnEmpty = GetDefault, + MaxValues = MaybeOutboundLanesCount, + >; + /// All queued outbound messages. #[pallet::storage] pub type OutboundMessages, I: 'static = ()> = @@ -627,6 +662,11 @@ pub mod pallet { } } + /// Return outbound lane data. + pub fn outbound_lane_data(lane: LaneId) -> OutboundLaneData { + OutboundLanes::::get(lane) + } + /// Return inbound lane data. pub fn inbound_lane_data(lane: LaneId) -> InboundLaneData { InboundLanes::::get(lane).0 @@ -703,6 +743,9 @@ fn send_message, I: 'static>( .send_message(encoded_payload) .map_err(Error::::MessageRejectedByPallet)?; + // return number of messages in the queue to let sender know about its state + let enqueued_messages = lane.data().queued_messages().saturating_len(); + log::trace!( target: LOG_TARGET, "Accepted message {} to lane {:?}. Message size: {:?}", @@ -713,7 +756,7 @@ fn send_message, I: 'static>( Pallet::::deposit_event(Event::MessageAccepted { lane_id, nonce }); - Ok(SendMessageArtifacts { nonce }) + Ok(SendMessageArtifacts { nonce, enqueued_messages }) } /// Ensure that the pallet is in normal operational mode. @@ -881,8 +924,9 @@ mod tests { mock::{ inbound_unrewarded_relayers_state, message, message_payload, run_test, unrewarded_relayer, AccountId, DbWeight, RuntimeEvent as TestEvent, RuntimeOrigin, - TestDeliveryConfirmationPayments, TestDeliveryPayments, TestMessagesDeliveryProof, - TestMessagesProof, TestRelayer, TestRuntime, TestWeightInfo, MAX_OUTBOUND_PAYLOAD_SIZE, + 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, }, @@ -908,10 +952,12 @@ mod tests { fn send_regular_message() { get_ready_for_events(); - let message_nonce = - outbound_lane::(TEST_LANE_ID).data().latest_generated_nonce + 1; - send_message::(TEST_LANE_ID, REGULAR_PAYLOAD) + let outbound_lane = outbound_lane::(TEST_LANE_ID); + let message_nonce = outbound_lane.data().latest_generated_nonce + 1; + let prev_enqueud_messages = outbound_lane.data().queued_messages().saturating_len(); + let artifacts = send_message::(TEST_LANE_ID, REGULAR_PAYLOAD) .expect("send_message has failed"); + assert_eq!(artifacts.enqueued_messages, prev_enqueud_messages + 1); // check event with assigned nonce assert_eq!( @@ -1201,6 +1247,23 @@ mod tests { }); } + #[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(|| { @@ -1304,6 +1367,7 @@ mod tests { ); 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 @@ -1346,6 +1410,7 @@ mod tests { ); 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))); }); } diff --git a/cumulus/bridges/modules/messages/src/mock.rs b/cumulus/bridges/modules/messages/src/mock.rs index 83752523efb9..67f7b78a487a 100644 --- a/cumulus/bridges/modules/messages/src/mock.rs +++ b/cumulus/bridges/modules/messages/src/mock.rs @@ -21,7 +21,9 @@ use crate::Config; use bp_messages::{ calc_relayers_rewards, - source_chain::{DeliveryConfirmationPayments, LaneMessageVerifier, TargetHeaderChain}, + source_chain::{ + DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, TargetHeaderChain, + }, target_chain::{ DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, @@ -161,6 +163,7 @@ impl Config for TestRuntime { type TargetHeaderChain = TestTargetHeaderChain; type LaneMessageVerifier = TestLaneMessageVerifier; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; + type OnMessagesDelivered = TestOnMessagesDelivered; type SourceHeaderChain = TestSourceHeaderChain; type MessageDispatch = TestMessageDispatch; @@ -402,14 +405,26 @@ impl SourceHeaderChain for TestSourceHeaderChain { } } -/// Source header chain that is used in tests. +/// Test message dispatcher. #[derive(Debug)] pub struct TestMessageDispatch; +impl TestMessageDispatch { + pub fn deactivate() { + frame_support::storage::unhashed::put(b"TestMessageDispatch.IsCongested", &true) + } +} + impl MessageDispatch for TestMessageDispatch { type DispatchPayload = TestPayload; type DispatchLevelResult = TestDispatchLevelResult; + fn is_active() -> bool { + !frame_support::storage::unhashed::get_or_default::( + b"TestMessageDispatch.IsCongested", + ) + } + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload.as_ref() { Ok(payload) => payload.declared_weight, @@ -427,6 +442,24 @@ impl MessageDispatch for TestMessageDispatch { } } +/// Test callback, called during message delivery confirmation transaction. +pub struct TestOnMessagesDelivered; + +impl TestOnMessagesDelivered { + pub fn call_arguments() -> Option<(LaneId, MessageNonce)> { + frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered") + } +} + +impl OnMessagesDelivered for TestOnMessagesDelivered { + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { + frame_support::storage::unhashed::put( + b"TestOnMessagesDelivered.OnMessagesDelivered", + &(lane, enqueued_messages), + ); + } +} + /// Return test lane message with given nonce and payload. pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { Message { key: MessageKey { lane_id: TEST_LANE_ID, nonce }, payload: payload.encode() } diff --git a/cumulus/bridges/modules/parachains/src/lib.rs b/cumulus/bridges/modules/parachains/src/lib.rs index 24bc3535e619..be46fae3c925 100644 --- a/cumulus/bridges/modules/parachains/src/lib.rs +++ b/cumulus/bridges/modules/parachains/src/lib.rs @@ -697,7 +697,7 @@ pub(crate) mod tests { use bp_test_utils::prepare_parachain_heads_proof; use codec::Encode; - use bp_header_chain::{justification::GrandpaJustification, HeaderGrandpaInfo}; + use bp_header_chain::{justification::GrandpaJustification, StoredHeaderGrandpaInfo}; use bp_parachains::{ BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider, }; @@ -1036,9 +1036,9 @@ pub(crate) mod tests { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { number: 1, hash: relay_1_hash, - grandpa_info: HeaderGrandpaInfo { - justification, - authority_set: None, + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification, + new_verification_context: None, }, } ), @@ -1177,9 +1177,9 @@ pub(crate) mod tests { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { number: 1, hash: relay_1_hash, - grandpa_info: HeaderGrandpaInfo { - justification, - authority_set: None, + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification, + new_verification_context: None, } } ), @@ -1230,9 +1230,9 @@ pub(crate) mod tests { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { number: 1, hash: relay_1_hash, - grandpa_info: HeaderGrandpaInfo { - justification: justification.clone(), - authority_set: None, + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification.clone(), + new_verification_context: None, } } ), @@ -1271,9 +1271,9 @@ pub(crate) mod tests { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { number: 1, hash: relay_1_hash, - grandpa_info: HeaderGrandpaInfo { - justification, - authority_set: None, + grandpa_info: StoredHeaderGrandpaInfo { + finality_proof: justification, + new_verification_context: None, } } ), diff --git a/cumulus/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/cumulus/bridges/modules/xcm-bridge-hub-router/Cargo.toml new file mode 100644 index 000000000000..3d13e7cc3d7f --- /dev/null +++ b/cumulus/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "pallet-xcm-bridge-hub-router" +description = "Bridge hub interface for sibling/parent chains with dynamic fees support." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +log = { version = "0.4.19", default-features = false } +scale-info = { version = "2.8.0", default-features = false, features = ["bit-vec", "derive", "serde"] } + +# Bridge dependencies + +bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } + +# Substrate Dependencies + +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Polkadot Dependencies + +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "bp-xcm-bridge-hub-router/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "xcm/std", + "xcm-builder/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/cumulus/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs b/cumulus/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs new file mode 100644 index 000000000000..b32b983daf72 --- /dev/null +++ b/cumulus/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -0,0 +1,96 @@ +// 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 . + +//! XCM bridge hub router pallet benchmarks. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{Bridge, Call}; + +use bp_xcm_bridge_hub_router::{BridgeState, MINIMAL_DELIVERY_FEE_FACTOR}; +use frame_benchmarking::benchmarks_instance_pallet; +use frame_support::{ + dispatch::UnfilteredDispatchable, + traits::{EnsureOrigin, Get, Hooks}, +}; +use sp_runtime::traits::Zero; +use xcm::prelude::*; + +/// Pallet we're benchmarking here. +pub struct Pallet, I: 'static = ()>(crate::Pallet); + +/// Trait that must be implemented by runtime to be able to benchmark pallet properly. +pub trait Config: crate::Config { + /// Fill up queue so it becomes congested. + fn make_congested(); + + /// Returns destination which is valid for this router instance. + /// (Needs to pass `T::Bridges`) + /// Make sure that `SendXcm` will pass. + fn ensure_bridged_target_destination() -> MultiLocation { + MultiLocation::new( + Self::UniversalLocation::get().len() as u8, + X1(GlobalConsensus(Self::BridgedNetworkId::get().unwrap())), + ) + } +} + +benchmarks_instance_pallet! { + on_initialize_when_non_congested { + Bridge::::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); + }: { + crate::Pallet::::on_initialize(Zero::zero()) + } + + on_initialize_when_congested { + Bridge::::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); + T::make_congested(); + }: { + crate::Pallet::::on_initialize(Zero::zero()) + } + + report_bridge_status { + Bridge::::put(BridgeState::default()); + + let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin"); + let bridge_id = Default::default(); + let is_congested = true; + + let call = Call::::report_bridge_status { bridge_id, is_congested }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Bridge::::get().is_congested); + } + + send_message { + // make local queue congested, because it means additional db write + T::make_congested(); + + let dest = T::ensure_bridged_target_destination(); + let xcm = sp_std::vec![].into(); + }: { + send_xcm::>(dest, xcm).expect("message is sent") + } + verify { + assert!(Bridge::::get().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR); + } +} diff --git a/cumulus/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/cumulus/bridges/modules/xcm-bridge-hub-router/src/lib.rs new file mode 100644 index 000000000000..87e050b45c73 --- /dev/null +++ b/cumulus/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -0,0 +1,557 @@ +// 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 that may be used instead of `SovereignPaidRemoteExporter` in the XCM router +//! configuration. The main thing that the pallet offers is the dynamic message fee, +//! that is computed based on the bridge queues state. It starts exponentially increasing +//! if the queue between this chain and the sibling/child bridge hub is congested. +//! +//! All other bridge hub queues offer some backpressure mechanisms. So if at least one +//! of all queues is congested, it will eventually lead to the growth of the queue at +//! this chain. +//! +//! **A note on terminology**: when we mention the bridge hub here, we mean the chain that +//! has the messages pallet deployed (`pallet-bridge-grandpa`, `pallet-bridge-messages`, +//! `pallet-xcm-bridge-hub`, ...). It may be the system bridge hub parachain or any other +//! chain. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_xcm_bridge_hub_router::{ + BridgeState, XcmChannelStatusProvider, MINIMAL_DELIVERY_FEE_FACTOR, +}; +use codec::Encode; +use frame_support::traits::Get; +use sp_core::H256; +use sp_runtime::{FixedPointNumber, FixedU128, Saturating}; +use xcm::prelude::*; +use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter}; + +pub use pallet::*; +pub use weights::WeightInfo; + +pub mod benchmarking; +pub mod weights; + +mod mock; + +/// The factor that is used to increase current message fee factor when bridge experiencing +/// some lags. +const EXPONENTIAL_FEE_BASE: FixedU128 = FixedU128::from_rational(105, 100); // 1.05 +/// The factor that is used to increase current message fee factor for every sent kilobyte. +const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001 + +/// Maximal size of the XCM message that may be sent over bridge. +/// +/// This should be less than the maximal size, allowed by the messages pallet, because +/// the message itself is wrapped in other structs and is double encoded. +pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024; + +/// The target that will be used when publishing logs related to this pallet. +/// +/// This doesn't match the pattern used by other bridge pallets (`runtime::bridge-*`). But this +/// pallet has significant differences with those pallets. The main one is that is intended to +/// be deployed at sending chains. Other bridge pallets are likely to be deployed at the separate +/// bridge hub parachain. +pub const LOG_TARGET: &str = "xcm::bridge-hub-router"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Benchmarks results from runtime we're plugged into. + type WeightInfo: WeightInfo; + + /// Universal location of this runtime. + type UniversalLocation: Get; + /// The bridged network that this config is for if specified. + /// Also used for filtering `Bridges` by `BridgedNetworkId`. + /// If not specified, allows all networks pass through. + type BridgedNetworkId: Get>; + /// Configuration for supported **bridged networks/locations** with **bridge location** and + /// **possible fee**. Allows to externalize better control over allowed **bridged + /// networks/locations**. + type Bridges: ExporterFor; + + /// Origin of the sibling bridge hub that is allowed to report bridge status. + type BridgeHubOrigin: EnsureOrigin; + /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. + type ToBridgeHubSender: SendXcm; + /// Underlying channel with the sibling bridge hub. It must match the channel, used + /// by the `Self::ToBridgeHubSender`. + type WithBridgeHubChannel: XcmChannelStatusProvider; + + /// Additional fee that is paid for every byte of the outbound message. + type ByteFee: Get; + /// Asset that is used to paid bridge fee. + type FeeAsset: Get; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + // TODO: make sure that `WithBridgeHubChannel::is_congested` returns true if either + // of XCM channels (outbound/inbound) is suspended. Because if outbound is suspended + // that is definitely congestion. If inbound is suspended, then we are not able to + // receive the "report_bridge_status" signal (that maybe sent by the bridge hub). + + // if the channel with sibling/child bridge hub is suspended, we don't change + // anything + if T::WithBridgeHubChannel::is_congested() { + return T::WeightInfo::on_initialize_when_congested() + } + + // if bridge has reported congestion, we don't change anything + let mut bridge = Self::bridge(); + if bridge.is_congested { + return T::WeightInfo::on_initialize_when_congested() + } + + // if fee factor is already minimal, we don't change anything + if bridge.delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR { + return T::WeightInfo::on_initialize_when_congested() + } + + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + MINIMAL_DELIVERY_FEE_FACTOR.max(bridge.delivery_fee_factor / EXPONENTIAL_FEE_BASE); + log::info!( + target: LOG_TARGET, + "Bridge queue is uncongested. Decreased fee factor from {} to {}", + previous_factor, + bridge.delivery_fee_factor, + ); + + Bridge::::put(bridge); + T::WeightInfo::on_initialize_when_non_congested() + } + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Notification about congested bridge queue. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::report_bridge_status())] + pub fn report_bridge_status( + origin: OriginFor, + // this argument is not currently used, but to ease future migration, we'll keep it + // here + bridge_id: H256, + is_congested: bool, + ) -> DispatchResult { + let _ = T::BridgeHubOrigin::ensure_origin(origin)?; + + log::info!( + target: LOG_TARGET, + "Received bridge status from {:?}: congested = {}", + bridge_id, + is_congested, + ); + + Bridge::::mutate(|bridge| { + bridge.is_congested = is_congested; + }); + Ok(()) + } + } + + /// Bridge that we are using. + /// + /// **bridges-v1** assumptions: all outbound messages through this router are using single lane + /// and to single remote consensus. If there is some other remote consensus that uses the same + /// bridge hub, the separate pallet instance shall be used, In `v2` we'll have all required + /// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple bridges + /// by the same pallet instance. + #[pallet::storage] + #[pallet::getter(fn bridge)] + pub type Bridge, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>; + + impl, I: 'static> Pallet { + /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. + pub(crate) fn on_message_sent_to_bridge(message_size: u32) { + let _ = Bridge::::try_mutate(|bridge| { + let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested(); + let is_bridge_congested = bridge.is_congested; + + // if outbound queue is not congested AND bridge has not reported congestion, do + // nothing + if !is_channel_with_bridge_hub_congested && !is_bridge_congested { + return Err(()) + } + + // ok - we need to increase the fee factor, let's do that + let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) + .saturating_mul(MESSAGE_SIZE_FEE_BASE); + let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + bridge.delivery_fee_factor.saturating_mul(total_factor); + + log::info!( + target: LOG_TARGET, + "Bridge channel is congested. Increased fee factor from {} to {}", + previous_factor, + bridge.delivery_fee_factor, + ); + + Ok(()) + }); + } + } +} + +/// We'll be using `SovereignPaidRemoteExporter` to send remote messages over the sibling/child +/// bridge hub. +type ViaBridgeHubExporter = SovereignPaidRemoteExporter< + Pallet, + >::ToBridgeHubSender, + >::UniversalLocation, +>; + +// This pallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute +// message fee using fee factor. +impl, I: 'static> ExporterFor for Pallet { + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorMultiLocation, + message: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + // ensure that the message is sent to the expected bridged network (if specified). + if let Some(bridged_network) = T::BridgedNetworkId::get() { + if *network != bridged_network { + log::trace!( + target: LOG_TARGET, + "Router with bridged_network_id {:?} does not support bridging to network {:?}!", + bridged_network, + network, + ); + return None + } + } + + // ensure that the message is sent to the expected bridged network and location. + let Some((bridge_hub_location, maybe_payment)) = + T::Bridges::exporter_for(network, remote_location, message) + else { + log::trace!( + target: LOG_TARGET, + "Router with bridged_network_id {:?} does not support bridging to network {:?} and remote_location {:?}!", + T::BridgedNetworkId::get(), + network, + remote_location, + ); + return None + }; + + // take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset` + let base_fee = match maybe_payment { + Some(payment) => match payment { + MultiAsset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount, + invalid_asset => { + log::error!( + target: LOG_TARGET, + "Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} which is not \ + compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!", + T::BridgedNetworkId::get(), + T::FeeAsset::get(), + invalid_asset, + bridge_hub_location, + network, + remote_location, + ); + return None + }, + }, + None => 0, + }; + + // compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending + // message from this chain to child/sibling bridge hub is determined by the + // `Config::ToBridgeHubSender` + let message_size = message.encoded_size(); + let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get()); + let fee_sum = base_fee.saturating_add(message_fee); + let fee_factor = Self::bridge().delivery_fee_factor; + let fee = fee_factor.saturating_mul_int(fee_sum); + + let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None }; + + log::info!( + target: LOG_TARGET, + "Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", + (network, remote_location), + message_size, + fee, + fee_factor + ); + + Some((bridge_hub_location, fee)) + } +} + +// This pallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular +// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that +// are going to the bridged network. +impl, I: 'static> SendXcm for Pallet { + type Ticket = (u32, ::Ticket); + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + // we won't have an access to `dest` and `xcm` in the `delvier` method, so precompute + // everything required here + let message_size = xcm + .as_ref() + .map(|xcm| xcm.encoded_size() as _) + .ok_or(SendError::MissingArgument)?; + + // bridge doesn't support oversized/overweight messages now. So it is better to drop such + // messages here than at the bridge hub. Let's check the message size. + if message_size > HARD_MESSAGE_SIZE_LIMIT { + return Err(SendError::ExceedsMaxMessageSize) + } + + // just use exporter to validate destination and insert instructions to pay message fee + // at the sibling/child bridge hub + // + // the cost will include both cost of: (1) to-sibling bridg hub delivery (returned by + // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by + // `Self::exporter_for`) + ViaBridgeHubExporter::::validate(dest, xcm) + .map(|(ticket, cost)| ((message_size, ticket), cost)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + // use router to enqueue message to the sibling/child bridge hub. This also should handle + // payment for passing through this queue. + let (message_size, ticket) = ticket; + let xcm_hash = ViaBridgeHubExporter::::deliver(ticket)?; + + // increase delivery fee factor if required + Self::on_message_sent_to_bridge(message_size); + + Ok(xcm_hash) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mock::*; + + use frame_support::traits::Hooks; + use sp_runtime::traits::One; + + fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: true, delivery_fee_factor } + } + + fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: false, delivery_fee_factor } + } + + #[test] + fn initial_fee_factor_is_one() { + run_test(|| { + assert_eq!( + Bridge::::get(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR), + ); + }) + } + + #[test] + fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() { + run_test(|| { + Bridge::::put(uncongested_bridge(FixedU128::from_rational(125, 100))); + TestWithBridgeHubChannel::make_congested(); + + // it should not decrease, because xcm channel is congested + let old_bridge = XcmBridgeHubRouter::bridge(); + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge); + }) + } + + #[test] + fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() { + run_test(|| { + Bridge::::put(congested_bridge(FixedU128::from_rational(125, 100))); + + // it should not decrease, because bridge congested + let old_bridge = XcmBridgeHubRouter::bridge(); + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge); + }) + } + + #[test] + fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() { + run_test(|| { + Bridge::::put(uncongested_bridge(FixedU128::from_rational(125, 100))); + + // it shold eventually decreased to one + while XcmBridgeHubRouter::bridge().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR { + XcmBridgeHubRouter::on_initialize(One::one()); + } + + // verify that it doesn't decreases anymore + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!( + XcmBridgeHubRouter::bridge(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR) + ); + }) + } + + #[test] + fn not_applicable_if_destination_is_within_other_network() { + run_test(|| { + assert_eq!( + send_xcm::( + MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))), + vec![].into(), + ), + Err(SendError::NotApplicable), + ); + }); + } + + #[test] + fn exceeds_max_message_size_if_size_is_above_hard_limit() { + run_test(|| { + assert_eq!( + send_xcm::( + MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))), + vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into(), + ), + Err(SendError::ExceedsMaxMessageSize), + ); + }); + } + + #[test] + fn returns_proper_delivery_price() { + run_test(|| { + let dest = MultiLocation::new(2, X1(GlobalConsensus(BridgedNetworkId::get()))); + let xcm: Xcm<()> = vec![ClearOrigin].into(); + let msg_size = xcm.encoded_size(); + + // initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE` + let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE; + assert_eq!( + XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm.clone())) + .unwrap() + .1 + .get(0), + Some(&(BridgeFeeAsset::get(), expected_fee).into()), + ); + + // but when factor is larger than one, it increases the fee, so it becomes: + // `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE` + let factor = FixedU128::from_rational(125, 100); + Bridge::::put(uncongested_bridge(factor)); + let expected_fee = + (FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) * + factor) + .into_inner() / FixedU128::DIV + + HRMP_FEE; + assert_eq!( + XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm)).unwrap().1.get(0), + Some(&(BridgeFeeAsset::get(), expected_fee).into()), + ); + }); + } + + #[test] + fn sent_message_doesnt_increase_factor_if_xcm_channel_is_uncongested() { + run_test(|| { + let old_bridge = XcmBridgeHubRouter::bridge(); + assert_eq!( + send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop), + Ok(()), + ); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert_eq!(old_bridge, XcmBridgeHubRouter::bridge()); + }); + } + + #[test] + fn sent_message_increases_factor_if_xcm_channel_is_congested() { + run_test(|| { + TestWithBridgeHubChannel::make_congested(); + + let old_bridge = XcmBridgeHubRouter::bridge(); + assert_eq!( + send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop), + Ok(()), + ); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); + }); + } + + #[test] + fn sent_message_increases_factor_if_bridge_has_reported_congestion() { + run_test(|| { + Bridge::::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)); + + let old_bridge = XcmBridgeHubRouter::bridge(); + assert_eq!( + send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop), + Ok(()), + ); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); + }); + } +} diff --git a/cumulus/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/cumulus/bridges/modules/xcm-bridge-hub-router/src/mock.rs new file mode 100644 index 000000000000..5ad7be4890a1 --- /dev/null +++ b/cumulus/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -0,0 +1,148 @@ +// 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 . + +#![cfg(test)] + +use crate as pallet_xcm_bridge_hub_router; + +use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; +use frame_support::{construct_runtime, parameter_types}; +use frame_system::EnsureRoot; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, ConstU128, IdentityLookup}, + BuildStorage, +}; +use xcm::prelude::*; +use xcm_builder::NetworkExportTable; + +pub type AccountId = u64; +type Block = frame_system::mocking::MockBlock; + +/// HRMP fee. +pub const HRMP_FEE: u128 = 500; +/// Base bridge fee. +pub const BASE_FEE: u128 = 1_000_000; +/// Byte bridge fee. +pub const BYTE_FEE: u128 = 1_000; + +construct_runtime! { + pub enum TestRuntime + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + XcmBridgeHubRouter: pallet_xcm_bridge_hub_router::{Pallet, Storage}, + } +} + +parameter_types! { + pub ThisNetworkId: NetworkId = Polkadot; + pub BridgedNetworkId: NetworkId = Kusama; + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(ThisNetworkId::get()), Parachain(1000)); + pub SiblingBridgeHubLocation: MultiLocation = ParentThen(X1(Parachain(1002))).into(); + pub BridgeFeeAsset: AssetId = MultiLocation::parent().into(); + pub BridgeTable: Vec<(NetworkId, MultiLocation, Option)> + = vec![(BridgedNetworkId::get(), SiblingBridgeHubLocation::get(), Some((BridgeFeeAsset::get(), BASE_FEE).into()))]; +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { + type WeightInfo = (); + + type UniversalLocation = UniversalLocation; + type BridgedNetworkId = BridgedNetworkId; + type Bridges = NetworkExportTable; + + type BridgeHubOrigin = EnsureRoot; + type ToBridgeHubSender = TestToBridgeHubSender; + type WithBridgeHubChannel = TestWithBridgeHubChannel; + + type ByteFee = ConstU128; + type FeeAsset = BridgeFeeAsset; +} + +pub struct TestToBridgeHubSender; + +impl TestToBridgeHubSender { + pub fn is_message_sent() -> bool { + frame_support::storage::unhashed::get_or_default(b"TestToBridgeHubSender.Sent") + } +} + +impl SendXcm for TestToBridgeHubSender { + type Ticket = (); + + fn validate( + _destination: &mut Option, + _message: &mut Option>, + ) -> SendResult { + Ok(((), (BridgeFeeAsset::get(), HRMP_FEE).into())) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + frame_support::storage::unhashed::put(b"TestToBridgeHubSender.Sent", &true); + Ok([0u8; 32]) + } +} + +pub struct TestWithBridgeHubChannel; + +impl TestWithBridgeHubChannel { + pub fn make_congested() { + frame_support::storage::unhashed::put(b"TestWithBridgeHubChannel.Congested", &true); + } +} + +impl XcmChannelStatusProvider for TestWithBridgeHubChannel { + fn is_congested() -> bool { + frame_support::storage::unhashed::get_or_default(b"TestWithBridgeHubChannel.Congested") + } +} + +/// Return test externalities to use in tests. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + sp_io::TestExternalities::new(t) +} + +/// Run pallet test. +pub fn run_test(test: impl FnOnce() -> T) -> T { + new_test_ext().execute_with(|| test()) +} diff --git a/cumulus/bridges/modules/xcm-bridge-hub-router/src/weights.rs b/cumulus/bridges/modules/xcm-bridge-hub-router/src/weights.rs new file mode 100644 index 000000000000..04909f3b2e83 --- /dev/null +++ b/cumulus/bridges/modules/xcm-bridge-hub-router/src/weights.rs @@ -0,0 +1,208 @@ +// 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 . + +//! Autogenerated weights for pallet_xcm_bridge_hub_router +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/millau-bridge-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_xcm_bridge_hub_router +// --extrinsic=* +// --execution=wasm +// --wasm-execution=Compiled +// --heap-pages=4096 +// --output=./modules/xcm-bridge-hub-router/src/weights.rs +// --template=./.maintain/bridge-weight-template.hbs + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xcm_bridge_hub_router. +pub trait WeightInfo { + fn on_initialize_when_non_congested() -> Weight; + fn on_initialize_when_congested() -> Weight; + fn report_bridge_status() -> Weight; + fn send_message() -> Weight; +} + +/// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn on_initialize_when_non_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `3518` + // Minimum execution time: 11_934 nanoseconds. + Weight::from_parts(12_201_000, 3518) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn on_initialize_when_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `94` + // Estimated: `3559` + // Minimum execution time: 9_010 nanoseconds. + Weight::from_parts(9_594_000, 3559) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn send_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 19_709 nanoseconds. + Weight::from_parts(20_110_000, 3517) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn on_initialize_when_non_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `3518` + // Minimum execution time: 11_934 nanoseconds. + Weight::from_parts(12_201_000, 3518) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn on_initialize_when_congested() -> Weight { + // Proof Size summary in bytes: + // Measured: `94` + // Estimated: `3559` + // Minimum execution time: 9_010 nanoseconds. + Weight::from_parts(9_594_000, 3559) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + /// + /// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` + /// (r:1 w:0) + /// + /// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1 + /// w:0) + fn send_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 19_709 nanoseconds. + Weight::from_parts(20_110_000, 3517) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/cumulus/bridges/primitives/chain-asset-hub-kusama/Cargo.toml b/cumulus/bridges/primitives/chain-asset-hub-kusama/Cargo.toml new file mode 100644 index 000000000000..6d5a4207ee66 --- /dev/null +++ b/cumulus/bridges/primitives/chain-asset-hub-kusama/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-asset-hub-kusama" +description = "Primitives of AssetHubKusama parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } + +# Substrate Dependencies +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Bridge Dependencies +bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-xcm-bridge-hub-router/std", + "frame-support/std", + "codec/std", + "scale-info/std", +] diff --git a/cumulus/bridges/primitives/chain-asset-hub-kusama/src/lib.rs b/cumulus/bridges/primitives/chain-asset-hub-kusama/src/lib.rs new file mode 100644 index 000000000000..b3b25ba6eddd --- /dev/null +++ b/cumulus/bridges/primitives/chain-asset-hub-kusama/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2022 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 . + +//! Module with configuration which reflects AssetHubKusama runtime setup. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; + +/// `AssetHubKusama` Runtime `Call` enum. +/// +/// The enum represents a subset of possible `Call`s we can send to `AssetHubKusama` chain. +/// Ideally this code would be auto-generated from metadata, because we want to +/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. +/// +/// All entries here (like pretty much in the entire file) must be kept in sync with +/// `AssetHubKusama` `construct_runtime`, so that we maintain SCALE-compatibility. +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum Call { + /// `ToPolkadotXcmRouter` bridge pallet. + #[codec(index = 43)] + ToPolkadotXcmRouter(XcmBridgeHubRouterCall), +} + +frame_support::parameter_types! { + /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. + pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); + + /// Base delivery fee to `BridgeHubKusama`. + /// (initially was calculated `170733333` + `10%` by test `BridgeHubKusama::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) + pub const BridgeHubKusamaBaseFeeInDots: u128 = 187806666; +} diff --git a/cumulus/bridges/primitives/chain-asset-hub-polkadot/Cargo.toml b/cumulus/bridges/primitives/chain-asset-hub-polkadot/Cargo.toml new file mode 100644 index 000000000000..4ab562c6b34c --- /dev/null +++ b/cumulus/bridges/primitives/chain-asset-hub-polkadot/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-asset-hub-polkadot" +description = "Primitives of AssetHubPolkadot parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } + +# Substrate Dependencies +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Bridge Dependencies +bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-xcm-bridge-hub-router/std", + "frame-support/std", + "codec/std", + "scale-info/std", +] diff --git a/cumulus/bridges/primitives/chain-asset-hub-polkadot/src/lib.rs b/cumulus/bridges/primitives/chain-asset-hub-polkadot/src/lib.rs new file mode 100644 index 000000000000..7363e5af02a2 --- /dev/null +++ b/cumulus/bridges/primitives/chain-asset-hub-polkadot/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2022 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 . + +//! Module with configuration which reflects AssetHubPolkadot runtime setup. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; + +/// `AssetHubPolkadot` Runtime `Call` enum. +/// +/// The enum represents a subset of possible `Call`s we can send to `AssetHubPolkadot` chain. +/// Ideally this code would be auto-generated from metadata, because we want to +/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. +/// +/// All entries here (like pretty much in the entire file) must be kept in sync with +/// `AssetHubPolkadot` `construct_runtime`, so that we maintain SCALE-compatibility. +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum Call { + /// `ToKusamaXcmRouter` bridge pallet. + #[codec(index = 43)] + ToKusamaXcmRouter(XcmBridgeHubRouterCall), +} + +frame_support::parameter_types! { + /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. + pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); + + /// Base delivery fee to `BridgeHubPolkadot`. + /// (initially was calculated `51220000` + `10%` by test `BridgeHubPolkadot::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) + pub const BridgeHubPolkadotBaseFeeInDots: u128 = 56342000; +} diff --git a/cumulus/bridges/primitives/header-chain/src/justification/mod.rs b/cumulus/bridges/primitives/header-chain/src/justification/mod.rs index 17be6cfd7961..5fa5d7d607c8 100644 --- a/cumulus/bridges/primitives/header-chain/src/justification/mod.rs +++ b/cumulus/bridges/primitives/header-chain/src/justification/mod.rs @@ -23,10 +23,11 @@ mod verification; use crate::ChainWithGrandpa; pub use verification::{ - equivocation::{EquivocationsCollector, Error as EquivocationsCollectorError}, + equivocation::{EquivocationsCollector, GrandpaEquivocationsFinder}, optimizer::verify_and_optimize_justification, strict::verify_justification, - AncestryChain, Error as JustificationVerificationError, PrecommitError, + AncestryChain, Error as JustificationVerificationError, JustificationVerificationContext, + PrecommitError, }; use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId}; diff --git a/cumulus/bridges/primitives/header-chain/src/justification/verification/equivocation.rs b/cumulus/bridges/primitives/header-chain/src/justification/verification/equivocation.rs index 0ade3736c227..2484fc4d6b2b 100644 --- a/cumulus/bridges/primitives/header-chain/src/justification/verification/equivocation.rs +++ b/cumulus/bridges/primitives/header-chain/src/justification/verification/equivocation.rs @@ -16,33 +16,26 @@ //! Logic for extracting equivocations from multiple GRANDPA Finality Proofs. -use crate::justification::{ - verification::{ - Error as JustificationVerificationError, JustificationVerifier, PrecommitError, - SignedPrecommit, +use crate::{ + justification::{ + verification::{ + Error as JustificationVerificationError, IterationFlow, + JustificationVerificationContext, JustificationVerifier, PrecommitError, + SignedPrecommit, + }, + GrandpaJustification, }, - GrandpaJustification, + ChainWithGrandpa, FindEquivocations, }; -use crate::justification::verification::IterationFlow; -use finality_grandpa::voter_set::VoterSet; -use frame_support::RuntimeDebug; -use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit, SetId}; +use bp_runtime::{BlockNumberOf, HashOf, HeaderOf}; +use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit}; use sp_runtime::traits::Header as HeaderT; use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, prelude::*, }; -/// Justification verification error. -#[derive(Eq, RuntimeDebug, PartialEq)] -pub enum Error { - /// Justification is targeting unexpected round. - InvalidRound, - /// Justification verification error. - JustificationVerification(JustificationVerificationError), -} - enum AuthorityVotes { SingleVote(SignedPrecommit
), Equivocation( @@ -53,8 +46,7 @@ enum AuthorityVotes { /// Structure that can extract equivocations from multiple GRANDPA justifications. pub struct EquivocationsCollector<'a, Header: HeaderT> { round: u64, - authorities_set_id: SetId, - authorities_set: &'a VoterSet, + context: &'a JustificationVerificationContext, votes: BTreeMap>, } @@ -62,38 +54,34 @@ pub struct EquivocationsCollector<'a, Header: HeaderT> { impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> { /// Create a new instance of `EquivocationsCollector`. pub fn new( - authorities_set_id: SetId, - authorities_set: &'a VoterSet, + context: &'a JustificationVerificationContext, base_justification: &GrandpaJustification
, - ) -> Result { - let mut checker = Self { - round: base_justification.round, - authorities_set_id, - authorities_set, - votes: BTreeMap::new(), - }; - - checker.parse_justification(base_justification)?; + ) -> Result { + let mut checker = Self { round: base_justification.round, context, votes: BTreeMap::new() }; + + checker.verify_justification( + (base_justification.commit.target_hash, base_justification.commit.target_number), + checker.context, + base_justification, + )?; + Ok(checker) } - /// Parse an additional justification for equivocations. - pub fn parse_justification( - &mut self, - justification: &GrandpaJustification
, - ) -> Result<(), Error> { - // The justification should target the same round as the base justification. - if self.round != justification.round { - return Err(Error::InvalidRound) + /// Parse additional justifications for equivocations. + pub fn parse_justifications(&mut self, justifications: &[GrandpaJustification
]) { + let round = self.round; + for justification in + justifications.iter().filter(|justification| round == justification.round) + { + // We ignore the Errors received here since we don't care if the proofs are valid. + // We only care about collecting equivocations. + let _ = self.verify_justification( + (justification.commit.target_hash, justification.commit.target_number), + self.context, + justification, + ); } - - self.verify_justification( - (justification.commit.target_hash, justification.commit.target_number), - self.authorities_set_id, - self.authorities_set, - justification, - ) - .map_err(Error::JustificationVerification) } /// Extract the equivocation proofs that have been collected. @@ -102,7 +90,7 @@ impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> { for (_authority, vote) in self.votes { if let AuthorityVotes::Equivocation(equivocation) = vote { equivocations.push(EquivocationProof::new( - self.authorities_set_id, + self.context.authority_set_id, sp_consensus_grandpa::Equivocation::Precommit(equivocation), )); } @@ -177,3 +165,29 @@ impl<'a, Header: HeaderT> JustificationVerifier
for EquivocationsCollect Ok(()) } } + +/// Helper struct for finding equivocations in GRANDPA proofs. +pub struct GrandpaEquivocationsFinder(sp_std::marker::PhantomData); + +impl + FindEquivocations< + GrandpaJustification>, + JustificationVerificationContext, + EquivocationProof, BlockNumberOf>, + > for GrandpaEquivocationsFinder +{ + type Error = JustificationVerificationError; + + fn find_equivocations( + verification_context: &JustificationVerificationContext, + synced_proof: &GrandpaJustification>, + source_proofs: &[GrandpaJustification>], + ) -> Result, BlockNumberOf>>, Self::Error> { + let mut equivocations_collector = + EquivocationsCollector::new(verification_context, synced_proof)?; + + equivocations_collector.parse_justifications(source_proofs); + + Ok(equivocations_collector.into_equivocation_proofs()) + } +} diff --git a/cumulus/bridges/primitives/header-chain/src/justification/verification/mod.rs b/cumulus/bridges/primitives/header-chain/src/justification/verification/mod.rs index 7cec1f14e966..7af5292256cb 100644 --- a/cumulus/bridges/primitives/header-chain/src/justification/verification/mod.rs +++ b/cumulus/bridges/primitives/header-chain/src/justification/verification/mod.rs @@ -20,7 +20,7 @@ pub mod equivocation; pub mod optimizer; pub mod strict; -use crate::justification::GrandpaJustification; +use crate::{justification::GrandpaJustification, AuthoritySet}; use bp_runtime::HeaderId; use finality_grandpa::voter_set::VoterSet; @@ -114,6 +114,8 @@ impl AncestryChain
{ /// Justification verification error. #[derive(Eq, RuntimeDebug, PartialEq)] pub enum Error { + /// Could not convert `AuthorityList` to `VoterSet`. + InvalidAuthorityList, /// Justification is finalizing unexpected header. InvalidJustificationTarget, /// Error validating a precommit @@ -141,6 +143,24 @@ pub enum PrecommitError { UnrelatedAncestryVote, } +/// The context needed for validating GRANDPA finality proofs. +pub struct JustificationVerificationContext { + /// The authority set used to verify the justification. + pub voter_set: VoterSet, + /// The ID of the authority set used to verify the justification. + pub authority_set_id: SetId, +} + +impl TryFrom for JustificationVerificationContext { + type Error = Error; + + fn try_from(authority_set: AuthoritySet) -> Result { + let voter_set = + VoterSet::new(authority_set.authorities).ok_or(Error::InvalidAuthorityList)?; + Ok(JustificationVerificationContext { voter_set, authority_set_id: authority_set.set_id }) + } +} + enum IterationFlow { Run, Skip, @@ -185,8 +205,7 @@ trait JustificationVerifier { fn verify_justification( &mut self, finalized_target: (Header::Hash, Header::Number), - authorities_set_id: SetId, - authorities_set: &VoterSet, + context: &JustificationVerificationContext, justification: &GrandpaJustification
, ) -> Result<(), Error> { // ensure that it is justification for the expected header @@ -196,7 +215,7 @@ trait JustificationVerifier { return Err(Error::InvalidJustificationTarget) } - let threshold = authorities_set.threshold().get(); + let threshold = context.voter_set.threshold().get(); let mut chain = AncestryChain::new(justification); let mut signature_buffer = Vec::new(); let mut cumulative_weight = 0u64; @@ -211,7 +230,7 @@ trait JustificationVerifier { } // authority must be in the set - let authority_info = match authorities_set.get(&signed.id) { + let authority_info = match context.voter_set.get(&signed.id) { Some(authority_info) => { // The implementer may want to do extra checks here. // For example to see if the authority has already voted in the same round. @@ -248,7 +267,7 @@ trait JustificationVerifier { &signed.id, &signed.signature, justification.round, - authorities_set_id, + context.authority_set_id, &mut signature_buffer, ) { self.process_invalid_signature_vote(precommit_idx).map_err(Error::Precommit)?; diff --git a/cumulus/bridges/primitives/header-chain/src/justification/verification/optimizer.rs b/cumulus/bridges/primitives/header-chain/src/justification/verification/optimizer.rs index 4cc6778ff511..99ccdd50616b 100644 --- a/cumulus/bridges/primitives/header-chain/src/justification/verification/optimizer.rs +++ b/cumulus/bridges/primitives/header-chain/src/justification/verification/optimizer.rs @@ -21,9 +21,10 @@ use crate::justification::{ GrandpaJustification, }; -use crate::justification::verification::{IterationFlow, SignedPrecommit}; -use finality_grandpa::voter_set::VoterSet; -use sp_consensus_grandpa::{AuthorityId, SetId}; +use crate::justification::verification::{ + IterationFlow, JustificationVerificationContext, SignedPrecommit, +}; +use sp_consensus_grandpa::AuthorityId; use sp_runtime::traits::Header as HeaderT; use sp_std::{collections::btree_set::BTreeSet, prelude::*}; @@ -111,8 +112,7 @@ impl JustificationVerifier
for JustificationOptimizer( finalized_target: (Header::Hash, Header::Number), - authorities_set_id: SetId, - authorities_set: &VoterSet, + context: &JustificationVerificationContext, justification: &mut GrandpaJustification
, ) -> Result<(), Error> { let mut optimizer = JustificationOptimizer { @@ -120,12 +120,7 @@ pub fn verify_and_optimize_justification( extra_precommits: vec![], redundant_votes_ancestries: Default::default(), }; - optimizer.verify_justification( - finalized_target, - authorities_set_id, - authorities_set, - justification, - )?; + optimizer.verify_justification(finalized_target, context, justification)?; optimizer.optimize(justification); Ok(()) diff --git a/cumulus/bridges/primitives/header-chain/src/justification/verification/strict.rs b/cumulus/bridges/primitives/header-chain/src/justification/verification/strict.rs index da936c235827..a9d5f4c1f736 100644 --- a/cumulus/bridges/primitives/header-chain/src/justification/verification/strict.rs +++ b/cumulus/bridges/primitives/header-chain/src/justification/verification/strict.rs @@ -21,9 +21,10 @@ use crate::justification::{ GrandpaJustification, }; -use crate::justification::verification::{IterationFlow, SignedPrecommit}; -use finality_grandpa::voter_set::VoterSet; -use sp_consensus_grandpa::{AuthorityId, SetId}; +use crate::justification::verification::{ + IterationFlow, JustificationVerificationContext, SignedPrecommit, +}; +use sp_consensus_grandpa::AuthorityId; use sp_runtime::traits::Header as HeaderT; use sp_std::collections::btree_set::BTreeSet; @@ -92,15 +93,9 @@ impl JustificationVerifier
for StrictJustificationVerif /// Verify that justification, that is generated by given authority set, finalizes given header. pub fn verify_justification( finalized_target: (Header::Hash, Header::Number), - authorities_set_id: SetId, - authorities_set: &VoterSet, + context: &JustificationVerificationContext, justification: &GrandpaJustification
, ) -> Result<(), Error> { let mut verifier = StrictJustificationVerifier { votes: BTreeSet::new() }; - verifier.verify_justification( - finalized_target, - authorities_set_id, - authorities_set, - justification, - ) + verifier.verify_justification(finalized_target, context, justification) } diff --git a/cumulus/bridges/primitives/header-chain/src/lib.rs b/cumulus/bridges/primitives/header-chain/src/lib.rs index 720dafdd93b0..ea6c58f4c097 100644 --- a/cumulus/bridges/primitives/header-chain/src/lib.rs +++ b/cumulus/bridges/primitives/header-chain/src/lib.rs @@ -19,6 +19,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::justification::{ + GrandpaJustification, JustificationVerificationContext, JustificationVerificationError, +}; use bp_runtime::{ BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof, StorageProofChecker, StorageProofError, UnderlyingChainProvider, @@ -30,7 +33,7 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_consensus_grandpa::{AuthorityList, ConsensusLog, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug}; -use sp_std::boxed::Box; +use sp_std::{boxed::Box, vec::Vec}; pub mod justification; pub mod storage_keys; @@ -172,13 +175,48 @@ impl ConsensusLogReader for GrandpaConsensusLogReader { } } -/// The Grandpa-related info associated to a header. +/// The finality-related info associated to a header. #[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)] -pub struct HeaderGrandpaInfo { - /// The header justification - pub justification: justification::GrandpaJustification
, - /// The authority set introduced by the header. - pub authority_set: Option, +pub struct HeaderFinalityInfo { + /// The header finality proof. + pub finality_proof: FinalityProof, + /// The new verification context introduced by the header. + pub new_verification_context: Option, +} + +/// Grandpa-related info associated to a header. This info can be saved to events. +pub type StoredHeaderGrandpaInfo
= + HeaderFinalityInfo, AuthoritySet>; + +/// Processed Grandpa-related info associated to a header. +pub type HeaderGrandpaInfo
= + HeaderFinalityInfo, JustificationVerificationContext>; + +impl TryFrom> for HeaderGrandpaInfo
{ + type Error = JustificationVerificationError; + + fn try_from(grandpa_info: StoredHeaderGrandpaInfo
) -> Result { + Ok(Self { + finality_proof: grandpa_info.finality_proof, + new_verification_context: match grandpa_info.new_verification_context { + Some(authority_set) => Some(authority_set.try_into()?), + None => None, + }, + }) + } +} + +/// Helper trait for finding equivocations in finality proofs. +pub trait FindEquivocations { + /// The type returned when encountering an error while looking for equivocations. + type Error; + + /// Find equivocations. + fn find_equivocations( + verification_context: &FinalityVerificationContext, + synced_proof: &FinalityProof, + source_proofs: &[FinalityProof], + ) -> Result, Self::Error>; } /// A minimized version of `pallet-bridge-grandpa::Call` that can be used without a runtime. @@ -244,16 +282,17 @@ pub trait ChainWithGrandpa: Chain { const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32; } -/// A trait that provides the type of the underlying `ChainWithGrandpa`. -pub trait UnderlyingChainWithGrandpaProvider: UnderlyingChainProvider { - /// Underlying `ChainWithGrandpa` type. - type ChainWithGrandpa: ChainWithGrandpa; -} - -impl UnderlyingChainWithGrandpaProvider for T +impl ChainWithGrandpa for T where - T: UnderlyingChainProvider, + T: Chain + UnderlyingChainProvider, T::Chain: ChainWithGrandpa, { - type ChainWithGrandpa = T::Chain; + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = + ::WITH_CHAIN_GRANDPA_PALLET_NAME; + const MAX_AUTHORITIES_COUNT: u32 = ::MAX_AUTHORITIES_COUNT; + const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = + ::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; + const MAX_HEADER_SIZE: u32 = ::MAX_HEADER_SIZE; + const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = + ::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; } diff --git a/cumulus/bridges/primitives/header-chain/tests/implementation_match.rs b/cumulus/bridges/primitives/header-chain/tests/implementation_match.rs index c4cd7f5f5b26..db96961832d4 100644 --- a/cumulus/bridges/primitives/header-chain/tests/implementation_match.rs +++ b/cumulus/bridges/primitives/header-chain/tests/implementation_match.rs @@ -22,14 +22,15 @@ //! but their purpose is different. use bp_header_chain::justification::{ - verify_justification, GrandpaJustification, JustificationVerificationError, PrecommitError, + verify_justification, GrandpaJustification, JustificationVerificationContext, + JustificationVerificationError, PrecommitError, }; use bp_test_utils::{ header_id, make_justification_for_header, signed_precommit, test_header, Account, JustificationGeneratorParams, ALICE, BOB, CHARLIE, DAVE, EVE, FERDIE, TEST_GRANDPA_SET_ID, }; use finality_grandpa::voter_set::VoterSet; -use sp_consensus_grandpa::{AuthorityId, AuthorityWeight}; +use sp_consensus_grandpa::{AuthorityId, AuthorityWeight, SetId}; use sp_runtime::traits::Header as HeaderT; type TestHeader = sp_runtime::testing::Header; @@ -81,6 +82,11 @@ fn full_voter_set() -> VoterSet { VoterSet::new(full_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w))).unwrap() } +pub fn full_verification_context(set_id: SetId) -> JustificationVerificationContext { + let voter_set = full_voter_set(); + JustificationVerificationContext { voter_set, authority_set_id: set_id } +} + /// Get a minimal set of accounts. fn minimal_accounts_set() -> Vec<(Account, AuthorityWeight)> { // there are 5 accounts in the full set => we need 2/3 + 1 accounts, which results in 4 accounts @@ -115,8 +121,7 @@ fn same_result_when_precommit_target_has_lower_number_than_commit_target() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)), @@ -148,8 +153,7 @@ fn same_result_when_precommit_target_is_not_descendant_of_commit_target() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)), @@ -182,8 +186,7 @@ fn same_result_when_there_are_not_enough_cumulative_weight_to_finalize_commit_ta assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::TooLowCumulativeWeight), @@ -220,8 +223,7 @@ fn different_result_when_justification_contains_duplicate_vote() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)), @@ -261,8 +263,7 @@ fn different_results_when_authority_equivocates_once_in_a_round() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)), @@ -314,8 +315,7 @@ fn different_results_when_authority_equivocates_twice_in_a_round() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)), @@ -353,8 +353,7 @@ fn different_results_when_there_are_more_than_enough_votes() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::RedundantAuthorityVote)), @@ -394,8 +393,7 @@ fn different_results_when_there_is_a_vote_of_unknown_authority() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &full_voter_set(), + &full_verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::UnknownAuthorityVote)), diff --git a/cumulus/bridges/primitives/header-chain/tests/justification/equivocation.rs b/cumulus/bridges/primitives/header-chain/tests/justification/equivocation.rs index 072d5668edeb..f3c133481fb8 100644 --- a/cumulus/bridges/primitives/header-chain/tests/justification/equivocation.rs +++ b/cumulus/bridges/primitives/header-chain/tests/justification/equivocation.rs @@ -25,19 +25,18 @@ type TestHeader = sp_runtime::testing::Header; #[test] fn duplicate_votes_are_not_considered_equivocations() { - let voter_set = voter_set(); + let verification_context = verification_context(TEST_GRANDPA_SET_ID); let base_justification = make_default_justification::(&test_header(1)); let mut collector = - EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap(); - collector.parse_justification(&base_justification.clone()).unwrap(); + EquivocationsCollector::new(&verification_context, &base_justification).unwrap(); + collector.parse_justifications(&[base_justification.clone()]); assert_eq!(collector.into_equivocation_proofs().len(), 0); } #[test] fn equivocations_are_detected_in_base_justification_redundant_votes() { - let voter_set = voter_set(); let mut base_justification = make_default_justification::(&test_header(1)); let first_vote = base_justification.commit.precommits[0].clone(); @@ -49,8 +48,9 @@ fn equivocations_are_detected_in_base_justification_redundant_votes() { ); base_justification.commit.precommits.push(equivocation.clone()); + let verification_context = verification_context(TEST_GRANDPA_SET_ID); let collector = - EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap(); + EquivocationsCollector::new(&verification_context, &base_justification).unwrap(); assert_eq!( collector.into_equivocation_proofs(), @@ -80,7 +80,6 @@ fn equivocations_are_detected_in_base_justification_redundant_votes() { #[test] fn equivocations_are_detected_in_extra_justification_redundant_votes() { - let voter_set = voter_set(); let base_justification = make_default_justification::(&test_header(1)); let first_vote = base_justification.commit.precommits[0].clone(); @@ -93,9 +92,10 @@ fn equivocations_are_detected_in_extra_justification_redundant_votes() { ); extra_justification.commit.precommits.push(equivocation.clone()); + let verification_context = verification_context(TEST_GRANDPA_SET_ID); let mut collector = - EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap(); - collector.parse_justification(&extra_justification).unwrap(); + EquivocationsCollector::new(&verification_context, &base_justification).unwrap(); + collector.parse_justifications(&[extra_justification]); assert_eq!( collector.into_equivocation_proofs(), diff --git a/cumulus/bridges/primitives/header-chain/tests/justification/optimizer.rs b/cumulus/bridges/primitives/header-chain/tests/justification/optimizer.rs index 8d1ba5ac6fac..bdc90a3b07cf 100644 --- a/cumulus/bridges/primitives/header-chain/tests/justification/optimizer.rs +++ b/cumulus/bridges/primitives/header-chain/tests/justification/optimizer.rs @@ -30,8 +30,7 @@ fn optimizer_does_noting_with_minimal_justification() { let num_precommits_before = justification.commit.precommits.len(); verify_and_optimize_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); @@ -53,8 +52,7 @@ fn unknown_authority_votes_are_removed_by_optimizer() { let num_precommits_before = justification.commit.precommits.len(); verify_and_optimize_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); @@ -74,8 +72,7 @@ fn duplicate_authority_votes_are_removed_by_optimizer() { let num_precommits_before = justification.commit.precommits.len(); verify_and_optimize_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); @@ -105,8 +102,7 @@ fn invalid_authority_signatures_are_removed_by_optimizer() { let num_precommits_before = justification.commit.precommits.len(); verify_and_optimize_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); @@ -128,8 +124,7 @@ fn redundant_authority_votes_are_removed_by_optimizer() { let num_precommits_before = justification.commit.precommits.len(); verify_and_optimize_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); @@ -154,8 +149,7 @@ fn unrelated_ancestry_votes_are_removed_by_optimizer() { let num_precommits_before = justification.commit.precommits.len(); verify_and_optimize_justification::( header_id::(2), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); @@ -172,8 +166,7 @@ fn redundant_votes_ancestries_are_removed_by_optimizer() { let num_votes_ancestries_before = justification.votes_ancestries.len(); verify_and_optimize_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &mut justification, ) .unwrap(); diff --git a/cumulus/bridges/primitives/header-chain/tests/justification/strict.rs b/cumulus/bridges/primitives/header-chain/tests/justification/strict.rs index bf8fa5c9f457..9568f7f7cf9a 100644 --- a/cumulus/bridges/primitives/header-chain/tests/justification/strict.rs +++ b/cumulus/bridges/primitives/header-chain/tests/justification/strict.rs @@ -17,8 +17,8 @@ //! Tests for Grandpa strict justification verifier code. use bp_header_chain::justification::{ - required_justification_precommits, verify_justification, JustificationVerificationError, - PrecommitError, + required_justification_precommits, verify_justification, JustificationVerificationContext, + JustificationVerificationError, PrecommitError, }; use bp_test_utils::*; @@ -40,8 +40,7 @@ fn valid_justification_accepted() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &justification, ), Ok(()), @@ -65,8 +64,7 @@ fn valid_justification_accepted_with_single_fork() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &make_justification_for_header::(params) ), Ok(()), @@ -100,8 +98,7 @@ fn valid_justification_accepted_with_arbitrary_number_of_authorities() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set, + &JustificationVerificationContext { voter_set, authority_set_id: TEST_GRANDPA_SET_ID }, &make_justification_for_header::(params) ), Ok(()), @@ -113,8 +110,7 @@ fn justification_with_invalid_target_rejected() { assert_eq!( verify_justification::( header_id::(2), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &make_default_justification::(&test_header(1)), ), Err(JustificationVerificationError::InvalidJustificationTarget), @@ -129,8 +125,7 @@ fn justification_with_invalid_commit_rejected() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::TooLowCumulativeWeight), @@ -146,8 +141,7 @@ fn justification_with_invalid_authority_signature_rejected() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::Precommit(PrecommitError::InvalidAuthoritySignature)), @@ -162,8 +156,7 @@ fn justification_with_invalid_precommit_ancestry() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &justification, ), Err(JustificationVerificationError::RedundantVotesAncestries), @@ -187,8 +180,7 @@ fn justification_is_invalid_if_we_dont_meet_threshold() { assert_eq!( verify_justification::( header_id::(1), - TEST_GRANDPA_SET_ID, - &voter_set(), + &verification_context(TEST_GRANDPA_SET_ID), &make_justification_for_header::(params) ), Err(JustificationVerificationError::TooLowCumulativeWeight), diff --git a/cumulus/bridges/primitives/messages/src/lib.rs b/cumulus/bridges/primitives/messages/src/lib.rs index cb3a14572d72..84c41de3b361 100644 --- a/cumulus/bridges/primitives/messages/src/lib.rs +++ b/cumulus/bridges/primitives/messages/src/lib.rs @@ -387,6 +387,14 @@ impl Default for OutboundLaneData { } } +impl OutboundLaneData { + /// Return nonces of all currently queued messages (i.e. messages that we believe + /// are not delivered yet). + pub fn queued_messages(&self) -> RangeInclusive { + (self.latest_received_nonce + 1)..=self.latest_generated_nonce + } +} + /// Calculate the number of messages that the relayers have delivered. pub fn calc_relayers_rewards( messages_relayers: VecDeque>, diff --git a/cumulus/bridges/primitives/messages/src/source_chain.rs b/cumulus/bridges/primitives/messages/src/source_chain.rs index 09f6396ad742..3cc78522baf1 100644 --- a/cumulus/bridges/primitives/messages/src/source_chain.rs +++ b/cumulus/bridges/primitives/messages/src/source_chain.rs @@ -115,11 +115,26 @@ impl DeliveryConfirmationPayments for () { } } +/// Callback that is called at the source chain (bridge hub) when we get delivery confirmation +/// for new messages. +pub trait OnMessagesDelivered { + /// New messages delivery has been confirmed. + /// + /// The only argument of the function is the number of yet undelivered messages + fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); +} + +impl OnMessagesDelivered for () { + fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {} +} + /// Send message artifacts. #[derive(Eq, RuntimeDebug, PartialEq)] pub struct SendMessageArtifacts { /// Nonce of the message. pub nonce: MessageNonce, + /// Number of enqueued messages at the lane, after the message is sent. + pub enqueued_messages: MessageNonce, } /// Messages bridge API to be used from other pallets. @@ -141,7 +156,7 @@ impl MessagesBridge for NoopMessagesBridge { type Error = &'static str; fn send_message(_lane: LaneId, _message: Payload) -> Result { - Ok(SendMessageArtifacts { nonce: 0 }) + Ok(SendMessageArtifacts { nonce: 0, enqueued_messages: 0 }) } } diff --git a/cumulus/bridges/primitives/messages/src/target_chain.rs b/cumulus/bridges/primitives/messages/src/target_chain.rs index 385bc4ac373d..60f5c1b3c1c5 100644 --- a/cumulus/bridges/primitives/messages/src/target_chain.rs +++ b/cumulus/bridges/primitives/messages/src/target_chain.rs @@ -91,6 +91,15 @@ pub trait MessageDispatch { /// Fine-grained result of single message dispatch (for better diagnostic purposes) type DispatchLevelResult: Clone + sp_std::fmt::Debug + Eq; + /// Returns `true` if dispatcher is ready to accept additional messages. The `false` should + /// be treated as a hint by both dispatcher and its consumers - i.e. dispatcher shall not + /// simply drop messages if it returns `false`. The consumer may still call the `dispatch` + /// if dispatcher has returned `false`. + /// + /// We check it in the messages delivery transaction prologue. So if it becomes `false` + /// after some portion of messages is already dispatched, it doesn't fail the whole transaction. + fn is_active() -> bool; + /// Estimate dispatch weight. /// /// This function must return correct upper bound of dispatch weight. The return value @@ -186,6 +195,10 @@ impl MessageDispatch type DispatchPayload = DispatchPayload; type DispatchLevelResult = (); + fn is_active() -> bool { + false + } + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { Weight::MAX } diff --git a/cumulus/bridges/primitives/runtime/src/chain.rs b/cumulus/bridges/primitives/runtime/src/chain.rs index a1b7e8d54b4f..43fdaf8da1b7 100644 --- a/cumulus/bridges/primitives/runtime/src/chain.rs +++ b/cumulus/bridges/primitives/runtime/src/chain.rs @@ -321,7 +321,7 @@ macro_rules! decl_bridge_finality_runtime_apis { } }; ($chain: ident, grandpa) => { - decl_bridge_finality_runtime_apis!($chain, grandpa => bp_header_chain::HeaderGrandpaInfo
); + decl_bridge_finality_runtime_apis!($chain, grandpa => bp_header_chain::StoredHeaderGrandpaInfo
); }; } diff --git a/cumulus/bridges/primitives/test-utils/src/keyring.rs b/cumulus/bridges/primitives/test-utils/src/keyring.rs index b1782109668a..a4e818a3b888 100644 --- a/cumulus/bridges/primitives/test-utils/src/keyring.rs +++ b/cumulus/bridges/primitives/test-utils/src/keyring.rs @@ -16,10 +16,11 @@ //! Utilities for working with test accounts. +use bp_header_chain::{justification::JustificationVerificationContext, AuthoritySet}; use codec::Encode; use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature}; use finality_grandpa::voter_set::VoterSet; -use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight}; +use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId}; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; @@ -78,6 +79,11 @@ pub fn voter_set() -> VoterSet { VoterSet::new(authority_list()).unwrap() } +/// Get a valid justification verification context for a GRANDPA round. +pub fn verification_context(set_id: SetId) -> JustificationVerificationContext { + AuthoritySet { authorities: authority_list(), set_id }.try_into().unwrap() +} + /// Convenience function to get a list of Grandpa authorities. pub fn authority_list() -> AuthorityList { test_keyring().iter().map(|(id, w)| (AuthorityId::from(*id), *w)).collect() diff --git a/cumulus/bridges/primitives/xcm-bridge-hub-router/Cargo.toml b/cumulus/bridges/primitives/xcm-bridge-hub-router/Cargo.toml new file mode 100644 index 000000000000..ca17f52d169b --- /dev/null +++ b/cumulus/bridges/primitives/xcm-bridge-hub-router/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bp-xcm-bridge-hub-router" +description = "Primitives of the xcm-bridge-hub fee pallet." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive", "bit-vec"] } +scale-info = { version = "2.9.0", default-features = false, features = ["bit-vec", "derive"] } + +# Substrate Dependencies +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-runtime/std", + "sp-core/std", +] diff --git a/cumulus/bridges/primitives/xcm-bridge-hub-router/src/lib.rs b/cumulus/bridges/primitives/xcm-bridge-hub-router/src/lib.rs new file mode 100644 index 000000000000..0dd329f9e4a0 --- /dev/null +++ b/cumulus/bridges/primitives/xcm-bridge-hub-router/src/lib.rs @@ -0,0 +1,66 @@ +// 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 . + +//! Primitives of the `xcm-bridge-hub-router` pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::{FixedU128, RuntimeDebug}; + +/// Minimal delivery fee factor. +pub const MINIMAL_DELIVERY_FEE_FACTOR: FixedU128 = FixedU128::from_u32(1); + +/// XCM channel status provider that may report whether it is congested or not. +/// +/// By channel we mean the physical channel that is used to deliver messages of one +/// of the bridge queues. +pub trait XcmChannelStatusProvider { + /// Returns true if the channel is currently congested. + fn is_congested() -> bool; +} + +impl XcmChannelStatusProvider for () { + fn is_congested() -> bool { + false + } +} + +/// Current status of the bridge. +#[derive(Clone, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct BridgeState { + /// Current delivery fee factor. + pub delivery_fee_factor: FixedU128, + /// Bridge congestion flag. + pub is_congested: bool, +} + +impl Default for BridgeState { + fn default() -> BridgeState { + BridgeState { delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR, is_congested: false } + } +} + +/// A minimized version of `pallet-xcm-bridge-hub-router::Call` that can be used without a runtime. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum XcmBridgeHubRouterCall { + /// `pallet-xcm-bridge-hub-router::Call::report_bridge_status` + #[codec(index = 0)] + report_bridge_status { bridge_id: H256, is_congested: bool }, +} diff --git a/cumulus/bridges/scripts/verify-pallets-build.sh b/cumulus/bridges/scripts/verify-pallets-build.sh index 2230d68a9c4c..1ea3dbe86260 100755 --- a/cumulus/bridges/scripts/verify-pallets-build.sh +++ b/cumulus/bridges/scripts/verify-pallets-build.sh @@ -125,6 +125,9 @@ cargo check -p pallet-bridge-parachains --features try-runtime cargo check -p pallet-bridge-relayers cargo check -p pallet-bridge-relayers --features runtime-benchmarks cargo check -p pallet-bridge-relayers --features try-runtime +cargo check -p pallet-xcm-bridge-hub-router +cargo check -p pallet-xcm-bridge-hub-router --features runtime-benchmarks +cargo check -p pallet-xcm-bridge-hub-router --features try-runtime cargo check -p bridge-runtime-common cargo check -p bridge-runtime-common --features runtime-benchmarks cargo check -p bridge-runtime-common --features integrity-test diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index 196e282ec2f1..274951b3bd29 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -27,7 +27,7 @@ use bridge_runtime_common::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, MessageBridge, ThisChainWithMessages, UnderlyingChainProvider, }, - messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter}, refund_relayer_extension::{ ActualFeeRefund, RefundBridgedParachainMessages, RefundableMessagesLane, RefundableParachain, @@ -51,6 +51,11 @@ parameter_types! { pub WococoGlobalConsensusNetwork: NetworkId = NetworkId::Wococo; pub ActiveOutboundLanesToBridgeHubWococo: &'static [bp_messages::LaneId] = &[DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO]; pub PriorityBoostPerMessage: u64 = 921_900_294; + + pub FromAssetHubRococoToAssetHubWococoRoute: SenderAndLane = SenderAndLane::new( + ParentThen(X1(Parachain(1000))).into(), + DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, + ); } /// Proof of messages, coming from Wococo. @@ -75,12 +80,13 @@ pub type ToBridgeHubWococoHaulBlobExporter = HaulBlobExporter< >; pub struct ToBridgeHubWococoXcmBlobHauler; impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { - type MessageSender = - pallet_bridge_messages::Pallet; + type Runtime = Runtime; + type MessagesInstance = WithBridgeHubWococoMessagesInstance; + type SenderAndLane = FromAssetHubRococoToAssetHubWococoRoute; - fn xcm_lane() -> LaneId { - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO - } + type ToSourceChainSender = crate::XcmRouter; + type CongestedMessage = (); + type UncongestedMessage = (); } pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = LaneId([0, 0, 0, 1]); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index 178a022dd493..e6342af128ed 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -27,7 +27,7 @@ use bridge_runtime_common::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, MessageBridge, ThisChainWithMessages, UnderlyingChainProvider, }, - messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter}, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler, XcmBlobHaulerAdapter}, refund_relayer_extension::{ ActualFeeRefund, RefundBridgedParachainMessages, RefundableMessagesLane, RefundableParachain, @@ -51,6 +51,11 @@ parameter_types! { pub RococoGlobalConsensusNetwork: NetworkId = NetworkId::Rococo; pub ActiveOutboundLanesToBridgeHubRococo: &'static [bp_messages::LaneId] = &[DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO]; pub PriorityBoostPerMessage: u64 = 921_900_294; + + pub FromAssetHubWococoToAssetHubRococoRoute: SenderAndLane = SenderAndLane::new( + ParentThen(X1(Parachain(1000))).into(), + DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, + ); } /// Proof of messages, coming from Rococo. @@ -75,12 +80,13 @@ pub type ToBridgeHubRococoHaulBlobExporter = HaulBlobExporter< >; pub struct ToBridgeHubRococoXcmBlobHauler; impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { - type MessageSender = - pallet_bridge_messages::Pallet; + type Runtime = Runtime; + type MessagesInstance = WithBridgeHubRococoMessagesInstance; + type SenderAndLane = FromAssetHubWococoToAssetHubRococoRoute; - fn xcm_lane() -> LaneId { - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO - } + type ToSourceChainSender = crate::XcmRouter; + type CongestedMessage = (); + type UncongestedMessage = (); } pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = LaneId([0, 0, 0, 1]); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 27f5e14174c8..8c95072bdc26 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -488,7 +488,8 @@ impl pallet_bridge_messages::Config for Run type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = - XcmBlobMessageDispatch; + XcmBlobMessageDispatch; + type OnMessagesDelivered = (); } /// Add XCM messages support for BridgeHubWococo to support Wococo->Rococo XCM messages @@ -521,7 +522,8 @@ impl pallet_bridge_messages::Config for Run type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = - XcmBlobMessageDispatch; + XcmBlobMessageDispatch; + type OnMessagesDelivered = (); } /// Allows collect and claim rewards for relayers @@ -784,7 +786,7 @@ impl_runtime_apis! { BridgeRococoGrandpa::best_finalized() } fn synced_headers_grandpa_info( - ) -> Vec> { + ) -> Vec> { BridgeRococoGrandpa::synced_headers_grandpa_info() } } @@ -794,7 +796,7 @@ impl_runtime_apis! { BridgeWococoGrandpa::best_finalized() } fn synced_headers_grandpa_info( - ) -> Vec> { + ) -> Vec> { BridgeWococoGrandpa::synced_headers_grandpa_info() } }