Fees, weights, message delivery and dispatch (#339)

* 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 <tomusdrw@users.noreply.github.com>

* removed messages_processed

* Update primitives/message-lane/src/source_chain.rs

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Update modules/message-lane/src/lib.rs

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* 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 <HCastano@users.noreply.github.com>

* cargo fmt --all

* clippy

* let dispatch_weight to be larger than actual_dispatch_weight

* post-merge fix

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
This commit is contained in:
Svyatoslav Nikolsky
2020-09-25 22:22:44 +03:00
committed by Bastian Köcher
parent 44beb30836
commit 3cd8937b38
13 changed files with 799 additions and 141 deletions
@@ -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<S: InboundLaneStorage> InboundLane<S> {
}
/// Receive new message.
pub fn receive_message<P: OnMessageReceived<S::Payload>>(
pub fn receive_message<P: MessageDispatch<S::Payload, S::MessageFee>>(
&mut self,
nonce: MessageNonce,
payload: S::Payload,
message_data: MessageData<S::Payload, S::MessageFee>,
) -> bool {
let mut data = self.storage.data();
let is_correct_message = nonce == data.latest_received_nonce + 1;
@@ -57,12 +61,12 @@ impl<S: InboundLaneStorage> InboundLane<S> {
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::<TestRuntime, _>(TEST_LANE_ID);
assert!(!lane.receive_message::<()>(10, REGULAR_PAYLOAD));
assert!(!lane.receive_message::<TestMessageDispatch>(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::<TestRuntime, _>(TEST_LANE_ID);
assert!(lane.receive_message::<()>(1, REGULAR_PAYLOAD));
assert!(lane.receive_message::<TestMessageDispatch>(1, message_data(REGULAR_PAYLOAD)));
assert_eq!(lane.storage.data().latest_received_nonce, 1);
});
}
+394 -52
View File
@@ -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<I = DefaultInstance>: frame_system::Trait {
// General types
/// They overarching event type.
type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
/// Message payload.
@@ -57,8 +67,50 @@ pub trait Trait<I = DefaultInstance>: 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<MessageNonce>;
/// Called when message has been received.
type OnMessageReceived: OnMessageReceived<Self::Payload>;
// 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<Self::Payload>;
/// Message payload verifier.
type LaneMessageVerifier: LaneMessageVerifier<Self::AccountId, Self::Payload, Self::MessageFee>;
/// Message delivery payment.
type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment<Self::AccountId, Self::MessageFee>;
// Types that are used by inbound_lane (on target chain).
/// Source header chain, as it is represented on target chain.
type SourceHeaderChain: SourceHeaderChain<Self::Payload, Self::MessageFee>;
/// Message dispatch.
type MessageDispatch: MessageDispatch<Self::Payload, Self::MessageFee>;
}
/// Shortcut to messages proof type for Trait.
type MessagesProofOf<T, I> = <<T as Trait<I>>::SourceHeaderChain as SourceHeaderChain<
<T as Trait<I>>::Payload,
<T as Trait<I>>::MessageFee,
>>::MessagesProof;
/// Shortcut to messages delivery proof type for Trait.
type MessagesDeliveryProofOf<T, I> =
<<T as Trait<I>>::TargetHeaderChain as TargetHeaderChain<<T as Trait<I>>::Payload>>::MessagesDeliveryProof;
decl_error! {
pub enum Error for Module<T: Trait<I>, 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<T::Payload>;
OutboundMessages: map hasher(blake2_128_concat) MessageKey => Option<MessageData<T::Payload, T::MessageFee>>;
}
}
@@ -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::<T, I>::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::<T, I>::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::<T, I>::FailedToWithdrawMessageFee
})?;
// finally, save message in outbound storage and emit event
let mut lane = outbound_lane::<T, I>(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<T: Trait<I>, I: Instance> Module<T, I> {
// =========================================================================================
// === Exposed mutables ====================================================================
// =========================================================================================
/// Receive messages proof from bridged chain.
#[weight = DELIVERY_BASE_WEIGHT + dispatch_weight]
pub fn receive_messages_proof(
origin,
proof: MessagesProofOf<T, I>,
dispatch_weight: Weight,
) -> DispatchResult {
let _ = ensure_signed(origin)?;
/// 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<Message<T::Payload>>) -> MessageNonce {
let mut correct_messages = 0;
for message in messages {
let mut lane = inbound_lane::<T, I>(message.key.lane_id);
if lane.receive_message::<T::OnMessageReceived>(message.key.nonce, message.payload) {
correct_messages += 1;
// 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::<T, I>::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::<T, I>::InvalidMessagesDispatchWeight.into());
}
// dispatch messages
let total_messages = messages.len();
let mut valid_messages = 0;
for message in messages {
let mut lane = inbound_lane::<T, I>(message.key.lane_id);
if lane.receive_message::<T::MessageDispatch>(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 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<T, I>) -> 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,
);
/// 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::<T, I>(*lane_id);
let received_range = lane.confirm_receival(latest_received_nonce);
Error::<T, I>::InvalidMessagesDeliveryProof
})?;
if let Some(received_range) = received_range {
Self::deposit_event(RawEvent::MessagesDelivered(
*lane_id,
received_range.0,
received_range.1,
));
let mut lane = outbound_lane::<T, I>(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<T, I = DefaultInstance> {
impl<T: Trait<I>, I: Instance> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
type Payload = T::Payload;
type MessageFee = T::MessageFee;
fn id(&self) -> LaneId {
self.lane_id
@@ -199,6 +353,7 @@ struct RuntimeOutboundLaneStorage<T, I = DefaultInstance> {
impl<T: Trait<I>, I: Instance> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
type Payload = T::Payload;
type MessageFee = T::MessageFee;
fn id(&self) -> LaneId {
self.lane_id
@@ -213,20 +368,20 @@ impl<T: Trait<I>, I: Instance> OutboundLaneStorage for RuntimeOutboundLaneStorag
}
#[cfg(test)]
fn message(&self, nonce: &MessageNonce) -> Option<Self::Payload> {
fn message(&self, nonce: &MessageNonce) -> Option<MessageData<T::Payload, T::MessageFee>> {
OutboundMessages::<T, I>::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<T::Payload, T::MessageFee>) {
OutboundMessages::<T, I>::insert(
MessageKey {
lane_id: self.lane_id,
nonce,
},
payload,
mesage_data,
);
}
@@ -237,3 +392,190 @@ impl<T: Trait<I>, 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::<TestRuntime>::set_block_number(1);
System::<TestRuntime>::reset_events();
assert_ok!(Module::<TestRuntime>::send_message(
Origin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
REGULAR_PAYLOAD.1,
));
// check event with assigned nonce
assert_eq!(
System::<TestRuntime>::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::<TestRuntime>::set_block_number(1);
System::<TestRuntime>::reset_events();
assert_ok!(Module::<TestRuntime>::receive_messages_delivery_proof(
Origin::signed(1),
Ok((TEST_LANE_ID, 1)),
));
assert_eq!(
System::<TestRuntime>::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::<TestRuntime>::send_message(
Origin::signed(1),
TEST_LANE_ID,
PAYLOAD_REJECTED_BY_TARGET_CHAIN,
PAYLOAD_REJECTED_BY_TARGET_CHAIN.1
),
Error::<TestRuntime, DefaultInstance>::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::<TestRuntime>::send_message(Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, 0),
Error::<TestRuntime, DefaultInstance>::MessageRejectedByLaneVerifier,
);
});
}
#[test]
fn message_send_fails_if_submitter_cant_pay_message_fee() {
run_test(|| {
TestMessageDeliveryAndDispatchPayment::reject_payments();
assert_noop!(
Module::<TestRuntime>::send_message(
Origin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
REGULAR_PAYLOAD.1
),
Error::<TestRuntime, DefaultInstance>::FailedToWithdrawMessageFee,
);
});
}
#[test]
fn receive_messages_proof_works() {
run_test(|| {
assert_ok!(Module::<TestRuntime>::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::<DefaultInstance>::get(TEST_LANE_ID).latest_received_nonce,
1
);
});
}
#[test]
fn receive_messages_proof_rejects_invalid_dispatch_weight() {
run_test(|| {
assert_noop!(
Module::<TestRuntime>::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::<TestRuntime, DefaultInstance>::InvalidMessagesDispatchWeight,
);
});
}
#[test]
fn receive_messages_proof_rejects_invalid_proof() {
run_test(|| {
assert_noop!(
Module::<TestRuntime, DefaultInstance>::receive_messages_proof(Origin::signed(1), Err(()), 0),
Error::<TestRuntime, DefaultInstance>::InvalidMessagesProof,
);
});
}
#[test]
fn receive_messages_delivery_proof_works() {
run_test(|| {
send_regular_message();
receive_messages_delivery_proof();
assert_eq!(
OutboundLanes::<DefaultInstance>::get(&TEST_LANE_ID).latest_received_nonce,
1,
);
});
}
#[test]
fn receive_messages_delivery_proof_rejects_invalid_proof() {
run_test(|| {
assert_noop!(
Module::<TestRuntime>::receive_messages_delivery_proof(Origin::signed(1), Err(()),),
Error::<TestRuntime, DefaultInstance>::InvalidMessagesDeliveryProof,
);
});
}
}
+137 -8
View File
@@ -14,7 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
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<TestPayload> 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<AccountId, TestPayload, TestMessageFee> 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<AccountId, TestMessageFee> 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<TestPayload, TestMessageFee> for TestSourceHeaderChain {
type Error = &'static str;
type MessagesProof = Result<Vec<Message<TestPayload, TestMessageFee>>, ()>;
fn verify_messages_proof(
proof: Self::MessagesProof,
) -> Result<Vec<Message<TestPayload, TestMessageFee>>, Self::Error> {
proof.map_err(|_| TEST_ERROR)
}
}
/// Source header chain that is used in tests.
#[derive(Debug)]
pub struct TestMessageDispatch;
impl MessageDispatch<TestPayload, TestMessageFee> for TestMessageDispatch {
fn dispatch_weight(message: &Message<TestPayload, TestMessageFee>) -> Weight {
message.data.payload.1
}
fn dispatch(_message: Message<TestPayload, TestMessageFee>) {}
}
/// Return message data with valid fee for given payload.
pub fn message_data(payload: TestPayload) -> MessageData<TestPayload, TestMessageFee> {
MessageData { payload, fee: 1 }
}
/// Run message lane test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
sp_io::TestExternalities::new(Default::default()).execute_with(test)
let t = frame_system::GenesisConfig::default()
.build_storage::<TestRuntime>()
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(test)
}
@@ -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<Self::Payload>;
fn message(&self, nonce: &MessageNonce) -> Option<MessageData<Self::Payload, Self::MessageFee>>;
/// 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<Self::Payload, Self::MessageFee>);
/// Remove outbound message from the storage.
fn remove_message(&mut self, nonce: &MessageNonce);
}
@@ -52,22 +54,22 @@ impl<S: OutboundLaneStorage> OutboundLane<S> {
/// 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<S::Payload, S::MessageFee>) -> 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<S: OutboundLaneStorage> OutboundLane<S> {
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::<TestRuntime, _>(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::<TestRuntime, _>(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::<TestRuntime, _>(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::<TestRuntime, _>(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);
});