// 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 .
//! Runtime module that allows sending and receiving messages using lane concept:
//!
//! 1) the message is sent using `send_message()` call;
//! 2) every outbound message is assigned nonce;
//! 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-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
//! delivered to the the bridged chain, it is reported using `MessagesDelivered` event.
#![cfg_attr(not(feature = "std"), no_std)]
use crate::inbound_lane::{InboundLane, InboundLaneStorage};
use crate::outbound_lane::{OutboundLane, OutboundLaneStorage};
use bp_message_lane::{
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_system::ensure_signed;
use sp_std::{marker::PhantomData, prelude::*};
mod inbound_lane;
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.
type Payload: Parameter;
/// Maximal number of messages that may be pruned during maintenance. Maintenance occurs
/// whenever outbound lane is updated - i.e. when new message is sent, or receival is
/// confirmed. The reason is that if you want to use lane, you should be ready to pay
/// for it.
type MaxMessagesToPruneAtOnce: Get;
// 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! {
trait Store for Module, I: Instance = DefaultInstance> as MessageLane {
/// Map of lane id => inbound lane data.
InboundLanes: map hasher(blake2_128_concat) LaneId => InboundLaneData;
/// 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>;
}
}
decl_event!(
pub enum Event where
::AccountId,
{
/// Message has been accepted and is waiting to be delivered.
MessageAccepted(LaneId, MessageNonce),
/// Messages in the inclusive range have been delivered and processed by the bridged chain.
MessagesDelivered(LaneId, MessageNonce, MessageNonce),
/// Phantom member, never used.
Dummy(PhantomData<(AccountId, I)>),
}
);
decl_module! {
pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: T::Origin {
/// Deposit one of this module's events by using the default implementation.
fn deposit_event() = default;
/// Send message over lane.
#[weight = 0] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
pub fn send_message(
origin,
lane_id: LaneId,
payload: T::Payload,
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(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(())
}
/// 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(())
}
/// 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(())
}
}
}
/// Creates new inbound lane object, backed by runtime storage.
fn inbound_lane, I: Instance>(lane_id: LaneId) -> InboundLane> {
InboundLane::new(RuntimeInboundLaneStorage {
lane_id,
_phantom: Default::default(),
})
}
/// Creates new outbound lane object, backed by runtime storage.
fn outbound_lane, I: Instance>(lane_id: LaneId) -> OutboundLane> {
OutboundLane::new(RuntimeOutboundLaneStorage {
lane_id,
_phantom: Default::default(),
})
}
/// Runtime inbound lane storage.
struct RuntimeInboundLaneStorage {
lane_id: LaneId,
_phantom: PhantomData<(T, I)>,
}
impl, I: Instance> InboundLaneStorage for RuntimeInboundLaneStorage {
type Payload = T::Payload;
type MessageFee = T::MessageFee;
fn id(&self) -> LaneId {
self.lane_id
}
fn data(&self) -> InboundLaneData {
InboundLanes::::get(&self.lane_id)
}
fn set_data(&mut self, data: InboundLaneData) {
InboundLanes::::insert(&self.lane_id, data)
}
}
/// Runtime outbound lane storage.
struct RuntimeOutboundLaneStorage {
lane_id: LaneId,
_phantom: PhantomData<(T, I)>,
}
impl, I: Instance> OutboundLaneStorage for RuntimeOutboundLaneStorage {
type Payload = T::Payload;
type MessageFee = T::MessageFee;
fn id(&self) -> LaneId {
self.lane_id
}
fn data(&self) -> OutboundLaneData {
OutboundLanes::::get(&self.lane_id)
}
fn set_data(&mut self, data: OutboundLaneData) {
OutboundLanes::::insert(&self.lane_id, data)
}
#[cfg(test)]
fn message(&self, nonce: &MessageNonce) -> Option> {
OutboundMessages::::get(MessageKey {
lane_id: self.lane_id,
nonce: *nonce,
})
}
fn save_message(&mut self, nonce: MessageNonce, mesage_data: MessageData) {
OutboundMessages::::insert(
MessageKey {
lane_id: self.lane_id,
nonce,
},
mesage_data,
);
}
fn remove_message(&mut self, nonce: &MessageNonce) {
OutboundMessages::::remove(MessageKey {
lane_id: self.lane_id,
nonce: *nonce,
});
}
}
#[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,
);
});
}
}