From a9af3962691656710fa4293fa3fa0eb5453870b2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 25 Sep 2020 22:22:44 +0300 Subject: [PATCH] Fees, weights, message delivery and dispatch (#339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * introduce BridgedHeaderChain trait * LaneMessageVerifier + tests * fixed tests * do not expose intenal functions * cargo fmt --all + fix no_std compilation * ByWeightDispatcher * process queued messages from message-lane::on_initialize * scheduled_messages_are_processed_from_on_initialize * flush * deal with fees + weights * drop heavy messages on dispatch * cargo fmt * clippy * fix comment * Update primitives/message-lane/src/source_chain.rs Co-authored-by: Tomasz Drwięga * removed messages_processed * Update primitives/message-lane/src/source_chain.rs Co-authored-by: Hernando Castano * Update modules/message-lane/src/lib.rs Co-authored-by: Hernando Castano * remove queueing from message-lane * also remove queueing from RPCs * remove by-weight traces * dispatch fee * receiving -> delivery * receival -> delivery * remove extra line * Update primitives/message-lane/src/source_chain.rs Co-authored-by: Hernando Castano * cargo fmt --all * clippy * let dispatch_weight to be larger than actual_dispatch_weight * post-merge fix Co-authored-by: Tomasz Drwięga Co-authored-by: Hernando Castano --- bridges/modules/call-dispatch/src/lib.rs | 22 +- bridges/modules/message-lane/Cargo.toml | 6 +- bridges/modules/message-lane/rpc/src/lib.rs | 14 +- .../modules/message-lane/src/inbound_lane.rs | 20 +- bridges/modules/message-lane/src/lib.rs | 452 +++++++++++++++--- bridges/modules/message-lane/src/mock.rs | 145 +++++- .../modules/message-lane/src/outbound_lane.rs | 64 +-- .../primitives/message-dispatch/src/lib.rs | 8 +- bridges/primitives/message-lane/Cargo.toml | 16 +- bridges/primitives/message-lane/src/lib.rs | 44 +- .../message-lane/src/source_chain.rs | 97 ++++ .../message-lane/src/target_chain.rs | 56 +++ .../relays/messages-relay/src/message_lane.rs | 2 +- 13 files changed, 802 insertions(+), 144 deletions(-) create mode 100644 bridges/primitives/message-lane/src/source_chain.rs create mode 100644 bridges/primitives/message-lane/src/target_chain.rs diff --git a/bridges/modules/call-dispatch/src/lib.rs b/bridges/modules/call-dispatch/src/lib.rs index 205c0cbfc00d8..1524c0d223802 100644 --- a/bridges/modules/call-dispatch/src/lib.rs +++ b/bridges/modules/call-dispatch/src/lib.rs @@ -46,10 +46,6 @@ use sp_std::{marker::PhantomData, prelude::*}; /// Spec version type. pub type SpecVersion = u32; -// TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) -/// Weight of single deposit_event() call. -const DEPOSIT_EVENT_WEIGHT: Weight = 0; - /// Origin of the call on the target chain. #[derive(RuntimeDebug, Encode, Decode, Clone)] pub enum CallOrigin { @@ -149,7 +145,11 @@ impl, I: Instance> MessageDispatch for Module { >::Call, >; - fn dispatch(bridge: InstanceId, id: T::MessageId, message: Self::Message) -> Weight { + fn dispatch_weight(message: &Self::Message) -> Weight { + message.weight + } + + fn dispatch(bridge: InstanceId, id: T::MessageId, message: Self::Message) { // verify spec version // (we want it to be the same, because otherwise we may decode Call improperly) let expected_version = ::Version::get().spec_version; @@ -167,7 +167,7 @@ impl, I: Instance> MessageDispatch for Module { expected_version, message.spec_version, )); - return DEPOSIT_EVENT_WEIGHT; + return; } // verify weight @@ -189,7 +189,7 @@ impl, I: Instance> MessageDispatch for Module { expected_weight, message.weight, )); - return DEPOSIT_EVENT_WEIGHT; + return; } // prepare dispatch origin @@ -210,7 +210,7 @@ impl, I: Instance> MessageDispatch for Module { target_signature, ); Self::deposit_event(RawEvent::MessageSignatureMismatch(bridge, id)); - return DEPOSIT_EVENT_WEIGHT; + return; } target_account @@ -222,9 +222,11 @@ impl, I: Instance> MessageDispatch for Module { let dispatch_result = message.call.dispatch(origin); let actual_call_weight = extract_actual_weight(&dispatch_result, &dispatch_info); frame_support::debug::trace!( - "Message {:?}/{:?} has been dispatched. Result: {:?}", + "Message {:?}/{:?} has been dispatched. Weight: {} of {}. Result: {:?}", bridge, id, + actual_call_weight, + message.weight, dispatch_result, ); @@ -233,8 +235,6 @@ impl, I: Instance> MessageDispatch for Module { id, dispatch_result.map(drop).map_err(|e| e.error), )); - - actual_call_weight + DEPOSIT_EVENT_WEIGHT } } diff --git a/bridges/modules/message-lane/Cargo.toml b/bridges/modules/message-lane/Cargo.toml index 71bb86f689f08..d9a2c3c46bfab 100644 --- a/bridges/modules/message-lane/Cargo.toml +++ b/bridges/modules/message-lane/Cargo.toml @@ -7,7 +7,9 @@ edition = "2018" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] +bp-message-dispatch = { path = "../../primitives/message-dispatch", default-features = false } bp-message-lane = { path = "../../primitives/message-lane", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false } # Substrate Based Dependencies @@ -48,9 +50,11 @@ git = "https://github.com/paritytech/substrate/" [features] default = ["std"] std = [ + "bp-message-dispatch/std", "bp-message-lane/std", + "bp-runtime/std", "codec/std", "frame-support/std", "frame-system/std", - "sp-std/std" + "sp-std/std", ] diff --git a/bridges/modules/message-lane/rpc/src/lib.rs b/bridges/modules/message-lane/rpc/src/lib.rs index 799de23dab9d7..ff3ab1507ad8a 100644 --- a/bridges/modules/message-lane/rpc/src/lib.rs +++ b/bridges/modules/message-lane/rpc/src/lib.rs @@ -39,7 +39,7 @@ pub type MessagesProof = Bytes; /// Trie-based storage proof that the message(s) with given key(s) have been received by the bridged chain. /// SCALE-encoded trie nodes array `Vec>`. -pub type MessagesReceivingProof = Bytes; +pub type MessagesDeliveryProof = Bytes; /// Runtime adapter. pub trait Runtime: Send + Sync + 'static { @@ -63,14 +63,14 @@ pub trait MessageLaneApi { block: Option, ) -> FutureResult; - /// Returns proof-of-message(s) receiving. - #[rpc(name = "messageLane_proveMessagesReceiving")] - fn prove_messages_receiving( + /// Returns proof-of-message(s) delivery. + #[rpc(name = "messageLane_proveMessagesDelivery")] + fn prove_messages_delivery( &self, instance: InstanceId, lane: LaneId, block: Option, - ) -> FutureResult; + ) -> FutureResult; } /// Implements the MessageLaneApi trait for interacting with message lanes. @@ -119,12 +119,12 @@ where ) } - fn prove_messages_receiving( + fn prove_messages_delivery( &self, instance: InstanceId, lane: LaneId, block: Option, - ) -> FutureResult { + ) -> FutureResult { Box::new( prove_keys_read( self.backend.clone(), diff --git a/bridges/modules/message-lane/src/inbound_lane.rs b/bridges/modules/message-lane/src/inbound_lane.rs index f8bf478ecb29a..9fedf043194da 100644 --- a/bridges/modules/message-lane/src/inbound_lane.rs +++ b/bridges/modules/message-lane/src/inbound_lane.rs @@ -16,12 +16,16 @@ //! Everything about incoming messages receival. -use bp_message_lane::{InboundLaneData, LaneId, Message, MessageKey, MessageNonce, OnMessageReceived}; +use bp_message_lane::{ + target_chain::MessageDispatch, InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, +}; /// Inbound lane storage. pub trait InboundLaneStorage { /// Message payload. type Payload; + /// Delivery and dispatch fee type on source chain. + type MessageFee; /// Lane id. fn id(&self) -> LaneId; @@ -43,10 +47,10 @@ impl InboundLane { } /// Receive new message. - pub fn receive_message>( + pub fn receive_message>( &mut self, nonce: MessageNonce, - payload: S::Payload, + message_data: MessageData, ) -> bool { let mut data = self.storage.data(); let is_correct_message = nonce == data.latest_received_nonce + 1; @@ -57,12 +61,12 @@ impl InboundLane { data.latest_received_nonce = nonce; self.storage.set_data(data); - P::on_message_received(Message { + P::dispatch(Message { key: MessageKey { lane_id: self.storage.id(), nonce, }, - payload, + data: message_data, }); true @@ -74,14 +78,14 @@ mod tests { use super::*; use crate::{ inbound_lane, - mock::{run_test, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID}, + mock::{message_data, run_test, TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID}, }; #[test] fn fails_to_receive_message_with_incorrect_nonce() { run_test(|| { let mut lane = inbound_lane::(TEST_LANE_ID); - assert!(!lane.receive_message::<()>(10, REGULAR_PAYLOAD)); + assert!(!lane.receive_message::(10, message_data(REGULAR_PAYLOAD))); assert_eq!(lane.storage.data().latest_received_nonce, 0); }); } @@ -90,7 +94,7 @@ mod tests { fn correct_message_is_processed_instantly() { run_test(|| { let mut lane = inbound_lane::(TEST_LANE_ID); - assert!(lane.receive_message::<()>(1, REGULAR_PAYLOAD)); + assert!(lane.receive_message::(1, message_data(REGULAR_PAYLOAD))); assert_eq!(lane.storage.data().latest_received_nonce, 1); }); } diff --git a/bridges/modules/message-lane/src/lib.rs b/bridges/modules/message-lane/src/lib.rs index f8c9b9d4f8e72..5f99eee756b63 100644 --- a/bridges/modules/message-lane/src/lib.rs +++ b/bridges/modules/message-lane/src/lib.rs @@ -21,12 +21,11 @@ //! 3) the messages are stored in the storage; //! 4) external component (relay) delivers messages to bridged chain; //! 5) messages are processed in order (ordered by assigned nonce); -//! 6) relay may send proof-of-receiving and proof-of-processing back to this chain. +//! 6) relay may send proof-of-delivery back to this chain. //! //! Once message is sent, its progress can be tracked by looking at module events. //! The assigned nonce is reported using `MessageAccepted` event. When message is -//! accepted by the bridged chain, `MessagesDelivered` is fired. When message is -//! processedby the bridged chain, `MessagesProcessed` by the bridged chain. +//! delivered to the the bridged chain, it is reported using `MessagesDelivered` event. #![cfg_attr(not(feature = "std"), no_std)] @@ -34,9 +33,14 @@ use crate::inbound_lane::{InboundLane, InboundLaneStorage}; use crate::outbound_lane::{OutboundLane, OutboundLaneStorage}; use bp_message_lane::{ - InboundLaneData, LaneId, Message, MessageKey, MessageNonce, OnMessageReceived, OutboundLaneData, + source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, TargetHeaderChain}, + target_chain::{MessageDispatch, SourceHeaderChain}, + InboundLaneData, LaneId, MessageData, MessageKey, MessageNonce, OutboundLaneData, +}; +use frame_support::{ + decl_error, decl_event, decl_module, decl_storage, sp_runtime::DispatchResult, traits::Get, weights::Weight, + Parameter, StorageMap, }; -use frame_support::{decl_event, decl_module, decl_storage, traits::Get, Parameter, StorageMap}; use frame_system::ensure_signed; use sp_std::{marker::PhantomData, prelude::*}; @@ -46,8 +50,14 @@ mod outbound_lane; #[cfg(test)] mod mock; +// TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) +/// Upper bound of delivery transaction weight. +const DELIVERY_BASE_WEIGHT: Weight = 0; + /// The module configuration trait pub trait Trait: frame_system::Trait { + // General types + /// They overarching event type. type Event: From> + Into<::Event>; /// Message payload. @@ -57,8 +67,50 @@ pub trait Trait: frame_system::Trait { /// confirmed. The reason is that if you want to use lane, you should be ready to pay /// for it. type MaxMessagesToPruneAtOnce: Get; - /// Called when message has been received. - type OnMessageReceived: OnMessageReceived; + + // Types that are used by outbound_lane (on source chain). + + /// Type of delivery_and_dispatch_fee on source chain. + type MessageFee: Parameter; + /// Target header chain. + type TargetHeaderChain: TargetHeaderChain; + /// Message payload verifier. + type LaneMessageVerifier: LaneMessageVerifier; + /// Message delivery payment. + type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment; + + // Types that are used by inbound_lane (on target chain). + + /// Source header chain, as it is represented on target chain. + type SourceHeaderChain: SourceHeaderChain; + /// Message dispatch. + type MessageDispatch: MessageDispatch; +} + +/// Shortcut to messages proof type for Trait. +type MessagesProofOf = <>::SourceHeaderChain as SourceHeaderChain< + >::Payload, + >::MessageFee, +>>::MessagesProof; +/// Shortcut to messages delivery proof type for Trait. +type MessagesDeliveryProofOf = + <>::TargetHeaderChain as TargetHeaderChain<>::Payload>>::MessagesDeliveryProof; + +decl_error! { + pub enum Error for Module, I: Instance> { + /// Message has been treated as invalid by chain verifier. + MessageRejectedByChainVerifier, + /// Message has been treated as invalid by lane verifier. + MessageRejectedByLaneVerifier, + /// Submitter has failed to pay fee for delivering and dispatching messages. + FailedToWithdrawMessageFee, + /// Invalid messages has been submitted. + InvalidMessagesProof, + /// Invalid messages dispatch weight has been declared by the relayer. + InvalidMessagesDispatchWeight, + /// Invalid messages delivery proof has been submitted. + InvalidMessagesDeliveryProof, + } } decl_storage! { @@ -68,7 +120,7 @@ decl_storage! { /// Map of lane id => outbound lane data. OutboundLanes: map hasher(blake2_128_concat) LaneId => OutboundLaneData; /// All queued outbound messages. - OutboundMessages: map hasher(blake2_128_concat) MessageKey => Option; + OutboundMessages: map hasher(blake2_128_concat) MessageKey => Option>; } } @@ -96,59 +148,160 @@ decl_module! { origin, lane_id: LaneId, payload: T::Payload, - ) { - let _ = ensure_signed(origin)?; + delivery_and_dispatch_fee: T::MessageFee, + ) -> DispatchResult { + let submitter = ensure_signed(origin)?; + + // let's first check if message can be delivered to target chain + T::TargetHeaderChain::verify_message(&payload).map_err(|err| { + frame_support::debug::trace!( + target: "runtime", + "Message to lane {:?} is rejected by target chain: {:?}", + lane_id, + err, + ); + + Error::::MessageRejectedByChainVerifier + })?; + + // now let's enforce any additional lane rules + T::LaneMessageVerifier::verify_message( + &submitter, + &delivery_and_dispatch_fee, + &lane_id, + &payload, + ).map_err(|err| { + frame_support::debug::trace!( + target: "runtime", + "Message to lane {:?} is rejected by lane verifier: {:?}", + lane_id, + err, + ); + + Error::::MessageRejectedByLaneVerifier + })?; + + // let's withdraw delivery and dispatch fee from submitter + T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee( + &submitter, + &delivery_and_dispatch_fee, + ).map_err(|err| { + frame_support::debug::trace!( + target: "runtime", + "Message to lane {:?} is rejected because submitter {:?} is unable to pay fee {:?}: {:?}", + lane_id, + submitter, + delivery_and_dispatch_fee, + err, + ); + + Error::::FailedToWithdrawMessageFee + })?; + + // finally, save message in outbound storage and emit event let mut lane = outbound_lane::(lane_id); - let nonce = lane.send_message(payload); + let nonce = lane.send_message(MessageData { + payload, + fee: delivery_and_dispatch_fee, + }); lane.prune_messages(T::MaxMessagesToPruneAtOnce::get()); + frame_support::debug::trace!( + target: "runtime", + "Accepted message {} to lane {:?}", + nonce, + lane_id, + ); + Self::deposit_event(RawEvent::MessageAccepted(lane_id, nonce)); + + Ok(()) } - } -} -impl, I: Instance> Module { - // ========================================================================================= - // === Exposed mutables ==================================================================== - // ========================================================================================= - - /// Receive new TRUSTED lane messages. - /// - /// Trusted here means that the function itself doesn't check whether message has actually - /// been sent through the other end of the channel. We only check that we are receiving - /// and processing messages in order here. - /// - /// Messages vector is required to be sorted by nonce within each lane. Otherise messages - /// will be rejected. - pub fn receive_messages(messages: Vec>) -> MessageNonce { - let mut correct_messages = 0; - for message in messages { - let mut lane = inbound_lane::(message.key.lane_id); - if lane.receive_message::(message.key.nonce, message.payload) { - correct_messages += 1; + /// Receive messages proof from bridged chain. + #[weight = DELIVERY_BASE_WEIGHT + dispatch_weight] + pub fn receive_messages_proof( + origin, + proof: MessagesProofOf, + dispatch_weight: Weight, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + // verify messages proof && convert proof into messages + let messages = T::SourceHeaderChain::verify_messages_proof(proof).map_err(|err| { + frame_support::debug::trace!( + target: "runtime", + "Rejecting invalid messages proof: {:?}", + err, + ); + + Error::::InvalidMessagesProof + })?; + + // verify that relayer is paying actual dispatch weight + let actual_dispatch_weight: Weight = messages + .iter() + .map(T::MessageDispatch::dispatch_weight) + .sum(); + if dispatch_weight < actual_dispatch_weight { + frame_support::debug::trace!( + target: "runtime", + "Rejecting messages proof because of dispatch weight mismatch: declared={}, expected={}", + dispatch_weight, + actual_dispatch_weight, + ); + + return Err(Error::::InvalidMessagesDispatchWeight.into()); + } + + // dispatch messages + let total_messages = messages.len(); + let mut valid_messages = 0; + for message in messages { + let mut lane = inbound_lane::(message.key.lane_id); + if lane.receive_message::(message.key.nonce, message.data) { + valid_messages += 1; + } } + + frame_support::debug::trace!( + target: "runtime", + "Received messages: total={}, valid={}", + total_messages, + valid_messages, + ); + + Ok(()) } - correct_messages - } - - /// Receive TRUSTED proof of message receival. - /// - /// Trusted here means that the function itself doesn't check whether the bridged chain has - /// actually received these messages. - /// - /// The caller may break the channel by providing `latest_received_nonce` that is larger - /// than actual one. Not-yet-sent messages may be pruned in this case. - pub fn confirm_receival(lane_id: &LaneId, latest_received_nonce: MessageNonce) { - let mut lane = outbound_lane::(*lane_id); - let received_range = lane.confirm_receival(latest_received_nonce); - - if let Some(received_range) = received_range { - Self::deposit_event(RawEvent::MessagesDelivered( - *lane_id, - received_range.0, - received_range.1, - )); + /// Receive messages delivery proof from bridged chain. + #[weight = 0] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + pub fn receive_messages_delivery_proof(origin, proof: MessagesDeliveryProofOf) -> DispatchResult { + let _ = ensure_signed(origin)?; + let (lane_id, nonce) = T::TargetHeaderChain::verify_messages_delivery_proof(proof).map_err(|err| { + frame_support::debug::trace!( + target: "runtime", + "Rejecting invalid messages delivery proof: {:?}", + err, + ); + + Error::::InvalidMessagesDeliveryProof + })?; + + let mut lane = outbound_lane::(lane_id); + let received_range = lane.confirm_delivery(nonce); + if let Some(received_range) = received_range { + Self::deposit_event(RawEvent::MessagesDelivered(lane_id, received_range.0, received_range.1)); + } + + frame_support::debug::trace!( + target: "runtime", + "Received messages delivery proof up to (and including) {} at lane {:?}", + nonce, + lane_id, + ); + + Ok(()) } } } @@ -177,6 +330,7 @@ struct RuntimeInboundLaneStorage { impl, I: Instance> InboundLaneStorage for RuntimeInboundLaneStorage { type Payload = T::Payload; + type MessageFee = T::MessageFee; fn id(&self) -> LaneId { self.lane_id @@ -199,6 +353,7 @@ struct RuntimeOutboundLaneStorage { impl, I: Instance> OutboundLaneStorage for RuntimeOutboundLaneStorage { type Payload = T::Payload; + type MessageFee = T::MessageFee; fn id(&self) -> LaneId { self.lane_id @@ -213,20 +368,20 @@ impl, I: Instance> OutboundLaneStorage for RuntimeOutboundLaneStorag } #[cfg(test)] - fn message(&self, nonce: &MessageNonce) -> Option { + fn message(&self, nonce: &MessageNonce) -> Option> { OutboundMessages::::get(MessageKey { lane_id: self.lane_id, nonce: *nonce, }) } - fn save_message(&mut self, nonce: MessageNonce, payload: T::Payload) { + fn save_message(&mut self, nonce: MessageNonce, mesage_data: MessageData) { OutboundMessages::::insert( MessageKey { lane_id: self.lane_id, nonce, }, - payload, + mesage_data, ); } @@ -237,3 +392,190 @@ impl, I: Instance> OutboundLaneStorage for RuntimeOutboundLaneStorag }); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ + run_test, Origin, TestEvent, TestMessageDeliveryAndDispatchPayment, TestRuntime, + PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, + }; + use bp_message_lane::Message; + use frame_support::{assert_noop, assert_ok}; + use frame_system::{EventRecord, Module as System, Phase}; + + fn send_regular_message() { + System::::set_block_number(1); + System::::reset_events(); + + assert_ok!(Module::::send_message( + Origin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + REGULAR_PAYLOAD.1, + )); + + // check event with assigned nonce + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::message_lane(RawEvent::MessageAccepted(TEST_LANE_ID, 1)), + topics: vec![], + }], + ); + + // check that fee has been withdrawn from submitter + assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid(1, REGULAR_PAYLOAD.1)); + } + + fn receive_messages_delivery_proof() { + System::::set_block_number(1); + System::::reset_events(); + + assert_ok!(Module::::receive_messages_delivery_proof( + Origin::signed(1), + Ok((TEST_LANE_ID, 1)), + )); + + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::message_lane(RawEvent::MessagesDelivered(TEST_LANE_ID, 1, 1)), + topics: vec![], + }], + ); + } + + #[test] + fn send_message_works() { + run_test(|| { + send_regular_message(); + }); + } + + #[test] + fn chain_verifier_rejects_invalid_message_in_send_message() { + run_test(|| { + // messages with this payload are rejected by target chain verifier + assert_noop!( + Module::::send_message( + Origin::signed(1), + TEST_LANE_ID, + PAYLOAD_REJECTED_BY_TARGET_CHAIN, + PAYLOAD_REJECTED_BY_TARGET_CHAIN.1 + ), + Error::::MessageRejectedByChainVerifier, + ); + }); + } + + #[test] + fn lane_verifier_rejects_invalid_message_in_send_message() { + run_test(|| { + // messages with zero fee are rejected by lane verifier + assert_noop!( + Module::::send_message(Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, 0), + Error::::MessageRejectedByLaneVerifier, + ); + }); + } + + #[test] + fn message_send_fails_if_submitter_cant_pay_message_fee() { + run_test(|| { + TestMessageDeliveryAndDispatchPayment::reject_payments(); + assert_noop!( + Module::::send_message( + Origin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + REGULAR_PAYLOAD.1 + ), + Error::::FailedToWithdrawMessageFee, + ); + }); + } + + #[test] + fn receive_messages_proof_works() { + run_test(|| { + assert_ok!(Module::::receive_messages_proof( + Origin::signed(1), + Ok(vec![Message { + key: MessageKey { + lane_id: TEST_LANE_ID, + nonce: 1, + }, + data: MessageData { + payload: REGULAR_PAYLOAD, + fee: 0, + }, + }]), + REGULAR_PAYLOAD.1, + )); + + assert_eq!( + InboundLanes::::get(TEST_LANE_ID).latest_received_nonce, + 1 + ); + }); + } + + #[test] + fn receive_messages_proof_rejects_invalid_dispatch_weight() { + run_test(|| { + assert_noop!( + Module::::receive_messages_proof( + Origin::signed(1), + Ok(vec![Message { + key: MessageKey { + lane_id: TEST_LANE_ID, + nonce: 1, + }, + data: MessageData { + payload: REGULAR_PAYLOAD, + fee: 0, + }, + }]), + REGULAR_PAYLOAD.1 - 1, + ), + Error::::InvalidMessagesDispatchWeight, + ); + }); + } + + #[test] + fn receive_messages_proof_rejects_invalid_proof() { + run_test(|| { + assert_noop!( + Module::::receive_messages_proof(Origin::signed(1), Err(()), 0), + Error::::InvalidMessagesProof, + ); + }); + } + + #[test] + fn receive_messages_delivery_proof_works() { + run_test(|| { + send_regular_message(); + receive_messages_delivery_proof(); + + assert_eq!( + OutboundLanes::::get(&TEST_LANE_ID).latest_received_nonce, + 1, + ); + }); + } + + #[test] + fn receive_messages_delivery_proof_rejects_invalid_proof() { + run_test(|| { + assert_noop!( + Module::::receive_messages_delivery_proof(Origin::signed(1), Err(()),), + Error::::InvalidMessagesDeliveryProof, + ); + }); + } +} diff --git a/bridges/modules/message-lane/src/mock.rs b/bridges/modules/message-lane/src/mock.rs index c0d27e389aa3b..a682d6705b55b 100644 --- a/bridges/modules/message-lane/src/mock.rs +++ b/bridges/modules/message-lane/src/mock.rs @@ -14,7 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use bp_message_lane::LaneId; +use crate::Trait; + +use bp_message_lane::{ + source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, TargetHeaderChain}, + target_chain::{MessageDispatch, SourceHeaderChain}, + LaneId, Message, MessageData, MessageNonce, +}; use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, weights::Weight}; use sp_core::H256; use sp_runtime::{ @@ -23,10 +29,9 @@ use sp_runtime::{ Perbill, }; -use crate::Trait; - pub type AccountId = u64; -pub type TestPayload = u64; +pub type TestPayload = (u64, Weight); +pub type TestMessageFee = u64; #[derive(Clone, Eq, PartialEq, Debug)] pub struct TestRuntime; @@ -69,7 +74,7 @@ impl frame_system::Trait for TestRuntime { type DbWeight = (); type BlockExecutionWeight = (); type ExtrinsicBaseWeight = (); - type MaximumExtrinsicWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; type AvailableBlockRatio = AvailableBlockRatio; type MaximumBlockLength = MaximumBlockLength; type Version = (); @@ -89,16 +94,140 @@ impl Trait for TestRuntime { type Event = TestEvent; type Payload = TestPayload; type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; - type OnMessageReceived = (); + + type MessageFee = TestMessageFee; + type TargetHeaderChain = TestTargetHeaderChain; + type LaneMessageVerifier = TestLaneMessageVerifier; + type MessageDeliveryAndDispatchPayment = TestMessageDeliveryAndDispatchPayment; + + type SourceHeaderChain = TestSourceHeaderChain; + type MessageDispatch = TestMessageDispatch; } +/// Error that is returned by all test implementations. +pub const TEST_ERROR: &str = "Test error"; + /// Lane that we're using in tests. pub const TEST_LANE_ID: LaneId = [0, 0, 0, 1]; /// Regular message payload. -pub const REGULAR_PAYLOAD: TestPayload = 0; +pub const REGULAR_PAYLOAD: TestPayload = (0, 50); + +/// Payload that is rejected by `TestTargetHeaderChain`. +pub const PAYLOAD_REJECTED_BY_TARGET_CHAIN: TestPayload = (1, 50); + +/// Target header chain that is used in tests. +#[derive(Debug, Default)] +pub struct TestTargetHeaderChain; + +impl TargetHeaderChain for TestTargetHeaderChain { + type Error = &'static str; + + type MessagesDeliveryProof = Result<(LaneId, MessageNonce), ()>; + + fn verify_message(payload: &TestPayload) -> Result<(), Self::Error> { + if *payload == PAYLOAD_REJECTED_BY_TARGET_CHAIN { + Err(TEST_ERROR) + } else { + Ok(()) + } + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, MessageNonce), Self::Error> { + proof.map_err(|_| TEST_ERROR) + } +} + +/// Lane message verifier that is used in tests. +#[derive(Debug, Default)] +pub struct TestLaneMessageVerifier; + +impl LaneMessageVerifier for TestLaneMessageVerifier { + type Error = &'static str; + + fn verify_message( + _submitter: &AccountId, + delivery_and_dispatch_fee: &TestMessageFee, + _lane: &LaneId, + _payload: &TestPayload, + ) -> Result<(), Self::Error> { + if *delivery_and_dispatch_fee != 0 { + Ok(()) + } else { + Err(TEST_ERROR) + } + } +} + +/// Message fee payment system that is used in tests. +#[derive(Debug, Default)] +pub struct TestMessageDeliveryAndDispatchPayment; + +impl TestMessageDeliveryAndDispatchPayment { + /// Reject all payments. + pub fn reject_payments() { + frame_support::storage::unhashed::put(b":reject-message-fee:", &true); + } + + /// Returns true if given fee has been paid by given relayer. + pub fn is_fee_paid(submitter: AccountId, fee: TestMessageFee) -> bool { + frame_support::storage::unhashed::get(b":message-fee:") == Some((submitter, fee)) + } +} + +impl MessageDeliveryAndDispatchPayment for TestMessageDeliveryAndDispatchPayment { + type Error = &'static str; + + fn pay_delivery_and_dispatch_fee(submitter: &AccountId, fee: &TestMessageFee) -> Result<(), Self::Error> { + if frame_support::storage::unhashed::get(b":reject-message-fee:") == Some(true) { + return Err(TEST_ERROR); + } + + frame_support::storage::unhashed::put(b":message-fee:", &(submitter, fee)); + Ok(()) + } +} + +/// Source header chain that is used in tests. +#[derive(Debug)] +pub struct TestSourceHeaderChain; + +impl SourceHeaderChain for TestSourceHeaderChain { + type Error = &'static str; + + type MessagesProof = Result>, ()>; + + fn verify_messages_proof( + proof: Self::MessagesProof, + ) -> Result>, Self::Error> { + proof.map_err(|_| TEST_ERROR) + } +} + +/// Source header chain that is used in tests. +#[derive(Debug)] +pub struct TestMessageDispatch; + +impl MessageDispatch for TestMessageDispatch { + fn dispatch_weight(message: &Message) -> Weight { + message.data.payload.1 + } + + fn dispatch(_message: Message) {} +} + +/// Return message data with valid fee for given payload. +pub fn message_data(payload: TestPayload) -> MessageData { + MessageData { payload, fee: 1 } +} /// Run message lane test. pub fn run_test(test: impl FnOnce() -> T) -> T { - sp_io::TestExternalities::new(Default::default()).execute_with(test) + let t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(test) } diff --git a/bridges/modules/message-lane/src/outbound_lane.rs b/bridges/modules/message-lane/src/outbound_lane.rs index 52a1404ba7a76..8afcd0702869b 100644 --- a/bridges/modules/message-lane/src/outbound_lane.rs +++ b/bridges/modules/message-lane/src/outbound_lane.rs @@ -16,12 +16,14 @@ //! Everything about outgoing messages sending. -use bp_message_lane::{LaneId, MessageNonce, OutboundLaneData}; +use bp_message_lane::{LaneId, MessageData, MessageNonce, OutboundLaneData}; /// Outbound lane storage. pub trait OutboundLaneStorage { /// Message payload. type Payload; + /// Delivery and dispatch fee type on source chain. + type MessageFee; /// Lane id. fn id(&self) -> LaneId; @@ -31,9 +33,9 @@ pub trait OutboundLaneStorage { fn set_data(&mut self, data: OutboundLaneData); /// Returns saved outbound message payload. #[cfg(test)] - fn message(&self, nonce: &MessageNonce) -> Option; + fn message(&self, nonce: &MessageNonce) -> Option>; /// Save outbound message in the storage. - fn save_message(&mut self, nonce: MessageNonce, payload: Self::Payload); + fn save_message(&mut self, nonce: MessageNonce, message_data: MessageData); /// Remove outbound message from the storage. fn remove_message(&mut self, nonce: &MessageNonce); } @@ -52,22 +54,22 @@ impl OutboundLane { /// Send message over lane. /// /// Returns new message nonce. - pub fn send_message(&mut self, payload: S::Payload) -> MessageNonce { + pub fn send_message(&mut self, message_data: MessageData) -> MessageNonce { let mut data = self.storage.data(); let nonce = data.latest_generated_nonce + 1; data.latest_generated_nonce = nonce; - self.storage.save_message(nonce, payload); + self.storage.save_message(nonce, message_data); self.storage.set_data(data); nonce } - /// Confirm message receival. + /// Confirm messages delivery. /// /// Returns `None` if confirmation is wrong/duplicate. /// Returns `Some` with inclusive ranges of message nonces that have been received. - pub fn confirm_receival(&mut self, latest_received_nonce: MessageNonce) -> Option<(MessageNonce, MessageNonce)> { + pub fn confirm_delivery(&mut self, latest_received_nonce: MessageNonce) -> Option<(MessageNonce, MessageNonce)> { let mut data = self.storage.data(); if latest_received_nonce <= data.latest_received_nonce || latest_received_nonce > data.latest_generated_nonce { return None; @@ -107,7 +109,7 @@ impl OutboundLane { mod tests { use super::*; use crate::{ - mock::{run_test, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID}, + mock::{message_data, run_test, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID}, outbound_lane, }; @@ -116,57 +118,57 @@ mod tests { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); assert_eq!(lane.storage.data().latest_generated_nonce, 0); - assert_eq!(lane.send_message(REGULAR_PAYLOAD), 1); + assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 1); assert!(lane.storage.message(&1).is_some()); assert_eq!(lane.storage.data().latest_generated_nonce, 1); }); } #[test] - fn confirm_receival_works() { + fn confirm_delivery_works() { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - assert_eq!(lane.send_message(REGULAR_PAYLOAD), 1); - assert_eq!(lane.send_message(REGULAR_PAYLOAD), 2); - assert_eq!(lane.send_message(REGULAR_PAYLOAD), 3); + assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 1); + assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 2); + assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 3); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); - assert_eq!(lane.confirm_receival(3), Some((1, 3))); + assert_eq!(lane.confirm_delivery(3), Some((1, 3))); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); }); } #[test] - fn confirm_receival_rejects_nonce_lesser_than_latest_received() { + fn confirm_delivery_rejects_nonce_lesser_than_latest_received() { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - lane.send_message(REGULAR_PAYLOAD); - lane.send_message(REGULAR_PAYLOAD); - lane.send_message(REGULAR_PAYLOAD); + lane.send_message(message_data(REGULAR_PAYLOAD)); + lane.send_message(message_data(REGULAR_PAYLOAD)); + lane.send_message(message_data(REGULAR_PAYLOAD)); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); - assert_eq!(lane.confirm_receival(3), Some((1, 3))); - assert_eq!(lane.confirm_receival(3), None); + assert_eq!(lane.confirm_delivery(3), Some((1, 3))); + assert_eq!(lane.confirm_delivery(3), None); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.confirm_receival(2), None); + assert_eq!(lane.confirm_delivery(2), None); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); }); } #[test] - fn confirm_receival_rejects_nonce_larger_than_last_generated() { + fn confirm_delivery_rejects_nonce_larger_than_last_generated() { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - lane.send_message(REGULAR_PAYLOAD); - lane.send_message(REGULAR_PAYLOAD); - lane.send_message(REGULAR_PAYLOAD); + lane.send_message(message_data(REGULAR_PAYLOAD)); + lane.send_message(message_data(REGULAR_PAYLOAD)); + lane.send_message(message_data(REGULAR_PAYLOAD)); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); - assert_eq!(lane.confirm_receival(10), None); + assert_eq!(lane.confirm_delivery(10), None); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); }); @@ -180,17 +182,17 @@ mod tests { assert_eq!(lane.prune_messages(100), 0); assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); // when nothing is confirmed, nothing is pruned - lane.send_message(REGULAR_PAYLOAD); - lane.send_message(REGULAR_PAYLOAD); - lane.send_message(REGULAR_PAYLOAD); + lane.send_message(message_data(REGULAR_PAYLOAD)); + lane.send_message(message_data(REGULAR_PAYLOAD)); + lane.send_message(message_data(REGULAR_PAYLOAD)); assert_eq!(lane.prune_messages(100), 0); assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); // after confirmation, some messages are received - assert_eq!(lane.confirm_receival(2), Some((1, 2))); + assert_eq!(lane.confirm_delivery(2), Some((1, 2))); assert_eq!(lane.prune_messages(100), 2); assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3); // after last message is confirmed, everything is pruned - assert_eq!(lane.confirm_receival(3), Some((3, 3))); + assert_eq!(lane.confirm_delivery(3), Some((3, 3))); assert_eq!(lane.prune_messages(100), 1); assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); diff --git a/bridges/primitives/message-dispatch/src/lib.rs b/bridges/primitives/message-dispatch/src/lib.rs index 04b40e597268f..1877df90bcb37 100644 --- a/bridges/primitives/message-dispatch/src/lib.rs +++ b/bridges/primitives/message-dispatch/src/lib.rs @@ -29,6 +29,12 @@ pub trait MessageDispatch { /// A type of the message to be dispatched. type Message: codec::Decode; + /// Estimate dispatch weight. + /// + /// This function must: (1) be instant and (2) return correct upper bound + /// of dispatch weight. + fn dispatch_weight(message: &Self::Message) -> Weight; + /// Dispatches the message internally. /// /// `bridge` indicates instance of deployed bridge where the message came from. @@ -36,5 +42,5 @@ pub trait MessageDispatch { /// `id` is a short unique if of the message. /// /// Returns post-dispatch (actual) message weight. - fn dispatch(bridge: InstanceId, id: MessageId, message: Self::Message) -> Weight; + fn dispatch(bridge: InstanceId, id: MessageId, message: Self::Message); } diff --git a/bridges/primitives/message-lane/Cargo.toml b/bridges/primitives/message-lane/Cargo.toml index b937e452ac72f..538a5d6fb72ac 100644 --- a/bridges/primitives/message-lane/Cargo.toml +++ b/bridges/primitives/message-lane/Cargo.toml @@ -11,15 +11,29 @@ codec = { package = "parity-scale-codec", version = "1.3.1", default-features = # Substrate Based Dependencies +[dependencies.frame-support] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate.git" + [dependencies.sp-api] version = "2.0.0-rc6" tag = 'v2.0.0-rc6' default-features = false git = "https://github.com/paritytech/substrate.git" +[dependencies.sp-std] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate.git" + [features] default = ["std"] std = [ "codec/std", - "sp-api/std" + "frame-support/std", + "sp-api/std", + "sp-std/std" ] diff --git a/bridges/primitives/message-lane/src/lib.rs b/bridges/primitives/message-lane/src/lib.rs index a4bfac459b8b9..a2e3e41cfd40c 100644 --- a/bridges/primitives/message-lane/src/lib.rs +++ b/bridges/primitives/message-lane/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -//! Primitives for sending and receiving Substrate <-> Substrate messages. +//! Primitives of message lane module. #![cfg_attr(not(feature = "std"), no_std)] // RuntimeApi generated functions @@ -23,7 +23,12 @@ #![allow(clippy::unnecessary_mut_passed)] use codec::{Decode, Encode}; +use frame_support::RuntimeDebug; use sp_api::decl_runtime_apis; +use sp_std::prelude::*; + +pub mod source_chain; +pub mod target_chain; /// Lane identifier. pub type LaneId = [u8; 4]; @@ -31,8 +36,11 @@ pub type LaneId = [u8; 4]; /// Message nonce. Valid messages will never have 0 nonce. pub type MessageNonce = u64; +/// Message id as a tuple. +pub type MessageId = (LaneId, MessageNonce); + /// Message key (unique message identifier) as it is stored in the storage. -#[derive(Encode, Decode, Clone)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] pub struct MessageKey { /// ID of the message lane. pub lane_id: LaneId, @@ -40,37 +48,33 @@ pub struct MessageKey { pub nonce: MessageNonce, } -/// Message as it is stored in the storage. -#[derive(Encode, Decode, Clone)] -pub struct Message { - /// Message key. - pub key: MessageKey, +/// Message data as it is stored in the storage. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct MessageData { /// Message payload. pub payload: Payload, + /// Message delivery and dispatch fee, paid by the submitter. + pub fee: Fee, } -/// Called when inbound message is received. -pub trait OnMessageReceived { - /// Called when inbound message is received. - /// - /// It is up to the implementers of this trait to determine whether the message - /// is invalid (i.e. improperly encoded, has too large weight, ...) or not. - fn on_message_received(message: Message); -} - -impl OnMessageReceived for () { - fn on_message_received(_message: Message) {} +/// Message as it is stored in the storage. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Message { + /// Message key. + pub key: MessageKey, + /// Message data. + pub data: MessageData, } /// Inbound lane data. -#[derive(Default, Encode, Decode, Clone)] +#[derive(Default, Encode, Decode, Clone, RuntimeDebug, PartialEq)] pub struct InboundLaneData { /// Nonce of latest message that we have received from bridged chain. pub latest_received_nonce: MessageNonce, } /// Outbound lane data. -#[derive(Encode, Decode, Clone)] +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq)] pub struct OutboundLaneData { /// Nonce of oldest message that we haven't yet pruned. May point to not-yet-generated message if /// all sent messages are already pruned. diff --git a/bridges/primitives/message-lane/src/source_chain.rs b/bridges/primitives/message-lane/src/source_chain.rs new file mode 100644 index 0000000000000..cce5e0d5bfbce --- /dev/null +++ b/bridges/primitives/message-lane/src/source_chain.rs @@ -0,0 +1,97 @@ +// Copyright 2019-2020 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 message lane module, that are used on the source chain. + +use crate::{LaneId, MessageNonce}; + +use frame_support::Parameter; +use sp_std::fmt::Debug; + +/// Target chain API. Used by source chain to verify target chain proofs. +/// +/// All implementations of this trait should only work with finalized data that +/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane +/// that's stuck) and/or processing messages without paying fees. +pub trait TargetHeaderChain { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Proof that messages have been received by target chain. + type MessagesDeliveryProof: Parameter; + + /// Verify message payload before we accept it. + /// + /// **CAUTION**: this is very important function. Incorrect implementation may lead + /// to stuck lanes and/or relayers loses. + /// + /// The proper implementation must ensure that the delivery-transaction with this + /// payload would (at least) be accepted into target chain transaction pool AND + /// eventually will be successfully 'mined'. The most obvious incorrect implementation + /// example would be implementation for BTC chain that accepts payloads larger than + /// 1MB. BTC nodes aren't accepting transactions that are larger than 1MB, so relayer + /// will be unable to craft valid transaction => this (and all subsequent) messages will + /// never be delivered. + fn verify_message(payload: &Payload) -> Result<(), Self::Error>; + + /// Verify messages delivery proof and return lane && nonce of the latest recevied message. + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, MessageNonce), Self::Error>; +} + +/// Lane message verifier. +/// +/// Runtime developer may implement any additional validation logic over message-lane mechanism. +/// E.g. if lanes should have some security (e.g. you can only accept Lane1 messages from +/// Submitter1, Lane2 messages for those who has submitted first message to this lane, disable +/// Lane3 until some block, ...), then it may be built using this verifier. +/// +/// Any fee requirements should also be enforced here. +pub trait LaneMessageVerifier { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the lane. + fn verify_message( + submitter: &Submitter, + delivery_and_dispatch_fee: &Fee, + lane: &LaneId, + payload: &Payload, + ) -> Result<(), Self::Error>; +} + +/// Message delivery payment. It is called as a part of submit-message transaction. Transaction +/// submitter is paying (in source chain tokens/assets) for: +/// +/// 1) submit-message-transaction-fee itself. This fee is not included in the +/// `delivery_and_dispatch_fee` and is witheld by the regular transaction payment mechanism; +/// 2) message-delivery-transaction-fee. It is submitted to the target node by relayer; +/// 3) message-dispatch fee. It is paid by relayer for processing message by target chain; +/// 4) message-receiving-delivery-transaction-fee. It is submitted to the source node +/// by relayer. +/// +/// So to be sure that any non-altruist relayer would agree to deliver message, submitter +/// should set `delivery_and_dispatch_fee` to at least (equialent of): sum of fees from (2) +/// to (4) above, plus some interest for the relayer. +pub trait MessageDeliveryAndDispatchPayment { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Withhold/write-off delivery_and_dispatch_fee from submitter account to + /// some relayers-fund account. + fn pay_delivery_and_dispatch_fee(submitter: &AccountId, fee: &Balance) -> Result<(), Self::Error>; +} diff --git a/bridges/primitives/message-lane/src/target_chain.rs b/bridges/primitives/message-lane/src/target_chain.rs new file mode 100644 index 0000000000000..7287b26ae5713 --- /dev/null +++ b/bridges/primitives/message-lane/src/target_chain.rs @@ -0,0 +1,56 @@ +// Copyright 2019-2020 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 message lane module, that are used on the target chain. + +use crate::Message; + +use frame_support::{weights::Weight, Parameter}; +use sp_std::{fmt::Debug, prelude::*}; + +/// Source chain API. Used by target chain, to verify source chain proofs. +/// +/// All implementations of this trait should only work with finalized data that +/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane +/// that's stuck) and/or processing messages without paying fees. +pub trait SourceHeaderChain { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Proof that messages are sent from source chain. + type MessagesProof: Parameter; + + /// Verify messages proof and return proved messages. + /// + /// Messages vector is required to be sorted by nonce within each lane. Out-of-order + /// messages will be rejected. + fn verify_messages_proof(proof: Self::MessagesProof) -> Result>, Self::Error>; +} + +/// Called when inbound message is received. +pub trait MessageDispatch { + /// Estimate dispatch weight. + /// + /// This function must: (1) be instant and (2) return correct upper bound + /// of dispatch weight. + fn dispatch_weight(message: &Message) -> Weight; + + /// Called when inbound message is received. + /// + /// It is up to the implementers of this trait to determine whether the message + /// is invalid (i.e. improperly encoded, has too large weight, ...) or not. + fn dispatch(message: Message); +} diff --git a/bridges/relays/messages-relay/src/message_lane.rs b/bridges/relays/messages-relay/src/message_lane.rs index 8671451394eb1..9db30926a0ede 100644 --- a/bridges/relays/messages-relay/src/message_lane.rs +++ b/bridges/relays/messages-relay/src/message_lane.rs @@ -17,7 +17,7 @@ //! One-way message lane types. Within single one-way lane we have three 'races' where we try to: //! //! 1) relay new messages from source to target node; -//! 2) relay proof-of-receiving from target to source node. +//! 2) relay proof-of-delivery from target to source node. use relay_utils::HeaderId;