diff --git a/bridges/bin/millau/runtime/src/rialto_messages.rs b/bridges/bin/millau/runtime/src/rialto_messages.rs index 9538f18f55426407eff51fdeae994a9578a21c35..caec651f70167e8eb2c23a46a6a4488d0ace72eb 100644 --- a/bridges/bin/millau/runtime/src/rialto_messages.rs +++ b/bridges/bin/millau/runtime/src/rialto_messages.rs @@ -167,6 +167,16 @@ impl messages::ChainWithMessageLanes for Millau { type MessageLaneInstance = pallet_message_lane::DefaultInstance; } +impl messages::ThisChainWithMessageLanes for Millau { + fn is_outbound_lane_enabled(lane: &LaneId) -> bool { + *lane == LaneId::default() + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MessageNonce::MAX + } +} + /// Rialto chain from message lane point of view. #[derive(RuntimeDebug, Clone, Copy)] pub struct Rialto; diff --git a/bridges/bin/rialto/runtime/src/millau_messages.rs b/bridges/bin/rialto/runtime/src/millau_messages.rs index 2810e888e7b8024961bfde6b3bf92c7b66edca22..9207d79edb78c7a07f411a83ab2401ed8f60cdb2 100644 --- a/bridges/bin/rialto/runtime/src/millau_messages.rs +++ b/bridges/bin/rialto/runtime/src/millau_messages.rs @@ -167,6 +167,16 @@ impl messages::ChainWithMessageLanes for Rialto { type MessageLaneInstance = crate::WithMillauMessageLaneInstance; } +impl messages::ThisChainWithMessageLanes for Rialto { + fn is_outbound_lane_enabled(lane: &LaneId) -> bool { + *lane == LaneId::default() + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MessageNonce::MAX + } +} + /// Millau chain from message lane point of view. #[derive(RuntimeDebug, Clone, Copy)] pub struct Millau; diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index 3054334d52df227c74b73a85b3ac4eef0fab6ccf..04b2317749b0af71d3a8c5e3a2550e3cacd600cd 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -44,7 +44,7 @@ pub trait MessageBridge { const RELAYER_FEE_PERCENT: u32; /// This chain in context of message bridge. - type ThisChain: ChainWithMessageLanes; + type ThisChain: ThisChainWithMessageLanes; /// Bridged chain in context of message bridge. type BridgedChain: ChainWithMessageLanes; @@ -104,6 +104,17 @@ pub trait ChainWithMessageLanes { type MessageLaneInstance: Instance; } +/// This chain that has `message-lane` and `call-dispatch` modules. +pub trait ThisChainWithMessageLanes: ChainWithMessageLanes { + /// Are we accepting any messages to the given lane? + fn is_outbound_lane_enabled(lane: &LaneId) -> bool; + + /// Maximal number of pending (not yet delivered) messages at this chain. + /// + /// Any messages over this limit, will be rejected. + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce; +} + pub(crate) type ThisChain<B> = <B as MessageBridge>::ThisChain; pub(crate) type BridgedChain<B> = <B as MessageBridge>::BridgedChain; pub(crate) type HashOf<C> = <C as ChainWithMessageLanes>::Hash; @@ -187,10 +198,24 @@ pub mod source { /// 'Parsed' message delivery proof - inbound lane id and its state. pub type ParsedMessagesDeliveryProofFromBridgedChain<B> = (LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>); - /// Message verifier that requires submitter to pay minimal delivery and dispatch fee. + /// Message verifier that is doing all basic checks. + /// + /// This verifier assumes following: + /// + /// - all message lanes are equivalent, so all checks are the same; + /// - messages are being dispatched using `pallet-bridge-call-dispatch` pallet on the target chain. + /// + /// Following checks are made: + /// + /// - message is rejected if its lane is currently blocked; + /// - message is rejected if there are too many pending (undelivered) messages at the outbound lane; + /// - check that the sender has rights to dispatch the call on target chain using provided dispatch origin; + /// - check that the sender has paid enough funds for both message delivery and dispatch. #[derive(RuntimeDebug)] pub struct FromThisChainMessageVerifier<B>(PhantomData<B>); + pub(crate) const OUTBOUND_LANE_DISABLED: &str = "The outbound message lane is disabled."; + pub(crate) const TOO_MANY_PENDING_MESSAGES: &str = "Too many pending messages at the lane."; pub(crate) const BAD_ORIGIN: &str = "Unable to match the source origin to expected target origin."; pub(crate) const TOO_LOW_FEE: &str = "Provided fee is below minimal threshold required by the lane."; @@ -205,9 +230,24 @@ pub mod source { fn verify_message( submitter: &Sender<AccountIdOf<ThisChain<B>>>, delivery_and_dispatch_fee: &BalanceOf<ThisChain<B>>, - _lane: &LaneId, + lane: &LaneId, + lane_outbound_data: &OutboundLaneData, payload: &FromThisChainMessagePayload<B>, ) -> Result<(), Self::Error> { + // reject message if lane is blocked + if !ThisChain::<B>::is_outbound_lane_enabled(lane) { + return Err(OUTBOUND_LANE_DISABLED); + } + + // reject message if there are too many pending messages at this lane + let max_pending_messages = ThisChain::<B>::maximal_pending_messages_at_outbound_lane(); + let pending_messages = lane_outbound_data + .latest_generated_nonce + .saturating_sub(lane_outbound_data.latest_received_nonce); + if pending_messages > max_pending_messages { + return Err(TOO_MANY_PENDING_MESSAGES); + } + // Do the dispatch-specific check. We assume that the target chain uses // `CallDispatch`, so we verify the message accordingly. pallet_bridge_call_dispatch::verify_message_origin(submitter, payload).map_err(|_| BAD_ORIGIN)?; @@ -812,6 +852,16 @@ mod tests { type MessageLaneInstance = pallet_message_lane::DefaultInstance; } + impl ThisChainWithMessageLanes for ThisChain { + fn is_outbound_lane_enabled(lane: &LaneId) -> bool { + lane == TEST_LANE_ID + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE + } + } + struct BridgedChain; impl ChainWithMessageLanes for BridgedChain { @@ -826,6 +876,20 @@ mod tests { type MessageLaneInstance = pallet_message_lane::DefaultInstance; } + impl ThisChainWithMessageLanes for BridgedChain { + fn is_outbound_lane_enabled(_lane: &LaneId) -> bool { + unreachable!() + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + unreachable!() + } + } + + fn test_lane_outbound_data() -> OutboundLaneData { + OutboundLaneData::default() + } + #[test] fn message_from_bridged_chain_is_decoded() { // the message is encoded on the bridged chain @@ -857,18 +921,23 @@ mod tests { } const TEST_LANE_ID: &LaneId = b"test"; + const MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE: MessageNonce = 32; + + fn regular_outbound_message_payload() -> source::FromThisChainMessagePayload<OnThisChainBridge> { + source::FromThisChainMessagePayload::<OnThisChainBridge> { + spec_version: 1, + weight: 100, + origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot, + call: vec![42], + } + } #[test] fn message_fee_is_checked_by_verifier() { const EXPECTED_MINIMAL_FEE: u32 = 5500; // payload of the This -> Bridged chain message - let payload = source::FromThisChainMessagePayload::<OnThisChainBridge> { - spec_version: 1, - weight: 100, - origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot, - call: vec![42], - }; + let payload = regular_outbound_message_payload(); // let's check if estimation matching hardcoded value assert_eq!( @@ -885,6 +954,7 @@ mod tests { &Sender::Root, &ThisChainBalance(1), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ), Err(source::TOO_LOW_FEE) @@ -894,6 +964,7 @@ mod tests { &Sender::Root, &ThisChainBalance(1_000_000), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ) .is_ok(), @@ -916,6 +987,7 @@ mod tests { &Sender::Signed(ThisChainAccountId(0)), &ThisChainBalance(1_000_000), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ), Err(source::BAD_ORIGIN) @@ -925,6 +997,7 @@ mod tests { &Sender::None, &ThisChainBalance(1_000_000), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ), Err(source::BAD_ORIGIN) @@ -934,6 +1007,7 @@ mod tests { &Sender::Root, &ThisChainBalance(1_000_000), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ) .is_ok(), @@ -956,6 +1030,7 @@ mod tests { &Sender::Signed(ThisChainAccountId(0)), &ThisChainBalance(1_000_000), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ), Err(source::BAD_ORIGIN) @@ -965,12 +1040,45 @@ mod tests { &Sender::Signed(ThisChainAccountId(1)), &ThisChainBalance(1_000_000), &TEST_LANE_ID, + &test_lane_outbound_data(), &payload, ) .is_ok(), ); } + #[test] + fn message_is_rejected_when_sent_using_disabled_lane() { + assert_eq!( + source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message( + &Sender::Root, + &ThisChainBalance(1_000_000), + b"dsbl", + &test_lane_outbound_data(), + ®ular_outbound_message_payload(), + ), + Err(source::OUTBOUND_LANE_DISABLED) + ); + } + + #[test] + fn message_is_rejected_when_there_are_too_many_pending_messages_at_outbound_lane() { + assert_eq!( + source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message( + &Sender::Root, + &ThisChainBalance(1_000_000), + &TEST_LANE_ID, + &OutboundLaneData { + latest_received_nonce: 100, + latest_generated_nonce: 100 + MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE + 1, + ..Default::default() + }, + ®ular_outbound_message_payload(), + ), + Err(source::TOO_MANY_PENDING_MESSAGES) + ); + } + #[test] fn verify_chain_message_rejects_message_with_too_small_declared_weight() { assert!( diff --git a/bridges/modules/message-lane/src/lib.rs b/bridges/modules/message-lane/src/lib.rs index c14ccdb45546c8b2f24f4124b497e3cb11444da2..9b55343680e3718620b06009c46cc3dc3b8bfa85 100644 --- a/bridges/modules/message-lane/src/lib.rs +++ b/bridges/modules/message-lane/src/lib.rs @@ -293,10 +293,12 @@ decl_module! { })?; // now let's enforce any additional lane rules + let mut lane = outbound_lane::<T, I>(lane_id); T::LaneMessageVerifier::verify_message( &submitter, &delivery_and_dispatch_fee, &lane_id, + &lane.data(), &payload, ).map_err(|err| { frame_support::debug::trace!( @@ -326,7 +328,6 @@ decl_module! { })?; // finally, save message in outbound storage and emit event - let mut lane = outbound_lane::<T, I>(lane_id); let encoded_payload = payload.encode(); let encoded_payload_len = encoded_payload.len(); let nonce = lane.send_message(MessageData { diff --git a/bridges/modules/message-lane/src/mock.rs b/bridges/modules/message-lane/src/mock.rs index 4d1bb488908b8d6867bc734bb6216e695ea21bf8..244d580f05782ebadb06a382345df450cf9a90ba 100644 --- a/bridges/modules/message-lane/src/mock.rs +++ b/bridges/modules/message-lane/src/mock.rs @@ -21,7 +21,7 @@ use bp_message_lane::{ LaneMessageVerifier, MessageDeliveryAndDispatchPayment, RelayersRewards, Sender, TargetHeaderChain, }, target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain}, - InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, + InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData, }; use bp_runtime::Size; use codec::{Decode, Encode}; @@ -253,6 +253,7 @@ impl LaneMessageVerifier<AccountId, TestPayload, TestMessageFee> for TestLaneMes _submitter: &Sender<AccountId>, delivery_and_dispatch_fee: &TestMessageFee, _lane: &LaneId, + _lane_outbound_data: &OutboundLaneData, _payload: &TestPayload, ) -> Result<(), Self::Error> { if *delivery_and_dispatch_fee != 0 { diff --git a/bridges/modules/message-lane/src/outbound_lane.rs b/bridges/modules/message-lane/src/outbound_lane.rs index dc1ec413012d08421f276ac0b122e50d1d96857c..8496d7f8c026d25f4b379538f81d3c398f4aa748 100644 --- a/bridges/modules/message-lane/src/outbound_lane.rs +++ b/bridges/modules/message-lane/src/outbound_lane.rs @@ -49,6 +49,11 @@ impl<S: OutboundLaneStorage> OutboundLane<S> { OutboundLane { storage } } + /// Get this lane data. + pub fn data(&self) -> OutboundLaneData { + self.storage.data() + } + /// Send message over lane. /// /// Returns new message nonce. diff --git a/bridges/primitives/message-lane/src/source_chain.rs b/bridges/primitives/message-lane/src/source_chain.rs index 1c47d8c6debb721bdb0bfa502b58c44dcba90aac..1bb0d591586f9e9c0d6401025974427afd61b740 100644 --- a/bridges/primitives/message-lane/src/source_chain.rs +++ b/bridges/primitives/message-lane/src/source_chain.rs @@ -16,7 +16,7 @@ //! Primitives of message lane module, that are used on the source chain. -use crate::{InboundLaneData, LaneId, MessageNonce}; +use crate::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData}; use bp_runtime::Size; use frame_support::{Parameter, RuntimeDebug}; @@ -86,6 +86,7 @@ pub trait LaneMessageVerifier<Submitter, Payload, Fee> { submitter: &Sender<Submitter>, delivery_and_dispatch_fee: &Fee, lane: &LaneId, + outbound_data: &OutboundLaneData, payload: &Payload, ) -> Result<(), Self::Error>; }