Newer
Older
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! 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.
//!
//! **IMPORTANT NOTE**: after generating weights (custom `WeighInfo` implementation) for
//! your runtime (where this module is plugged to), please add test for these weights.
//! The test should call the `ensure_weights_are_correct` function from this module.
//! If this test fails with your weights, then either weights are computed incorrectly,
//! or some benchmarks assumptions are broken for your runtime.
#![cfg_attr(not(feature = "std"), no_std)]
// Generated by `decl_event!`
#![allow(clippy::unused_unit)]
pub use crate::weights_ext::{
ensure_able_to_receive_confirmation, ensure_able_to_receive_message, ensure_weights_are_correct, WeightInfoExt,
EXPECTED_DEFAULT_MESSAGE_LENGTH,
};
use crate::inbound_lane::{InboundLane, InboundLaneStorage, ReceivalResult};
Svyatoslav Nikolsky
committed
use crate::outbound_lane::{OutboundLane, OutboundLaneStorage, ReceivalConfirmationResult};
use bp_messages::{
Svyatoslav Nikolsky
committed
source_chain::{
LaneMessageVerifier, MessageDeliveryAndDispatchPayment, OnDeliveryConfirmed, OnMessageAccepted,
RelayersRewards, TargetHeaderChain,
Svyatoslav Nikolsky
committed
},
target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
Svyatoslav Nikolsky
committed
total_unrewarded_messages, DeliveredMessages, InboundLaneData, LaneId, MessageData, MessageKey, MessageNonce,
OperatingMode, OutboundLaneData, Parameter as MessagesParameter, UnrewardedRelayersState,
Hector Bulgarini
committed
use bp_runtime::{ChainId, Size};
use frame_support::{
fail,
traits::Get,
weights::{Pays, PostDispatchInfo},
};
use frame_system::RawOrigin;
use num_traits::{SaturatingAdd, Zero};
use sp_core::H256;
use sp_runtime::traits::{BadOrigin, Convert};
use sp_std::{cell::RefCell, cmp::PartialOrd, marker::PhantomData, prelude::*};
mod inbound_lane;
mod outbound_lane;
Svyatoslav Nikolsky
committed
pub mod instant_payments;
Svyatoslav Nikolsky
committed
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
// General types
/// The overarching event type.
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
/// Benchmarks results from runtime we're plugged into.
type WeightInfo: WeightInfoExt;
Hector Bulgarini
committed
/// Gets the chain id value from the instance.
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#[pallet::constant]
type BridgedChainId: Get<ChainId>;
/// Pallet parameter that is opaque to the pallet itself, but may be used by the runtime
/// for integrating the pallet.
///
/// All pallet parameters may only be updated either by the root, or by the pallet owner.
type Parameter: MessagesParameter;
/// Maximal number of messages that may be pruned during maintenance. Maintenance occurs
/// whenever new message is sent. The reason is that if you want to use lane, you should
/// be ready to pay for its maintenance.
type MaxMessagesToPruneAtOnce: Get<MessageNonce>;
/// Maximal number of unrewarded relayer entries at inbound lane. Unrewarded means that the
/// relayer has delivered messages, but either confirmations haven't been delivered back to the
/// source chain, or we haven't received reward confirmations yet.
///
/// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep
/// in mind that the same relayer account may take several (non-consecutive) entries in this
/// set.
type MaxUnrewardedRelayerEntriesAtInboundLane: Get<MessageNonce>;
/// Maximal number of unconfirmed messages at inbound lane. Unconfirmed means that the
/// message has been delivered, but either confirmations haven't been delivered back to the
/// source chain, or we haven't received reward confirmations for these messages yet.
///
/// This constant limits difference between last message from last entry of the
/// `InboundLaneData::relayers` and first message at the first entry.
///
/// There is no point of making this parameter lesser than MaxUnrewardedRelayerEntriesAtInboundLane,
/// because then maximal number of relayer entries will be limited by maximal number of messages.
///
/// This value also represents maximal number of messages in single delivery transaction. Transaction
/// that is declaring more messages than this value, will be rejected. Even if these messages are
/// from different lanes.
type MaxUnconfirmedMessagesAtInboundLane: Get<MessageNonce>;
/// Payload type of outbound messages. This payload is dispatched on the bridged chain.
type OutboundPayload: Parameter + Size;
/// Message fee type of outbound messages. This fee is paid on this chain.
type OutboundMessageFee: Default + From<u64> + PartialOrd + Parameter + SaturatingAdd + Zero;
/// Payload type of inbound messages. This payload is dispatched on this chain.
type InboundPayload: Decode;
/// Message fee type of inbound messages. This fee is paid on the bridged chain.
type InboundMessageFee: Decode;
/// Identifier of relayer that deliver messages to this chain. Relayer reward is paid on the bridged chain.
type InboundRelayer: Parameter;
/// A type which can be turned into an AccountId from a 256-bit hash.
///
/// Used when deriving the shared relayer fund account.
type AccountIdConverter: sp_runtime::traits::Convert<sp_core::hash::H256, Self::AccountId>;
// Types that are used by outbound_lane (on source chain).
/// Target header chain.
type TargetHeaderChain: TargetHeaderChain<Self::OutboundPayload, Self::AccountId>;
/// Message payload verifier.
type LaneMessageVerifier: LaneMessageVerifier<Self::AccountId, Self::OutboundPayload, Self::OutboundMessageFee>;
/// Message delivery payment.
type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment<
Self::AccountId,
Self::OutboundMessageFee,
>;
/// Handler for accepted messages.
type OnMessageAccepted: OnMessageAccepted;
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/// Handler for delivered messages.
type OnDeliveryConfirmed: OnDeliveryConfirmed;
// Types that are used by inbound_lane (on target chain).
/// Source header chain, as it is represented on target chain.
type SourceHeaderChain: SourceHeaderChain<Self::InboundMessageFee>;
/// Message dispatch.
type MessageDispatch: MessageDispatch<
Self::AccountId,
Self::InboundMessageFee,
DispatchPayload = Self::InboundPayload,
>;
}
/// Shortcut to messages proof type for Config.
type MessagesProofOf<T, I> =
<<T as Config<I>>::SourceHeaderChain as SourceHeaderChain<<T as Config<I>>::InboundMessageFee>>::MessagesProof;
/// Shortcut to messages delivery proof type for Config.
type MessagesDeliveryProofOf<T, I> = <<T as Config<I>>::TargetHeaderChain as TargetHeaderChain<
<T as Config<I>>::OutboundPayload,
<T as frame_system::Config>::AccountId,
>>::MessagesDeliveryProof;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// May only be called either by root, or by `PalletOwner`.
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
ensure_owner_or_root::<T, I>(origin)?;
match new_owner {
log::info!(target: "runtime::bridge-messages", "Setting pallet Owner to: {:?}", new_owner);
log::info!(target: "runtime::bridge-messages", "Removed Owner of pallet.");
/// Halt or resume all/some pallet operations.
/// May only be called either by root, or by `PalletOwner`.
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(origin: OriginFor<T>, operating_mode: OperatingMode) -> DispatchResult {
ensure_owner_or_root::<T, I>(origin)?;
PalletOperatingMode::<T, I>::put(operating_mode);
log::info!(
target: "runtime::bridge-messages",
"Setting messages pallet operating mode to {:?}.",
operating_mode,
);
/// Update pallet parameter.
///
/// May only be called either by root, or by `PalletOwner`.
///
/// The weight is: single read for permissions check + 2 writes for parameter value and event.
#[pallet::weight((T::DbWeight::get().reads_writes(1, 2), DispatchClass::Operational))]
pub fn update_pallet_parameter(origin: OriginFor<T>, parameter: T::Parameter) -> DispatchResult {
ensure_owner_or_root::<T, I>(origin)?;
parameter.save();
Self::deposit_event(Event::ParameterUpdated(parameter));
Ok(())
}
/// Send message over lane.
#[pallet::weight(T::WeightInfo::send_message_weight(payload, T::DbWeight::get()))]
pub fn send_message(
origin: OriginFor<T>,
payload: T::OutboundPayload,
delivery_and_dispatch_fee: T::OutboundMessageFee,
) -> DispatchResultWithPostInfo {
crate::send_message::<T, I>(
origin.into().map_err(|_| BadOrigin)?,
payload,
delivery_and_dispatch_fee,
)
.map(|sent_message| sent_message.post_dispatch_info)
/// Pay additional fee for the message.
#[pallet::weight(T::WeightInfo::maximal_increase_message_fee())]
origin: OriginFor<T>,
lane_id: LaneId,
nonce: MessageNonce,
additional_fee: T::OutboundMessageFee,
) -> DispatchResultWithPostInfo {
ensure_not_halted::<T, I>()?;
// if someone tries to pay for already-delivered message, we're rejecting this intention
// (otherwise this additional fee will be locked forever in relayers fund)
//
// if someone tries to pay for not-yet-sent message, we're rejecting this intention, or
// we're risking to have mess in the storage
let lane = outbound_lane::<T, I>(lane_id);
ensure!(
nonce > lane.data().latest_received_nonce,
Error::<T, I>::MessageIsAlreadyDelivered
);
ensure!(
nonce <= lane.data().latest_generated_nonce,
Error::<T, I>::MessageIsNotYetSent
);
// withdraw additional fee from submitter
let submitter = origin.into().map_err(|_| BadOrigin)?;
T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
&submitter,
&additional_fee,
&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
)
.map_err(|err| {
target: "runtime::bridge-messages",
"Submitter {:?} can't pay additional fee {:?} for the message {:?}/{:?} to {:?}: {:?}",
submitter,
additional_fee,
lane_id,
nonce,
relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
err,
);
Error::<T, I>::FailedToWithdrawMessageFee
})?;
// and finally update fee in the storage
let message_key = MessageKey { lane_id, nonce };
let message_size = OutboundMessages::<T, I>::mutate(message_key, |message_data| {
// saturating_add is fine here - overflow here means that someone controls all
// chain funds, which shouldn't ever happen + `pay_delivery_and_dispatch_fee`
// above will fail before we reach here
let message_data = message_data
.as_mut()
.expect("the message is sent and not yet delivered; so it is in the storage; qed");
message_data.fee = message_data.fee.saturating_add(&additional_fee);
message_data.payload.len()
// compute actual dispatch weight that depends on the stored message size
let actual_weight = sp_std::cmp::min(
T::WeightInfo::maximal_increase_message_fee(),
T::WeightInfo::increase_message_fee(message_size as _),
);
Ok(PostDispatchInfo {
actual_weight: Some(actual_weight),
pays_fee: Pays::Yes,
})
/// Receive messages proof from bridged chain.
///
/// The weight of the call assumes that the transaction always brings outbound lane
/// state update. Because of that, the submitter (relayer) has no benefit of not including
/// this data in the transaction, so reward confirmations lags should be minimal.
#[pallet::weight(T::WeightInfo::receive_messages_proof_weight(proof, *messages_count, *dispatch_weight))]
pub fn receive_messages_proof(
origin: OriginFor<T>,
relayer_id_at_bridged_chain: T::InboundRelayer,
proof: MessagesProofOf<T, I>,
dispatch_weight: Weight,
) -> DispatchResultWithPostInfo {
ensure_not_halted::<T, I>()?;
let relayer_id_at_this_chain = ensure_signed(origin)?;
// reject transactions that are declaring too many messages
ensure!(
MessageNonce::from(messages_count) <= T::MaxUnconfirmedMessagesAtInboundLane::get(),
Error::<T, I>::TooManyMessagesInTheProof
);
// why do we need to know the weight of this (`receive_messages_proof`) call? Because
// we may want to return some funds for not-dispatching (or partially dispatching) some
// messages to the call origin (relayer). And this is done by returning actual weight
// from the call. But we only know dispatch weight of every messages. So to refund relayer
// because we have not dispatched Message, we need to:
//
// ActualWeight = DeclaredWeight - Message.DispatchWeight
//
// The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible
// to get pre-computed value (and it has been already computed by the executive).
let declared_weight = T::WeightInfo::receive_messages_proof_weight(&proof, messages_count, dispatch_weight);
let mut actual_weight = declared_weight;
// verify messages proof && convert proof into messages
let messages = verify_and_decode_messages_proof::<
T::SourceHeaderChain,
T::InboundMessageFee,
T::InboundPayload,
>(proof, messages_count)
.map_err(|err| {
log::trace!(
target: "runtime::bridge-messages",
"Rejecting invalid messages proof: {:?}",
err,
);
Error::<T, I>::InvalidMessagesProof
})?;
// dispatch messages and (optionally) update lane(s) state(s)
let mut total_messages = 0;
let mut valid_messages = 0;
let mut dispatch_weight_left = dispatch_weight;
for (lane_id, lane_data) in messages {
let mut lane = inbound_lane::<T, I>(lane_id);
if let Some(lane_state) = lane_data.lane_state {
let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state);
if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce {
target: "runtime::bridge-messages",
"Received lane {:?} state update: latest_confirmed_nonce={}",
lane_id,
updated_latest_confirmed_nonce,
);
}
}
for message in lane_data.messages {
debug_assert_eq!(message.key.lane_id, lane_id);
// ensure that relayer has declared enough weight for dispatching next message on
// this lane. We can't dispatch lane messages out-of-order, so if declared weight
// is not enough, let's move to next lane
let dispatch_weight = T::MessageDispatch::dispatch_weight(&message);
if dispatch_weight > dispatch_weight_left {
log::trace!(
target: "runtime::bridge-messages",
"Cannot dispatch any more messages on lane {:?}. Weight: declared={}, left={}",
lane_id,
dispatch_weight,
dispatch_weight_left,
);
break;
}
total_messages += 1;
let receival_result = lane.receive_message::<T::MessageDispatch, T::AccountId>(
&relayer_id_at_bridged_chain,
&relayer_id_at_this_chain,
message.key.nonce,
message.data,
);
// note that we're returning unspent weight to relayer even if message has been
// rejected by the lane. This allows relayers to submit spam transactions with
// e.g. the same set of already delivered messages over and over again, without
// losing funds for messages dispatch. But keep in mind that relayer pays base
// delivery transaction cost anyway. And base cost covers everything except
// dispatch, so we have a balance here.
let (unspent_weight, refund_pay_dispatch_fee) = match receival_result {
ReceivalResult::Dispatched(dispatch_result) => {
valid_messages += 1;
(
dispatch_result.unspent_weight,
!dispatch_result.dispatch_fee_paid_during_dispatch,
)
}
ReceivalResult::InvalidNonce
| ReceivalResult::TooManyUnrewardedRelayers
| ReceivalResult::TooManyUnconfirmedMessages => (dispatch_weight, true),
let unspent_weight = sp_std::cmp::min(unspent_weight, dispatch_weight);
dispatch_weight_left -= dispatch_weight - unspent_weight;
actual_weight = actual_weight.saturating_sub(unspent_weight).saturating_sub(
// delivery call weight formula assumes that the fee is paid at
// this (target) chain. If the message is prepaid at the source
// chain, let's refund relayer with this extra cost.
if refund_pay_dispatch_fee {
T::WeightInfo::pay_inbound_dispatch_fee_overhead()
} else {
0
},
);
target: "runtime::bridge-messages",
"Received messages: total={}, valid={}. Weight used: {}/{}",
total_messages,
valid_messages,
actual_weight,
declared_weight,
Ok(PostDispatchInfo {
actual_weight: Some(actual_weight),
pays_fee: Pays::Yes,
})
/// Receive messages delivery proof from bridged chain.
#[pallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
proof,
relayers_state,
T::DbWeight::get(),
Svyatoslav Nikolsky
committed
pub fn receive_messages_delivery_proof(
origin: OriginFor<T>,
Svyatoslav Nikolsky
committed
proof: MessagesDeliveryProofOf<T, I>,
relayers_state: UnrewardedRelayersState,
) -> DispatchResultWithPostInfo {
ensure_not_halted::<T, I>()?;
// why do we need to know the weight of this (`receive_messages_delivery_proof`) call? Because
// we may want to return some funds for messages that are not processed by the delivery callback,
// or if their actual processing weight is less than accounted by weight formula.
// So to refund relayer, we need to:
//
// ActualWeight = DeclaredWeight - UnspentCallbackWeight
//
// The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible
// to get pre-computed value (and it has been already computed by the executive).
let single_message_callback_overhead = T::WeightInfo::single_message_callback_overhead(T::DbWeight::get());
let declared_weight =
T::WeightInfo::receive_messages_delivery_proof_weight(&proof, &relayers_state, T::DbWeight::get());
let mut actual_weight = declared_weight;
let confirmation_relayer = ensure_signed(origin)?;
let (lane_id, lane_data) = T::TargetHeaderChain::verify_messages_delivery_proof(proof).map_err(|err| {
target: "runtime::bridge-messages",
"Rejecting invalid messages delivery proof: {:?}",
err,
);
Error::<T, I>::InvalidMessagesDeliveryProof
})?;
Svyatoslav Nikolsky
committed
// verify that the relayer has declared correct `lane_data::relayers` state
// (we only care about total number of entries and messages, because this affects call weight)
ensure!(
total_unrewarded_messages(&lane_data.relayers).unwrap_or(MessageNonce::MAX)
== relayers_state.total_messages
Svyatoslav Nikolsky
committed
&& lane_data.relayers.len() as MessageNonce == relayers_state.unrewarded_relayer_entries,
Error::<T, I>::InvalidUnrewardedRelayersState
);
// mark messages as delivered
let mut lane = outbound_lane::<T, I>(lane_id);
Svyatoslav Nikolsky
committed
let mut relayers_rewards: RelayersRewards<_, T::OutboundMessageFee> = RelayersRewards::new();
let last_delivered_nonce = lane_data.last_delivered_nonce();
let confirmed_messages =
match lane.confirm_delivery(relayers_state.total_messages, last_delivered_nonce, &lane_data.relayers) {
ReceivalConfirmationResult::ConfirmedMessages(confirmed_messages) => Some(confirmed_messages),
ReceivalConfirmationResult::NoNewConfirmations => None,
ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(to_confirm_messages_count) => {
log::trace!(
target: "runtime::bridge-messages",
"Messages delivery proof contains too many messages to confirm: {} vs declared {}",
to_confirm_messages_count,
relayers_state.total_messages,
);
Svyatoslav Nikolsky
committed
fail!(Error::<T, I>::TryingToConfirmMoreMessagesThanExpected);
}
error => {
log::trace!(
target: "runtime::bridge-messages",
"Messages delivery proof contains invalid unrewarded relayers vec: {:?}",
error,
);
Svyatoslav Nikolsky
committed
fail!(Error::<T, I>::InvalidUnrewardedRelayers);
}
};
Svyatoslav Nikolsky
committed
Svyatoslav Nikolsky
committed
if let Some(confirmed_messages) = confirmed_messages {
// handle messages delivery confirmation
let preliminary_callback_overhead = relayers_state
.total_messages
.saturating_mul(single_message_callback_overhead);
let actual_callback_weight =
T::OnDeliveryConfirmed::on_messages_delivered(&lane_id, &confirmed_messages);
match preliminary_callback_overhead.checked_sub(actual_callback_weight) {
Some(difference) if difference == 0 => (),
Some(difference) => {
log::trace!(
target: "runtime::bridge-messages",
"T::OnDeliveryConfirmed callback has spent less weight than expected. Refunding: \
{} - {} = {}",
preliminary_callback_overhead,
actual_callback_weight,
difference,
);
actual_weight = actual_weight.saturating_sub(difference);
None => {
debug_assert!(false, "T::OnDeliveryConfirmed callback consumed too much weight.");
log::error!(
target: "runtime::bridge-messages",
"T::OnDeliveryConfirmed callback has spent more weight that it is allowed to: \
{} vs {}",
preliminary_callback_overhead,
actual_callback_weight,
);
}
}
Svyatoslav Nikolsky
committed
// emit 'delivered' event
let received_range = confirmed_messages.begin..=confirmed_messages.end;
Self::deposit_event(Event::MessagesDelivered(lane_id, confirmed_messages));
Svyatoslav Nikolsky
committed
// remember to reward relayers that have delivered messages
// this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain
Svyatoslav Nikolsky
committed
for entry in lane_data.relayers {
let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start());
let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end());
// loop won't proceed if current entry is ahead of received range (begin > end).
// this loop is bound by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain
Svyatoslav Nikolsky
committed
let mut relayer_reward = relayers_rewards.entry(entry.relayer).or_default();
for nonce in nonce_begin..nonce_end + 1 {
let message_data = OutboundMessages::<T, I>::get(MessageKey { lane_id, nonce })
.expect("message was just confirmed; we never prune unconfirmed messages; qed");
Svyatoslav Nikolsky
committed
relayer_reward.reward = relayer_reward.reward.saturating_add(&message_data.fee);
relayer_reward.messages += 1;
Svyatoslav Nikolsky
committed
// if some new messages have been confirmed, reward relayers
if !relayers_rewards.is_empty() {
let relayer_fund_account = relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
Svyatoslav Nikolsky
committed
<T as Config<I>>::MessageDeliveryAndDispatchPayment::pay_relayers_rewards(
&confirmation_relayer,
relayers_rewards,
&relayer_fund_account,
);
}
target: "runtime::bridge-messages",
"Received messages delivery proof up to (and including) {} at lane {:?}",
last_delivered_nonce,
lane_id,
);
Ok(PostDispatchInfo {
actual_weight: Some(actual_weight),
pays_fee: Pays::Yes,
})
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
#[pallet::metadata(T::Parameter = "Parameter")]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// Pallet parameter has been updated.
ParameterUpdated(T::Parameter),
/// Message has been accepted and is waiting to be delivered.
MessageAccepted(LaneId, MessageNonce),
/// Messages in the inclusive range have been delivered to the bridged chain.
MessagesDelivered(LaneId, DeliveredMessages),
Svyatoslav Nikolsky
committed
}
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
#[pallet::error]
pub enum Error<T, I = ()> {
/// All pallet operations are halted.
Halted,
/// 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,
/// The transaction brings too many messages.
TooManyMessagesInTheProof,
/// Invalid messages has been submitted.
InvalidMessagesProof,
/// Invalid messages delivery proof has been submitted.
InvalidMessagesDeliveryProof,
/// The bridged chain has invalid `UnrewardedRelayers` in its storage (fatal for the lane).
InvalidUnrewardedRelayers,
/// The relayer has declared invalid unrewarded relayers state in the `receive_messages_delivery_proof` call.
InvalidUnrewardedRelayersState,
/// The message someone is trying to work with (i.e. increase fee) is already-delivered.
MessageIsAlreadyDelivered,
/// The message someone is trying to work with (i.e. increase fee) is not yet sent.
MessageIsNotYetSent,
/// The number of actually confirmed messages is going to be larger than the number of messages in the proof.
/// This may mean that this or bridged chain storage is corrupted.
TryingToConfirmMoreMessagesThanExpected,
Svyatoslav Nikolsky
committed
}
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
/// Optional pallet owner.
///
/// Pallet owner has a right to halt all pallet operations and then resume it. If it is
/// `None`, then there are no direct ways to halt/resume pallet operations, but other
/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
/// flag directly or call the `halt_operations`).
#[pallet::storage]
#[pallet::getter(fn module_owner)]
pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
/// The current operating mode of the pallet.
///
/// Depending on the mode either all, some, or no transactions will be allowed.
#[pallet::storage]
#[pallet::getter(fn operating_mode)]
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> = StorageValue<_, OperatingMode, ValueQuery>;
/// Map of lane id => inbound lane data.
#[pallet::storage]
pub type InboundLanes<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, LaneId, InboundLaneData<T::InboundRelayer>, ValueQuery>;
/// Map of lane id => outbound lane data.
#[pallet::storage]
pub type OutboundLanes<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, LaneId, OutboundLaneData, ValueQuery>;
/// All queued outbound messages.
#[pallet::storage]
pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, MessageKey, MessageData<T::OutboundMessageFee>>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
/// Initial pallet operating mode.
pub operating_mode: OperatingMode,
/// Initial pallet owner.
pub owner: Option<T::AccountId>,
/// Dummy marker.
pub phantom: sp_std::marker::PhantomData<I>,
}
#[cfg(feature = "std")]
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self {
Self {
operating_mode: Default::default(),
owner: Default::default(),
phantom: Default::default(),
}
}
Svyatoslav Nikolsky
committed
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> {
fn build(&self) {
PalletOperatingMode::<T, I>::put(&self.operating_mode);
if let Some(ref owner) = self.owner {
PalletOwner::<T, I>::put(owner);
}
}
Svyatoslav Nikolsky
committed
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get stored data of the outbound message with given nonce.
pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option<MessageData<T::OutboundMessageFee>> {
OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce })
}
/// Get nonce of the latest generated message at given outbound lane.
pub fn outbound_latest_generated_nonce(lane: LaneId) -> MessageNonce {
OutboundLanes::<T, I>::get(&lane).latest_generated_nonce
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
/// Get nonce of the latest confirmed message at given outbound lane.
pub fn outbound_latest_received_nonce(lane: LaneId) -> MessageNonce {
OutboundLanes::<T, I>::get(&lane).latest_received_nonce
}
/// Get nonce of the latest received message at given inbound lane.
pub fn inbound_latest_received_nonce(lane: LaneId) -> MessageNonce {
InboundLanes::<T, I>::get(&lane).last_delivered_nonce()
}
/// Get nonce of the latest confirmed message at given inbound lane.
pub fn inbound_latest_confirmed_nonce(lane: LaneId) -> MessageNonce {
InboundLanes::<T, I>::get(&lane).last_confirmed_nonce
}
/// Get state of unrewarded relayers set.
pub fn inbound_unrewarded_relayers_state(lane: bp_messages::LaneId) -> bp_messages::UnrewardedRelayersState {
let relayers = InboundLanes::<T, I>::get(&lane).relayers;
bp_messages::UnrewardedRelayersState {
unrewarded_relayer_entries: relayers.len() as _,
messages_in_oldest_entry: relayers
.front()
.map(|entry| 1 + entry.messages.end - entry.messages.begin)
.unwrap_or(0),
total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX),
}
}
Svyatoslav Nikolsky
committed
}
/// Getting storage keys for messages and lanes states. These keys are normally used when building
/// messages and lanes states proofs.
pub mod storage_keys {
use super::*;
use frame_support::StorageHasher;
use sp_core::storage::StorageKey;
/// Storage key of the outbound message in the runtime storage.
pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey {
storage_map_final_key(
pallet_prefix,
"OutboundMessages",
&MessageKey { lane_id: *lane, nonce }.encode(),
)
}
/// Storage key of the outbound message lane state in the runtime storage.
pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey {
storage_map_final_key(pallet_prefix, "OutboundLanes", lane)
}
/// Storage key of the inbound message lane state in the runtime storage.
pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey {
storage_map_final_key(pallet_prefix, "InboundLanes", lane)
}
/// This is a copypaste of the `frame_support::storage::generator::StorageMap::storage_map_final_key`.
fn storage_map_final_key(pallet_prefix: &str, map_name: &str, key: &[u8]) -> StorageKey {
let pallet_prefix_hashed = frame_support::Twox128::hash(pallet_prefix.as_bytes());
let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes());
let key_hashed = frame_support::Blake2_128Concat::hash(key);
let mut final_key =
Vec::with_capacity(pallet_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len());
final_key.extend_from_slice(&pallet_prefix_hashed[..]);
final_key.extend_from_slice(&storage_prefix_hashed[..]);
final_key.extend_from_slice(key_hashed.as_ref());
StorageKey(final_key)
}
}
/// AccountId of the shared relayer fund account.
///
/// This account is passed to `MessageDeliveryAndDispatchPayment` trait, and depending
/// on the implementation it can be used to store relayers rewards.
/// See [`InstantCurrencyPayments`] for a concrete implementation.
pub fn relayer_fund_account_id<AccountId, AccountIdConverter: Convert<H256, AccountId>>() -> AccountId {
let encoded_id = bp_runtime::derive_relayer_fund_account_id(bp_runtime::NO_INSTANCE_ID);
AccountIdConverter::convert(encoded_id)
}
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
impl<T, I> bp_messages::source_chain::MessagesBridge<T::AccountId, T::OutboundMessageFee, T::OutboundPayload>
for Pallet<T, I>
where
T: Config<I>,
I: 'static,
{
type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>;
fn send_message(
sender: bp_messages::source_chain::Sender<T::AccountId>,
lane: LaneId,
message: T::OutboundPayload,
delivery_and_dispatch_fee: T::OutboundMessageFee,
) -> Result<MessageNonce, Self::Error> {
crate::send_message::<T, I>(sender, lane, message, delivery_and_dispatch_fee)
.map(|sent_message| sent_message.nonce)
}
}
/// Message that has been sent.
struct SentMessage {
/// Nonce of the message.
pub nonce: MessageNonce,
/// Post-dispatch call info.
pub post_dispatch_info: PostDispatchInfo,
}
/// Function that actually sends message.
fn send_message<T: Config<I>, I: 'static>(
submitter: bp_messages::source_chain::Sender<T::AccountId>,
lane_id: LaneId,
payload: T::OutboundPayload,
delivery_and_dispatch_fee: T::OutboundMessageFee,
) -> sp_std::result::Result<SentMessage, sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>> {
ensure_normal_operating_mode::<T, I>()?;
// initially, actual (post-dispatch) weight is equal to pre-dispatch weight
let mut actual_weight = T::WeightInfo::send_message_weight(&payload, T::DbWeight::get());
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
// let's first check if message can be delivered to target chain
T::TargetHeaderChain::verify_message(&payload).map_err(|err| {
log::trace!(
target: "runtime::bridge-messages",
"Message to lane {:?} is rejected by target chain: {:?}",
lane_id,
err,
);
Error::<T, I>::MessageRejectedByChainVerifier
})?;
// now let's enforce any additional lane rules
let mut lane = outbound_lane::<T, I>(lane_id);
T::LaneMessageVerifier::verify_message(&submitter, &delivery_and_dispatch_fee, &lane_id, &lane.data(), &payload)
.map_err(|err| {
log::trace!(
target: "runtime::bridge-messages",
"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,
&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
)
.map_err(|err| {
log::trace!(
target: "runtime::bridge-messages",
"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 encoded_payload = payload.encode();
let encoded_payload_len = encoded_payload.len();
let nonce = lane.send_message(MessageData {
payload: encoded_payload,
fee: delivery_and_dispatch_fee,
});
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
// Guaranteed to be called outside only when the message is accepted.
// We assume that the maximum weight call back used is `single_message_callback_overhead`, so do not perform
// complex db operation in callback. If you want to, put these magic logic in outside pallet and control
// the weight there.
let single_message_callback_overhead = T::WeightInfo::single_message_callback_overhead(T::DbWeight::get());
let actual_callback_weight = T::OnMessageAccepted::on_messages_accepted(&lane_id, &nonce);
match single_message_callback_overhead.checked_sub(actual_callback_weight) {
Some(difference) if difference == 0 => (),
Some(difference) => {
log::trace!(
target: "runtime::bridge-messages",
"T::OnMessageAccepted callback has spent less weight than expected. Refunding: \
{} - {} = {}",
single_message_callback_overhead,
actual_callback_weight,
difference,
);
actual_weight = actual_weight.saturating_sub(difference);
}
None => {
debug_assert!(false, "T::OnMessageAccepted callback consumed too much weight.");
log::error!(
target: "runtime::bridge-messages",
"T::OnMessageAccepted callback has spent more weight that it is allowed to: \
{} vs {}",
single_message_callback_overhead,
actual_callback_weight,
);
}
}
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
// message sender pays for pruning at most `MaxMessagesToPruneAtOnce` messages
// the cost of pruning every message is roughly single db write
// => lets refund sender if less than `MaxMessagesToPruneAtOnce` messages pruned
let max_messages_to_prune = T::MaxMessagesToPruneAtOnce::get();
let pruned_messages = lane.prune_messages(max_messages_to_prune);
if let Some(extra_messages) = max_messages_to_prune.checked_sub(pruned_messages) {
actual_weight = actual_weight.saturating_sub(T::DbWeight::get().writes(extra_messages));
}
log::trace!(
target: "runtime::bridge-messages",
"Accepted message {} to lane {:?}. Message size: {:?}",
nonce,
lane_id,
encoded_payload_len,
);
Pallet::<T, I>::deposit_event(Event::MessageAccepted(lane_id, nonce));
Ok(SentMessage {
nonce,
post_dispatch_info: PostDispatchInfo {
actual_weight: Some(actual_weight),
pays_fee: Pays::Yes,
},
})
}
/// Ensure that the origin is either root, or `PalletOwner`.
fn ensure_owner_or_root<T: Config<I>, I: 'static>(origin: T::Origin) -> Result<(), BadOrigin> {
match origin.into() {
Ok(RawOrigin::Root) => Ok(()),
Ok(RawOrigin::Signed(ref signer)) if Some(signer) == Pallet::<T, I>::module_owner().as_ref() => Ok(()),
_ => Err(BadOrigin),
}
}
/// Ensure that the pallet is in normal operational mode.
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
if PalletOperatingMode::<T, I>::get() != OperatingMode::Normal {
Err(Error::<T, I>::Halted)
} else {
Ok(())
}
}
/// Ensure that the pallet is not halted.
fn ensure_not_halted<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
if PalletOperatingMode::<T, I>::get() == OperatingMode::Halted {
Err(Error::<T, I>::Halted)