// Copyright 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 . use crate::messages::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, }; use bp_messages::{LaneId, MessageNonce}; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, RuntimeDebug}; use pallet_bridge_messages::{Config, Pallet}; use sp_runtime::transaction_validity::TransactionValidity; /// Generic info about a messages delivery/confirmation proof. #[derive(PartialEq, RuntimeDebug)] pub struct BaseMessagesProofInfo { /// Message lane, used by the call. pub lane_id: LaneId, /// Nonce of the best message, included in the call. pub best_bundled_nonce: MessageNonce, /// Nonce of the best message, stored by this chain before the call is dispatched. pub best_stored_nonce: MessageNonce, } impl BaseMessagesProofInfo { fn is_obsolete(&self) -> bool { self.best_bundled_nonce <= self.best_stored_nonce } } /// Info about a `ReceiveMessagesProof` call which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] pub struct ReceiveMessagesProofInfo(pub BaseMessagesProofInfo); /// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo); /// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call /// which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] pub enum CallInfo { ReceiveMessagesProof(ReceiveMessagesProofInfo), ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo), } /// Helper struct that provides methods for working with a call supported by `CallInfo`. pub struct CallHelper, I: 'static> { pub _phantom_data: sp_std::marker::PhantomData<(T, I)>, } impl, I: 'static> CallHelper { /// Returns true if: /// /// - call is `receive_messages_proof` and all messages have been delivered; /// /// - call is `receive_messages_delivery_proof` and all messages confirmations have been /// received. pub fn was_successful(info: &CallInfo) -> bool { match info { CallInfo::ReceiveMessagesProof(info) => { let inbound_lane_data = pallet_bridge_messages::InboundLanes::::get(info.0.lane_id); inbound_lane_data.last_delivered_nonce() == info.0.best_bundled_nonce }, CallInfo::ReceiveMessagesDeliveryProof(info) => { let outbound_lane_data = pallet_bridge_messages::OutboundLanes::::get(info.0.lane_id); outbound_lane_data.latest_received_nonce == info.0.best_bundled_nonce }, } } } /// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`. pub trait MessagesCallSubType, I: 'static>: IsSubType, T>> { /// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call. fn receive_messages_proof_info(&self) -> Option; /// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from /// a `ReceiveMessagesDeliveryProof` call. fn receive_messages_delivery_proof_info(&self) -> Option; /// Create a new instance of `CallInfo` from a `ReceiveMessagesProof` /// or a `ReceiveMessagesDeliveryProof` call. fn call_info(&self) -> Option; /// Create a new instance of `CallInfo` from a `ReceiveMessagesProof` /// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane. fn call_info_for(&self, lane_id: LaneId) -> Option; /// Check that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call is trying /// to deliver/confirm at least some messages that are better than the ones we know of. fn check_obsolete_call(&self) -> TransactionValidity; } impl< BridgedHeaderHash, SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain< MessagesProof = FromBridgedChainMessagesProof, >, TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain< >::OutboundPayload, ::AccountId, MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof, >, Call: IsSubType, T>>, T: frame_system::Config + Config, I: 'static, > MessagesCallSubType for T::RuntimeCall { fn receive_messages_proof_info(&self) -> Option { if let Some(pallet_bridge_messages::Call::::receive_messages_proof { ref proof, .. }) = self.is_sub_type() { let inbound_lane_data = pallet_bridge_messages::InboundLanes::::get(proof.lane); return Some(ReceiveMessagesProofInfo(BaseMessagesProofInfo { lane_id: proof.lane, best_bundled_nonce: proof.nonces_end, best_stored_nonce: inbound_lane_data.last_delivered_nonce(), })) } None } fn receive_messages_delivery_proof_info(&self) -> Option { if let Some(pallet_bridge_messages::Call::::receive_messages_delivery_proof { ref proof, ref relayers_state, .. }) = self.is_sub_type() { let outbound_lane_data = pallet_bridge_messages::OutboundLanes::::get(proof.lane); return Some(ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo { lane_id: proof.lane, best_bundled_nonce: relayers_state.last_delivered_nonce, best_stored_nonce: outbound_lane_data.latest_received_nonce, })) } None } fn call_info(&self) -> Option { if let Some(info) = self.receive_messages_proof_info() { return Some(CallInfo::ReceiveMessagesProof(info)) } if let Some(info) = self.receive_messages_delivery_proof_info() { return Some(CallInfo::ReceiveMessagesDeliveryProof(info)) } None } fn call_info_for(&self, lane_id: LaneId) -> Option { self.call_info().filter(|info| { let actual_lane_id = match info { CallInfo::ReceiveMessagesProof(info) => info.0.lane_id, CallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id, }; actual_lane_id == lane_id }) } fn check_obsolete_call(&self) -> TransactionValidity { match self.call_info() { Some(CallInfo::ReceiveMessagesProof(proof_info)) if proof_info.0.is_obsolete() => { log::trace!( target: pallet_bridge_messages::LOG_TARGET, "Rejecting obsolete messages delivery transaction: {:?}", proof_info ); return sp_runtime::transaction_validity::InvalidTransaction::Stale.into() }, Some(CallInfo::ReceiveMessagesDeliveryProof(proof_info)) if proof_info.0.is_obsolete() => { log::trace!( target: pallet_bridge_messages::LOG_TARGET, "Rejecting obsolete messages confirmation transaction: {:?}", proof_info, ); return sp_runtime::transaction_validity::InvalidTransaction::Stale.into() }, _ => {}, } Ok(sp_runtime::transaction_validity::ValidTransaction::default()) } } #[cfg(test)] mod tests { use crate::{ messages::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, }, messages_call_ext::MessagesCallSubType, mock::{TestRuntime, ThisChainRuntimeCall}, }; use bp_messages::UnrewardedRelayersState; fn deliver_message_10() { pallet_bridge_messages::InboundLanes::::insert( bp_messages::LaneId([0, 0, 0, 0]), bp_messages::InboundLaneData { relayers: Default::default(), last_confirmed_nonce: 10 }, ); } fn validate_message_delivery( nonces_start: bp_messages::MessageNonce, nonces_end: bp_messages::MessageNonce, ) -> bool { ThisChainRuntimeCall::BridgeMessages( pallet_bridge_messages::Call::::receive_messages_proof { relayer_id_at_bridged_chain: 42, messages_count: (nonces_end - nonces_start + 1) as u32, dispatch_weight: frame_support::weights::Weight::zero(), proof: FromBridgedChainMessagesProof { bridged_header_hash: Default::default(), storage_proof: vec![], lane: bp_messages::LaneId([0, 0, 0, 0]), nonces_start, nonces_end, }, }, ) .check_obsolete_call() .is_ok() } #[test] fn extension_rejects_obsolete_messages() { sp_io::TestExternalities::new(Default::default()).execute_with(|| { // when current best delivered is message#10 and we're trying to deliver message#5 => tx // is rejected deliver_message_10(); assert!(!validate_message_delivery(8, 9)); }); } #[test] fn extension_rejects_same_message() { sp_io::TestExternalities::new(Default::default()).execute_with(|| { // when current best delivered is message#10 and we're trying to import message#10 => tx // is rejected deliver_message_10(); assert!(!validate_message_delivery(8, 10)); }); } #[test] fn extension_accepts_new_message() { sp_io::TestExternalities::new(Default::default()).execute_with(|| { // when current best delivered is message#10 and we're trying to deliver message#15 => // tx is accepted deliver_message_10(); assert!(validate_message_delivery(10, 15)); }); } fn confirm_message_10() { pallet_bridge_messages::OutboundLanes::::insert( bp_messages::LaneId([0, 0, 0, 0]), bp_messages::OutboundLaneData { oldest_unpruned_nonce: 0, latest_received_nonce: 10, latest_generated_nonce: 10, }, ); } fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool { ThisChainRuntimeCall::BridgeMessages( pallet_bridge_messages::Call::::receive_messages_delivery_proof { proof: FromBridgedChainMessagesDeliveryProof { bridged_header_hash: Default::default(), storage_proof: Vec::new(), lane: bp_messages::LaneId([0, 0, 0, 0]), }, relayers_state: UnrewardedRelayersState { last_delivered_nonce, ..Default::default() }, }, ) .check_obsolete_call() .is_ok() } #[test] fn extension_rejects_obsolete_confirmations() { sp_io::TestExternalities::new(Default::default()).execute_with(|| { // when current best confirmed is message#10 and we're trying to confirm message#5 => tx // is rejected confirm_message_10(); assert!(!validate_message_confirmation(5)); }); } #[test] fn extension_rejects_same_confirmation() { sp_io::TestExternalities::new(Default::default()).execute_with(|| { // when current best confirmed is message#10 and we're trying to confirm message#10 => // tx is rejected confirm_message_10(); assert!(!validate_message_confirmation(10)); }); } #[test] fn extension_accepts_new_confirmation() { sp_io::TestExternalities::new(Default::default()).execute_with(|| { // when current best confirmed is message#10 and we're trying to confirm message#15 => // tx is accepted confirm_message_10(); assert!(validate_message_confirmation(15)); }); } }