fix: Complete snowbridge pezpallet rebrand and critical bug fixes

- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs)
- pallet/ directories → pezpallet/ (4 locations)
- Fixed pezpallet.rs self-include recursion bug
- Fixed sc-chain-spec hardcoded crate name in derive macro
- Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API)
- Added BizinikiwiConfig type alias for zombienet tests
- Deleted obsolete session state files

Verified: pezsnowbridge-pezpallet-*, pezpallet-staking,
pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
2025-12-16 09:57:23 +03:00
parent eea003e14d
commit 3139ffa25e
3022 changed files with 42157 additions and 23579 deletions
@@ -0,0 +1,552 @@
// Copyright (C) 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/>.
//! Messages pezpallet benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use crate::{
active_outbound_lane, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, BridgedChainOf, Call,
InboundLanes, OutboundLanes,
};
use bp_messages::{
source_chain::FromBridgedChainMessagesDeliveryProof,
target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages,
InboundLaneData, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
};
use pezbp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams};
use codec::Decode;
use pezframe_benchmarking::{account, v2::*};
use pezframe_support::weights::Weight;
use pezframe_system::RawOrigin;
use pezsp_runtime::{traits::TrailingZeroInput, BoundedVec};
use pezsp_std::{ops::RangeInclusive, prelude::*};
const SEED: u32 = 0;
/// Pezpallet we're benchmarking here.
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
/// Benchmark-specific message proof parameters.
#[derive(Debug)]
pub struct MessageProofParams<LaneId> {
/// Id of the lane.
pub lane: LaneId,
/// Range of messages to include in the proof.
pub message_nonces: RangeInclusive<MessageNonce>,
/// If `Some`, the proof needs to include this outbound lane data.
pub outbound_lane_data: Option<OutboundLaneData>,
/// If `true`, the caller expects that the proof will contain correct messages that will
/// be successfully dispatched. This is only called from the "optional"
/// `receive_single_message_proof_with_dispatch` benchmark. If you don't need it, just
/// return `true` from the `is_message_successfully_dispatched`.
pub is_successful_dispatch_expected: bool,
/// Proof size requirements.
pub proof_params: UnverifiedStorageProofParams,
}
/// Benchmark-specific message delivery proof parameters.
#[derive(Debug)]
pub struct MessageDeliveryProofParams<ThisChainAccountId, LaneId> {
/// Id of the lane.
pub lane: LaneId,
/// The proof needs to include this inbound lane data.
pub inbound_lane_data: InboundLaneData<ThisChainAccountId>,
/// Proof size requirements.
pub proof_params: UnverifiedStorageProofParams,
}
/// Trait that must be implemented by runtime.
pub trait Config<I: 'static>: crate::Config<I> {
/// Lane id to use in benchmarks.
fn bench_lane_id() -> Self::LaneId {
Self::LaneId::default()
}
/// Return id of relayer account at the bridged chain.
///
/// By default, zero account is returned.
fn bridged_relayer_id() -> AccountIdOf<BridgedChainOf<Self, I>> {
Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap()
}
/// Create given account and give it enough balance for test purposes. Used to create
/// relayer account at the target chain. Is strictly necessary when your rewards scheme
/// assumes that the relayer account must exist.
///
/// Does nothing by default.
fn endow_account(_account: &Self::AccountId) {}
/// Prepare messages proof to receive by the module.
fn prepare_message_proof(
params: MessageProofParams<Self::LaneId>,
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>, Weight);
/// Prepare messages delivery proof to receive by the module.
fn prepare_message_delivery_proof(
params: MessageDeliveryProofParams<Self::AccountId, Self::LaneId>,
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>;
/// Returns true if message has been successfully dispatched or not.
fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool {
true
}
/// Returns true if given relayer has been rewarded for some of its actions.
fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool;
}
fn send_regular_message<T: Config<I>, I: 'static>() {
OutboundLanes::<T, I>::insert(
T::bench_lane_id(),
OutboundLaneData {
state: LaneState::Opened,
latest_generated_nonce: 1,
..Default::default()
},
);
let mut outbound_lane = active_outbound_lane::<T, I>(T::bench_lane_id()).unwrap();
outbound_lane.send_message(BoundedVec::try_from(vec![]).expect("We craft valid messages"));
}
fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
InboundLanes::<T, I>::insert(
T::bench_lane_id(),
InboundLaneData {
state: LaneState::Opened,
relayers: vec![UnrewardedRelayer {
relayer: T::bridged_relayer_id(),
messages: DeliveredMessages::new(nonce),
}]
.into(),
last_confirmed_nonce: 0,
},
);
}
struct ReceiveMessagesProofSetup<T: Config<I>, I: 'static> {
relayer_id_on_src: AccountIdOf<BridgedChainOf<T, I>>,
relayer_id_on_tgt: T::AccountId,
msgs_count: u32,
_phantom_data: pezsp_std::marker::PhantomData<I>,
}
impl<T: Config<I>, I: 'static> ReceiveMessagesProofSetup<T, I> {
const LATEST_RECEIVED_NONCE: MessageNonce = 20;
fn new(msgs_count: u32) -> Self {
let setup = Self {
relayer_id_on_src: T::bridged_relayer_id(),
relayer_id_on_tgt: account("relayer", 0, SEED),
msgs_count,
_phantom_data: Default::default(),
};
T::endow_account(&setup.relayer_id_on_tgt);
// mark messages 1..=latest_recvd_nonce as delivered
receive_messages::<T, I>(Self::LATEST_RECEIVED_NONCE);
setup
}
fn relayer_id_on_src(&self) -> AccountIdOf<BridgedChainOf<T, I>> {
self.relayer_id_on_src.clone()
}
fn relayer_id_on_tgt(&self) -> T::AccountId {
self.relayer_id_on_tgt.clone()
}
fn last_nonce(&self) -> MessageNonce {
Self::LATEST_RECEIVED_NONCE + self.msgs_count as u64
}
fn nonces(&self) -> RangeInclusive<MessageNonce> {
(Self::LATEST_RECEIVED_NONCE + 1)..=self.last_nonce()
}
fn check_last_nonce(&self) {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).map(|d| d.last_delivered_nonce()),
Some(self.last_nonce()),
);
}
}
#[instance_benchmarks]
mod benchmarks {
use super::*;
//
// Benchmarks that are used directly by the runtime calls weight formulae.
//
fn max_msgs<T: Config<I>, I: 'static>() -> u32 {
T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX as u32 -
ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE as u32
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
// conditions:
// * proof does not include outbound lane state proof;
// * inbound lane already has state, so it needs to be read and decoded;
// * message is dispatched (reminder: dispatch weight should be minimal);
// * message requires all heavy checks done by dispatcher.
#[benchmark]
fn receive_single_message_proof() {
// setup code
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: setup.nonces(),
outbound_lane_data: None,
is_successful_dispatch_expected: false,
proof_params: UnverifiedStorageProofParams::from_db_size(
EXPECTED_DEFAULT_MESSAGE_LENGTH,
),
});
#[extrinsic_call]
receive_messages_proof(
RawOrigin::Signed(setup.relayer_id_on_tgt()),
setup.relayer_id_on_src(),
Box::new(proof),
setup.msgs_count,
dispatch_weight,
);
// verification code
setup.check_last_nonce();
}
// Benchmark `receive_messages_proof` extrinsic with `n` minimal-weight messages and following
// conditions:
// * proof does not include outbound lane state proof;
// * inbound lane already has state, so it needs to be read and decoded;
// * message is dispatched (reminder: dispatch weight should be minimal);
// * message requires all heavy checks done by dispatcher.
#[benchmark]
fn receive_n_messages_proof(n: Linear<1, { max_msgs::<T, I>() }>) {
// setup code
let setup = ReceiveMessagesProofSetup::<T, I>::new(n);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: setup.nonces(),
outbound_lane_data: None,
is_successful_dispatch_expected: false,
proof_params: UnverifiedStorageProofParams::from_db_size(
EXPECTED_DEFAULT_MESSAGE_LENGTH,
),
});
#[extrinsic_call]
receive_messages_proof(
RawOrigin::Signed(setup.relayer_id_on_tgt()),
setup.relayer_id_on_src(),
Box::new(proof),
setup.msgs_count,
dispatch_weight,
);
// verification code
setup.check_last_nonce();
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
// conditions:
// * proof includes outbound lane state proof;
// * inbound lane already has state, so it needs to be read and decoded;
// * message is successfully dispatched (reminder: dispatch weight should be minimal);
// * message requires all heavy checks done by dispatcher.
//
// The weight of outbound lane state delivery would be
// `weight(receive_single_message_proof_with_outbound_lane_state) -
// weight(receive_single_message_proof)`. This won't be super-accurate if message has non-zero
// dispatch weight, but estimation should be close enough to real weight.
#[benchmark]
fn receive_single_message_proof_with_outbound_lane_state() {
// setup code
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: setup.nonces(),
outbound_lane_data: Some(OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: setup.last_nonce(),
latest_received_nonce: ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE,
latest_generated_nonce: setup.last_nonce(),
}),
is_successful_dispatch_expected: false,
proof_params: UnverifiedStorageProofParams::from_db_size(
EXPECTED_DEFAULT_MESSAGE_LENGTH,
),
});
#[extrinsic_call]
receive_messages_proof(
RawOrigin::Signed(setup.relayer_id_on_tgt()),
setup.relayer_id_on_src(),
Box::new(proof),
setup.msgs_count,
dispatch_weight,
);
// verification code
setup.check_last_nonce();
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
// conditions:
// * the proof has large leaf with total size ranging between 1KB and 16KB;
// * proof does not include outbound lane state proof;
// * inbound lane already has state, so it needs to be read and decoded;
// * message is dispatched (reminder: dispatch weight should be minimal);
// * message requires all heavy checks done by dispatcher.
#[benchmark]
fn receive_single_n_bytes_message_proof(
/// Proof size in KB
n: Linear<1, { 16 * 1024 }>,
) {
// setup code
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: setup.nonces(),
outbound_lane_data: None,
is_successful_dispatch_expected: false,
proof_params: UnverifiedStorageProofParams::from_db_size(n),
});
#[extrinsic_call]
receive_messages_proof(
RawOrigin::Signed(setup.relayer_id_on_tgt()),
setup.relayer_id_on_src(),
Box::new(proof),
setup.msgs_count,
dispatch_weight,
);
// verification code
setup.check_last_nonce();
}
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
// * single relayer is rewarded for relaying single message;
// * relayer account does not exist (in practice it needs to exist in production environment).
//
// This is base benchmark for all other confirmations delivery benchmarks.
#[benchmark]
fn receive_delivery_proof_for_single_message() {
let relayer_id: T::AccountId = account("relayer", 0, SEED);
// send message that we're going to confirm
send_regular_message::<T, I>();
let relayers_state = UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1,
last_delivered_nonce: 1,
};
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
lane: T::bench_lane_id(),
inbound_lane_data: InboundLaneData {
state: LaneState::Opened,
relayers: vec![UnrewardedRelayer {
relayer: relayer_id.clone(),
messages: DeliveredMessages::new(1),
}]
.into_iter()
.collect(),
last_confirmed_nonce: 0,
},
proof_params: UnverifiedStorageProofParams::default(),
});
#[extrinsic_call]
receive_messages_delivery_proof(
RawOrigin::Signed(relayer_id.clone()),
proof,
relayers_state,
);
assert_eq!(
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
Some(1)
);
assert!(T::is_relayer_rewarded(&relayer_id));
}
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
// * single relayer is rewarded for relaying two messages;
// * relayer account does not exist (in practice it needs to exist in production environment).
//
// Additional weight for paying single-message reward to the same relayer could be computed
// as `weight(receive_delivery_proof_for_two_messages_by_single_relayer)
// - weight(receive_delivery_proof_for_single_message)`.
#[benchmark]
fn receive_delivery_proof_for_two_messages_by_single_relayer() {
let relayer_id: T::AccountId = account("relayer", 0, SEED);
// send message that we're going to confirm
send_regular_message::<T, I>();
send_regular_message::<T, I>();
let relayers_state = UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 2,
total_messages: 2,
last_delivered_nonce: 2,
};
let mut delivered_messages = DeliveredMessages::new(1);
delivered_messages.note_dispatched_message();
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
lane: T::bench_lane_id(),
inbound_lane_data: InboundLaneData {
state: LaneState::Opened,
relayers: vec![UnrewardedRelayer {
relayer: relayer_id.clone(),
messages: delivered_messages,
}]
.into_iter()
.collect(),
last_confirmed_nonce: 0,
},
proof_params: UnverifiedStorageProofParams::default(),
});
#[extrinsic_call]
receive_messages_delivery_proof(
RawOrigin::Signed(relayer_id.clone()),
proof,
relayers_state,
);
assert_eq!(
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
Some(2)
);
assert!(T::is_relayer_rewarded(&relayer_id));
}
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
// * two relayers are rewarded for relaying single message each;
// * relayer account does not exist (in practice it needs to exist in production environment).
//
// Additional weight for paying reward to the next relayer could be computed
// as `weight(receive_delivery_proof_for_two_messages_by_two_relayers)
// - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`.
#[benchmark]
fn receive_delivery_proof_for_two_messages_by_two_relayers() {
let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
// send message that we're going to confirm
send_regular_message::<T, I>();
send_regular_message::<T, I>();
let relayers_state = UnrewardedRelayersState {
unrewarded_relayer_entries: 2,
messages_in_oldest_entry: 1,
total_messages: 2,
last_delivered_nonce: 2,
};
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
lane: T::bench_lane_id(),
inbound_lane_data: InboundLaneData {
state: LaneState::Opened,
relayers: vec![
UnrewardedRelayer {
relayer: relayer1_id.clone(),
messages: DeliveredMessages::new(1),
},
UnrewardedRelayer {
relayer: relayer2_id.clone(),
messages: DeliveredMessages::new(2),
},
]
.into_iter()
.collect(),
last_confirmed_nonce: 0,
},
proof_params: UnverifiedStorageProofParams::default(),
});
#[extrinsic_call]
receive_messages_delivery_proof(
RawOrigin::Signed(relayer1_id.clone()),
proof,
relayers_state,
);
assert_eq!(
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
Some(2)
);
assert!(T::is_relayer_rewarded(&relayer1_id));
assert!(T::is_relayer_rewarded(&relayer2_id));
}
//
// Benchmarks that the runtime developers may use for proper pezpallet configuration.
//
// This benchmark is optional and may be used when runtime developer need a way to compute
// message dispatch weight. In this case, he needs to provide messages that can go the whole
// dispatch
//
// Benchmark `receive_messages_proof` extrinsic with single message and following conditions:
//
// * proof does not include outbound lane state proof;
// * inbound lane already has state, so it needs to be read and decoded;
// * message is **SUCCESSFULLY** dispatched;
// * message requires all heavy checks done by dispatcher.
#[benchmark]
fn receive_single_n_bytes_message_proof_with_dispatch(
/// Proof size in KB
n: Linear<1, { 16 * 1024 }>,
) {
// setup code
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: setup.nonces(),
outbound_lane_data: None,
is_successful_dispatch_expected: true,
proof_params: UnverifiedStorageProofParams::from_db_size(n),
});
#[extrinsic_call]
receive_messages_proof(
RawOrigin::Signed(setup.relayer_id_on_tgt()),
setup.relayer_id_on_src(),
Box::new(proof),
setup.msgs_count,
dispatch_weight,
);
// verification code
setup.check_last_nonce();
assert!(T::is_message_successfully_dispatched(setup.last_nonce()));
}
impl_benchmark_test_suite!(
Pezpallet,
crate::tests::mock::new_test_ext(),
crate::tests::mock::TestRuntime
);
}
+576
View File
@@ -0,0 +1,576 @@
// Copyright (C) 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/>.
//! Helpers for easier manipulation of call processing with signed extensions.
use crate::{BridgedChainOf, Config, InboundLanes, OutboundLanes, Pezpallet, LOG_TARGET};
use bp_messages::{
target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData,
MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
UnrewardedRelayerOccupation,
};
use pezbp_runtime::{AccountIdOf, OwnedBridgeModule};
use pezframe_support::{dispatch::CallableCallFor, traits::IsSubType};
use pezsp_runtime::transaction_validity::TransactionValidity;
/// Helper struct that provides methods for working with a call supported by `MessagesCallInfo`.
pub struct CallHelper<T: Config<I>, I: 'static> {
_phantom_data: pezsp_std::marker::PhantomData<(T, I)>,
}
impl<T: Config<I>, I: 'static> CallHelper<T, I> {
/// Returns true if:
///
/// - call is `receive_messages_proof` and all messages have been delivered;
///
/// - call is `receive_messages_delivery_proof` and all messages confirmations have been
/// received.
pub fn was_successful(info: &MessagesCallInfo<T::LaneId>) -> bool {
match info {
MessagesCallInfo::ReceiveMessagesProof(info) => {
let inbound_lane_data = match InboundLanes::<T, I>::get(info.base.lane_id) {
Some(inbound_lane_data) => inbound_lane_data,
None => return false,
};
if info.base.bundled_range.is_empty() {
let post_occupation =
unrewarded_relayers_occupation::<T, I>(&inbound_lane_data);
// we don't care about `free_relayer_slots` here - it is checked in
// `is_obsolete` and every relayer has delivered at least one message,
// so if relayer slots are released, then message slots are also
// released
return post_occupation.free_message_slots >
info.unrewarded_relayers.free_message_slots;
}
inbound_lane_data.last_delivered_nonce() == *info.base.bundled_range.end()
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => {
let outbound_lane_data = match OutboundLanes::<T, I>::get(info.0.lane_id) {
Some(outbound_lane_data) => outbound_lane_data,
None => return false,
};
outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
},
}
}
}
/// Trait representing a call that is a sub type of `pezpallet_bridge_messages::Call`.
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
{
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>>;
/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
/// a `ReceiveMessagesDeliveryProof` call.
fn receive_messages_delivery_proof_info(
&self,
) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>>;
/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
/// or a `ReceiveMessagesDeliveryProof` call.
fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>>;
/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>>;
/// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call:
///
/// - does not deliver already delivered messages. We require all messages in the
/// `ReceiveMessagesProof` call to be undelivered;
///
/// - does not submit empty `ReceiveMessagesProof` call with zero messages, unless the lane
/// needs to be unblocked by providing relayer rewards proof;
///
/// - brings no new delivery confirmations in a `ReceiveMessagesDeliveryProof` call. We require
/// at least one new delivery confirmation in the unrewarded relayers set;
///
/// - does not violate some basic (easy verifiable) messages pezpallet rules obsolete (like
/// submitting a call when a pezpallet is halted or delivering messages when a dispatcher is
/// inactive).
///
/// If one of above rules is violated, the transaction is treated as invalid.
fn check_obsolete_call(&self) -> TransactionValidity;
}
impl<
Call: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>,
T: pezframe_system::Config<RuntimeCall = Call> + Config<I>,
I: 'static,
> CallSubType<T, I> for T::RuntimeCall
{
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>> {
if let Some(crate::Call::<T, I>::receive_messages_proof { ref proof, .. }) =
self.is_sub_type()
{
let inbound_lane_data = InboundLanes::<T, I>::get(proof.lane)?;
return Some(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: proof.lane,
// we want all messages in this range to be new for us. Otherwise transaction
// will be considered obsolete.
bundled_range: proof.nonces_start..=proof.nonces_end,
best_stored_nonce: inbound_lane_data.last_delivered_nonce(),
},
unrewarded_relayers: unrewarded_relayers_occupation::<T, I>(&inbound_lane_data),
});
}
None
}
fn receive_messages_delivery_proof_info(
&self,
) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>> {
if let Some(crate::Call::<T, I>::receive_messages_delivery_proof {
ref proof,
ref relayers_state,
..
}) = self.is_sub_type()
{
let outbound_lane_data = OutboundLanes::<T, I>::get(proof.lane)?;
return Some(ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: proof.lane,
// there's a time frame between message delivery, message confirmation and reward
// confirmation. Because of that, we can't assume that our state has been confirmed
// to the bridged chain. So we are accepting any proof that brings new
// confirmations.
bundled_range: outbound_lane_data.latest_received_nonce + 1..=
relayers_state.last_delivered_nonce,
best_stored_nonce: outbound_lane_data.latest_received_nonce,
}));
}
None
}
fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>> {
if let Some(info) = self.receive_messages_proof_info() {
return Some(MessagesCallInfo::ReceiveMessagesProof(info));
}
if let Some(info) = self.receive_messages_delivery_proof_info() {
return Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(info));
}
None
}
fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>> {
self.call_info().filter(|info| {
let actual_lane_id = match info {
MessagesCallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
};
actual_lane_id == lane_id
})
}
fn check_obsolete_call(&self) -> TransactionValidity {
let is_pallet_halted = Pezpallet::<T, I>::ensure_not_halted().is_err();
match self.call_info() {
Some(proof_info) if is_pallet_halted => {
tracing::trace!(
target: LOG_TARGET,
?proof_info,
"Rejecting messages transaction on halted pezpallet"
);
return pezsp_runtime::transaction_validity::InvalidTransaction::Call.into();
},
Some(MessagesCallInfo::ReceiveMessagesProof(proof_info))
if proof_info
.is_obsolete(T::MessageDispatch::is_active(proof_info.base.lane_id)) =>
{
tracing::trace!(
target: LOG_TARGET,
?proof_info,
"Rejecting obsolete messages delivery transaction"
);
return pezsp_runtime::transaction_validity::InvalidTransaction::Stale.into();
},
Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(proof_info))
if proof_info.is_obsolete() =>
{
tracing::trace!(
target: LOG_TARGET,
?proof_info,
"Rejecting obsolete messages confirmation transaction"
);
return pezsp_runtime::transaction_validity::InvalidTransaction::Stale.into();
},
_ => {},
}
Ok(pezsp_runtime::transaction_validity::ValidTransaction::default())
}
}
/// Returns occupation state of unrewarded relayers vector.
fn unrewarded_relayers_occupation<T: Config<I>, I: 'static>(
inbound_lane_data: &InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
) -> UnrewardedRelayerOccupation {
UnrewardedRelayerOccupation {
free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
.saturating_sub(inbound_lane_data.relayers.len() as MessageNonce),
free_message_slots: {
let unconfirmed_messages = inbound_lane_data
.last_delivered_nonce()
.saturating_sub(inbound_lane_data.last_confirmed_nonce);
T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
.saturating_sub(unconfirmed_messages)
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::mock::*;
use bp_messages::{
source_chain::FromBridgedChainMessagesDeliveryProof,
target_chain::FromBridgedChainMessagesProof, DeliveredMessages, InboundLaneData, LaneState,
OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState,
};
use pezsp_std::ops::RangeInclusive;
fn fill_unrewarded_relayers() {
let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX {
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
relayer: Default::default(),
messages: DeliveredMessages { begin: n + 1, end: n + 1 },
});
}
InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
}
fn fill_unrewarded_messages() {
let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
relayer: Default::default(),
messages: DeliveredMessages {
begin: 1,
end: BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
},
});
InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
}
fn deliver_message_10() {
InboundLanes::<TestRuntime>::insert(
test_lane_id(),
bp_messages::InboundLaneData {
state: LaneState::Opened,
relayers: Default::default(),
last_confirmed_nonce: 10,
},
);
}
fn validate_message_delivery(
nonces_start: bp_messages::MessageNonce,
nonces_end: bp_messages::MessageNonce,
) -> bool {
RuntimeCall::Messages(crate::Call::<TestRuntime, ()>::receive_messages_proof {
relayer_id_at_bridged_chain: 42,
messages_count: nonces_end.checked_sub(nonces_start).map(|x| x + 1).unwrap_or(0) as u32,
dispatch_weight: pezframe_support::weights::Weight::zero(),
proof: Box::new(FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: Default::default(),
lane: test_lane_id(),
nonces_start,
nonces_end,
}),
})
.check_obsolete_call()
.is_ok()
}
fn run_test<T>(test: impl Fn() -> T) -> T {
pezsp_io::TestExternalities::new(Default::default()).execute_with(|| {
InboundLanes::<TestRuntime>::insert(test_lane_id(), InboundLaneData::opened());
OutboundLanes::<TestRuntime>::insert(test_lane_id(), OutboundLaneData::opened());
test()
})
}
#[test]
fn extension_rejects_obsolete_messages() {
run_test(|| {
// when current best delivered is message#10 and we're trying to deliver messages 8..=9
// => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(8, 9));
});
}
#[test]
fn extension_rejects_same_message() {
run_test(|| {
// when current best delivered is message#10 and we're trying to import messages 10..=10
// => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(8, 10));
});
}
#[test]
fn extension_rejects_call_with_some_obsolete_messages() {
run_test(|| {
// when current best delivered is message#10 and we're trying to deliver messages
// 10..=15 => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(10, 15));
});
}
#[test]
fn extension_rejects_call_with_future_messages() {
run_test(|| {
// when current best delivered is message#10 and we're trying to deliver messages
// 13..=15 => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(13, 15));
});
}
#[test]
fn extension_reject_call_when_dispatcher_is_inactive() {
run_test(|| {
// when current best delivered is message#10 and we're trying to deliver message 11..=15
// => tx is accepted, but we have inactive dispatcher, so...
deliver_message_10();
TestMessageDispatch::deactivate(test_lane_id());
assert!(!validate_message_delivery(11, 15));
});
}
#[test]
fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
) {
run_test(|| {
deliver_message_10();
assert!(!validate_message_delivery(10, 9));
});
}
#[test]
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_relayer_slots(
) {
run_test(|| {
deliver_message_10();
fill_unrewarded_relayers();
assert!(validate_message_delivery(10, 9));
});
}
#[test]
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_message_slots(
) {
run_test(|| {
fill_unrewarded_messages();
assert!(validate_message_delivery(
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX - 1
));
});
}
#[test]
fn extension_accepts_new_messages() {
run_test(|| {
// when current best delivered is message#10 and we're trying to deliver message 11..=15
// => tx is accepted
deliver_message_10();
assert!(validate_message_delivery(11, 15));
});
}
fn confirm_message_10() {
OutboundLanes::<TestRuntime>::insert(
test_lane_id(),
bp_messages::OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 0,
latest_received_nonce: 10,
latest_generated_nonce: 10,
},
);
}
fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
RuntimeCall::Messages(crate::Call::<TestRuntime>::receive_messages_delivery_proof {
proof: FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: Default::default(),
lane: test_lane_id(),
},
relayers_state: UnrewardedRelayersState { last_delivered_nonce, ..Default::default() },
})
.check_obsolete_call()
.is_ok()
}
#[test]
fn extension_rejects_obsolete_confirmations() {
run_test(|| {
// when current best confirmed is message#10 and we're trying to confirm message#5 => tx
// is rejected
confirm_message_10();
assert!(!validate_message_confirmation(5));
});
}
#[test]
fn extension_rejects_same_confirmation() {
run_test(|| {
// when current best confirmed is message#10 and we're trying to confirm message#10 =>
// tx is rejected
confirm_message_10();
assert!(!validate_message_confirmation(10));
});
}
#[test]
fn extension_rejects_empty_confirmation_even_if_there_are_no_free_unrewarded_entries() {
run_test(|| {
confirm_message_10();
fill_unrewarded_relayers();
assert!(!validate_message_confirmation(10));
});
}
#[test]
fn extension_accepts_new_confirmation() {
run_test(|| {
// when current best confirmed is message#10 and we're trying to confirm message#15 =>
// tx is accepted
confirm_message_10();
assert!(validate_message_confirmation(15));
});
}
fn was_message_delivery_successful(
bundled_range: RangeInclusive<MessageNonce>,
is_empty: bool,
) -> bool {
CallHelper::<TestRuntime, ()>::was_successful(&MessagesCallInfo::ReceiveMessagesProof(
ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: test_lane_id(),
bundled_range,
best_stored_nonce: 0, // doesn't matter for `was_successful`
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: 0, // doesn't matter for `was_successful`
free_message_slots: if is_empty {
0
} else {
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
},
},
},
))
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
run_test(|| {
fill_unrewarded_messages();
assert!(!was_message_delivery_successful(10..=9, true));
});
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn was_successful_returns_true_for_successful_reward_confirmation_transaction() {
run_test(|| {
assert!(was_message_delivery_successful(10..=9, true));
});
}
#[test]
fn was_successful_returns_false_for_failed_delivery() {
run_test(|| {
deliver_message_10();
assert!(!was_message_delivery_successful(10..=12, false));
});
}
#[test]
fn was_successful_returns_false_for_partially_successful_delivery() {
run_test(|| {
deliver_message_10();
assert!(!was_message_delivery_successful(9..=12, false));
});
}
#[test]
fn was_successful_returns_true_for_successful_delivery() {
run_test(|| {
deliver_message_10();
assert!(was_message_delivery_successful(9..=10, false));
});
}
fn was_message_confirmation_successful(bundled_range: RangeInclusive<MessageNonce>) -> bool {
CallHelper::<TestRuntime, ()>::was_successful(
&MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: test_lane_id(),
bundled_range,
best_stored_nonce: 0, // doesn't matter for `was_successful`
},
)),
)
}
#[test]
fn was_successful_returns_false_for_failed_confirmation() {
run_test(|| {
confirm_message_10();
assert!(!was_message_confirmation_successful(10..=12));
});
}
#[test]
fn was_successful_returns_false_for_partially_successful_confirmation() {
run_test(|| {
confirm_message_10();
assert!(!was_message_confirmation_successful(9..=12));
});
}
#[test]
fn was_successful_returns_true_for_successful_confirmation() {
run_test(|| {
confirm_message_10();
assert!(was_message_confirmation_successful(9..=10));
});
}
}
@@ -0,0 +1,570 @@
// Copyright (C) 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/>.
//! Everything about incoming messages receival.
use crate::{BridgedChainOf, Config};
use bp_messages::{
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
ChainWithMessages, DeliveredMessages, InboundLaneData, LaneState, MessageKey, MessageNonce,
OutboundLaneData, ReceptionResult, UnrewardedRelayer,
};
use pezbp_runtime::AccountIdOf;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use scale_info::{Type, TypeInfo};
use pezsp_runtime::RuntimeDebug;
use pezsp_std::prelude::PartialEq;
/// Inbound lane storage.
pub trait InboundLaneStorage {
/// Id of relayer on source chain.
type Relayer: Clone + PartialEq;
/// Lane identifier type.
type LaneId: Encode;
/// Lane id.
fn id(&self) -> Self::LaneId;
/// Return maximal number of unrewarded relayer entries in inbound lane.
fn max_unrewarded_relayer_entries(&self) -> MessageNonce;
/// Return maximal number of unconfirmed messages in inbound lane.
fn max_unconfirmed_messages(&self) -> MessageNonce;
/// Get lane data from the storage.
fn data(&self) -> InboundLaneData<Self::Relayer>;
/// Update lane data in the storage.
fn set_data(&mut self, data: InboundLaneData<Self::Relayer>);
/// Purge lane data from the storage.
fn purge(self);
}
/// Inbound lane data wrapper that implements `MaxEncodedLen`.
///
/// We have already had `MaxEncodedLen`-like functionality before, but its usage has
/// been localized and we haven't been passing bounds (maximal count of unrewarded relayer entries,
/// maximal count of unconfirmed messages) everywhere. This wrapper allows us to avoid passing
/// these generic bounds all over the code.
///
/// The encoding of this type matches encoding of the corresponding `MessageData`.
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)]
pub struct StoredInboundLaneData<T: Config<I>, I: 'static>(
pub InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
);
impl<T: Config<I>, I: 'static> pezsp_std::ops::Deref for StoredInboundLaneData<T, I> {
type Target = InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Config<I>, I: 'static> pezsp_std::ops::DerefMut for StoredInboundLaneData<T, I> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: Config<I>, I: 'static> Default for StoredInboundLaneData<T, I> {
fn default() -> Self {
StoredInboundLaneData(Default::default())
}
}
impl<T: Config<I>, I: 'static> From<StoredInboundLaneData<T, I>>
for InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>
{
fn from(data: StoredInboundLaneData<T, I>) -> Self {
data.0
}
}
impl<T: Config<I>, I: 'static> EncodeLike<StoredInboundLaneData<T, I>>
for InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>
{
}
impl<T: Config<I>, I: 'static> TypeInfo for StoredInboundLaneData<T, I> {
type Identity = Self;
fn type_info() -> Type {
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::type_info()
}
}
impl<T: Config<I>, I: 'static> MaxEncodedLen for StoredInboundLaneData<T, I> {
fn max_encoded_len() -> usize {
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::encoded_size_hint(
BridgedChainOf::<T, I>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize,
)
.unwrap_or(usize::MAX)
}
}
/// Inbound messages lane.
pub struct InboundLane<S> {
storage: S,
}
impl<S: InboundLaneStorage> InboundLane<S> {
/// Create new inbound lane backed by given storage.
pub fn new(storage: S) -> Self {
InboundLane { storage }
}
/// Get lane state.
pub fn state(&self) -> LaneState {
self.storage.data().state
}
/// Returns storage reference.
pub fn storage(&self) -> &S {
&self.storage
}
/// Set lane state.
pub fn set_state(&mut self, state: LaneState) {
let mut data = self.storage.data();
data.state = state;
self.storage.set_data(data);
}
/// Receive state of the corresponding outbound lane.
pub fn receive_state_update(
&mut self,
outbound_lane_data: OutboundLaneData,
) -> Option<MessageNonce> {
let mut data = self.storage.data();
let last_delivered_nonce = data.last_delivered_nonce();
if outbound_lane_data.latest_received_nonce > last_delivered_nonce {
// this is something that should never happen if proofs are correct
return None;
}
if outbound_lane_data.latest_received_nonce <= data.last_confirmed_nonce {
return None;
}
let new_confirmed_nonce = outbound_lane_data.latest_received_nonce;
data.last_confirmed_nonce = new_confirmed_nonce;
// Firstly, remove all of the records where higher nonce <= new confirmed nonce
while data
.relayers
.front()
.map(|entry| entry.messages.end <= new_confirmed_nonce)
.unwrap_or(false)
{
data.relayers.pop_front();
}
// Secondly, update the next record with lower nonce equal to new confirmed nonce if needed.
// Note: There will be max. 1 record to update as we don't allow messages from relayers to
// overlap.
match data.relayers.front_mut() {
Some(entry) if entry.messages.begin <= new_confirmed_nonce => {
entry.messages.begin = new_confirmed_nonce + 1;
},
_ => {},
}
self.storage.set_data(data);
Some(outbound_lane_data.latest_received_nonce)
}
/// Receive new message.
pub fn receive_message<Dispatch: MessageDispatch<LaneId = S::LaneId>>(
&mut self,
relayer_at_bridged_chain: &S::Relayer,
nonce: MessageNonce,
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
) -> ReceptionResult<Dispatch::DispatchLevelResult> {
let mut data = self.storage.data();
if Some(nonce) != data.last_delivered_nonce().checked_add(1) {
return ReceptionResult::InvalidNonce;
}
// if there are more unrewarded relayer entries than we may accept, reject this message
if data.relayers.len() as MessageNonce >= self.storage.max_unrewarded_relayer_entries() {
return ReceptionResult::TooManyUnrewardedRelayers;
}
// if there are more unconfirmed messages than we may accept, reject this message
let unconfirmed_messages_count = nonce.saturating_sub(data.last_confirmed_nonce);
if unconfirmed_messages_count > self.storage.max_unconfirmed_messages() {
return ReceptionResult::TooManyUnconfirmedMessages;
}
// then, dispatch message
let dispatch_result = Dispatch::dispatch(DispatchMessage {
key: MessageKey { lane_id: self.storage.id(), nonce },
data: message_data,
});
// now let's update inbound lane storage
match data.relayers.back_mut() {
Some(entry) if entry.relayer == *relayer_at_bridged_chain => {
entry.messages.note_dispatched_message();
},
_ => {
data.relayers.push_back(UnrewardedRelayer {
relayer: relayer_at_bridged_chain.clone(),
messages: DeliveredMessages::new(nonce),
});
},
};
self.storage.set_data(data);
ReceptionResult::Dispatched(dispatch_result)
}
/// Purge lane state from the storage.
pub fn purge(self) {
self.storage.purge()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{active_inbound_lane, lanes_manager::RuntimeInboundLaneStorage, tests::mock::*};
use bp_messages::UnrewardedRelayersState;
fn receive_regular_message(
lane: &mut InboundLane<RuntimeInboundLaneStorage<TestRuntime, ()>>,
nonce: MessageNonce,
) {
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
nonce,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
}
#[test]
fn receive_status_update_ignores_status_from_the_future() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
receive_regular_message(&mut lane, 1);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 10,
..Default::default()
}),
None,
);
assert_eq!(lane.storage.data().last_confirmed_nonce, 0);
});
}
#[test]
fn receive_status_update_ignores_obsolete_status() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
receive_regular_message(&mut lane, 1);
receive_regular_message(&mut lane, 2);
receive_regular_message(&mut lane, 3);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 3,
..Default::default()
}),
Some(3),
);
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 3,
..Default::default()
}),
None,
);
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
});
}
#[test]
fn receive_status_update_works() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
receive_regular_message(&mut lane, 1);
receive_regular_message(&mut lane, 2);
receive_regular_message(&mut lane, 3);
assert_eq!(lane.storage.data().last_confirmed_nonce, 0);
assert_eq!(
lane.storage.data().relayers,
vec![unrewarded_relayer(1, 3, TEST_RELAYER_A)]
);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 2,
..Default::default()
}),
Some(2),
);
assert_eq!(lane.storage.data().last_confirmed_nonce, 2);
assert_eq!(
lane.storage.data().relayers,
vec![unrewarded_relayer(3, 3, TEST_RELAYER_A)]
);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 3,
..Default::default()
}),
Some(3),
);
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
assert_eq!(lane.storage.data().relayers, vec![]);
});
}
#[test]
fn receive_status_update_works_with_batches_from_relayers() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
let mut seed_storage_data = lane.storage.data();
// Prepare data
seed_storage_data.last_confirmed_nonce = 0;
seed_storage_data.relayers.push_back(unrewarded_relayer(1, 1, TEST_RELAYER_A));
// Simulate messages batch (2, 3, 4) from relayer #2
seed_storage_data.relayers.push_back(unrewarded_relayer(2, 4, TEST_RELAYER_B));
seed_storage_data.relayers.push_back(unrewarded_relayer(5, 5, TEST_RELAYER_C));
lane.storage.set_data(seed_storage_data);
// Check
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 3,
..Default::default()
}),
Some(3),
);
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
assert_eq!(
lane.storage.data().relayers,
vec![
unrewarded_relayer(4, 4, TEST_RELAYER_B),
unrewarded_relayer(5, 5, TEST_RELAYER_C)
]
);
});
}
#[test]
fn fails_to_receive_message_with_incorrect_nonce() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
10,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::InvalidNonce
);
assert_eq!(lane.storage.data().last_delivered_nonce(), 0);
});
}
#[test]
fn fails_to_receive_messages_above_unrewarded_relayer_entries_limit_per_lane() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
let max_nonce = BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
for current_nonce in 1..max_nonce + 1 {
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&(TEST_RELAYER_A + current_nonce),
current_nonce,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
}
// Fails to dispatch new message from different than latest relayer.
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&(TEST_RELAYER_A + max_nonce + 1),
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::TooManyUnrewardedRelayers,
);
// Fails to dispatch new messages from latest relayer. Prevents griefing attacks.
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&(TEST_RELAYER_A + max_nonce),
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::TooManyUnrewardedRelayers,
);
});
}
#[test]
fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
let max_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
for current_nonce in 1..=max_nonce {
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
current_nonce,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
}
// Fails to dispatch new message from different than latest relayer.
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_B,
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::TooManyUnconfirmedMessages,
);
// Fails to dispatch new messages from latest relayer.
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::TooManyUnconfirmedMessages,
);
});
}
#[test]
fn correctly_receives_following_messages_from_two_relayers_alternately() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_B,
2,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
3,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.storage.data().relayers,
vec![
unrewarded_relayer(1, 1, TEST_RELAYER_A),
unrewarded_relayer(2, 2, TEST_RELAYER_B),
unrewarded_relayer(3, 3, TEST_RELAYER_A)
]
);
});
}
#[test]
fn rejects_same_message_from_two_different_relayers() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_B,
1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::InvalidNonce,
);
});
}
#[test]
fn correct_message_is_processed_instantly() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
receive_regular_message(&mut lane, 1);
assert_eq!(lane.storage.data().last_delivered_nonce(), 1);
});
}
#[test]
fn unspent_weight_is_returned_by_receive_message() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
let mut payload = REGULAR_PAYLOAD;
*payload.dispatch_result.unspent_weight.ref_time_mut() = 1;
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
1,
inbound_message_data(payload)
),
ReceptionResult::Dispatched(dispatch_result(1))
);
});
}
#[test]
fn first_message_is_confirmed_correctly() {
run_test(|| {
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
receive_regular_message(&mut lane, 1);
receive_regular_message(&mut lane, 2);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 1,
..Default::default()
}),
Some(1),
);
assert_eq!(
inbound_unrewarded_relayers_state(test_lane_id()),
UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1,
last_delivered_nonce: 2,
},
);
});
}
}
@@ -0,0 +1,287 @@
// 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/>.
use crate::{
BridgedChainOf, Config, InboundLane, InboundLaneStorage, InboundLanes, OutboundLane,
OutboundLaneStorage, OutboundLanes, OutboundMessages, StoredInboundLaneData,
StoredMessagePayload,
};
use bp_messages::{
target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneState, MessageKey,
MessageNonce, OutboundLaneData,
};
use pezbp_runtime::AccountIdOf;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use pezframe_support::{ensure, pezsp_runtime::RuntimeDebug, PalletError};
use scale_info::TypeInfo;
use pezsp_std::marker::PhantomData;
/// Lanes manager errors.
#[derive(
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
)]
pub enum LanesManagerError {
/// Inbound lane already exists.
InboundLaneAlreadyExists,
/// Outbound lane already exists.
OutboundLaneAlreadyExists,
/// No inbound lane with given id.
UnknownInboundLane,
/// No outbound lane with given id.
UnknownOutboundLane,
/// Inbound lane with given id is closed.
ClosedInboundLane,
/// Outbound lane with given id is closed.
ClosedOutboundLane,
/// Message dispatcher is inactive at given inbound lane. This is logical equivalent
/// of the [`Self::ClosedInboundLane`] variant.
LaneDispatcherInactive,
}
/// Message lanes manager.
pub struct LanesManager<T, I>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> Default for LanesManager<T, I> {
fn default() -> Self {
Self::new()
}
}
impl<T: Config<I>, I: 'static> LanesManager<T, I> {
/// Create new lanes manager.
pub fn new() -> Self {
Self(PhantomData)
}
/// Create new inbound lane in `Opened` state.
pub fn create_inbound_lane(
&self,
lane_id: T::LaneId,
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
InboundLanes::<T, I>::try_mutate(lane_id, |lane| match lane {
Some(_) => Err(LanesManagerError::InboundLaneAlreadyExists),
None => {
*lane = Some(StoredInboundLaneData(InboundLaneData {
state: LaneState::Opened,
..Default::default()
}));
Ok(())
},
})?;
self.active_inbound_lane(lane_id)
}
/// Create new outbound lane in `Opened` state.
pub fn create_outbound_lane(
&self,
lane_id: T::LaneId,
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
OutboundLanes::<T, I>::try_mutate(lane_id, |lane| match lane {
Some(_) => Err(LanesManagerError::OutboundLaneAlreadyExists),
None => {
*lane = Some(OutboundLaneData { state: LaneState::Opened, ..Default::default() });
Ok(())
},
})?;
self.active_outbound_lane(lane_id)
}
/// Get existing inbound lane, checking that it is in usable state.
pub fn active_inbound_lane(
&self,
lane_id: T::LaneId,
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, true)?))
}
/// Get existing outbound lane, checking that it is in usable state.
pub fn active_outbound_lane(
&self,
lane_id: T::LaneId,
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, true)?))
}
/// Get existing inbound lane without any additional state checks.
pub fn any_state_inbound_lane(
&self,
lane_id: T::LaneId,
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, false)?))
}
/// Get existing outbound lane without any additional state checks.
pub fn any_state_outbound_lane(
&self,
lane_id: T::LaneId,
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, false)?))
}
}
/// Runtime inbound lane storage.
pub struct RuntimeInboundLaneStorage<T: Config<I>, I: 'static = ()> {
pub(crate) lane_id: T::LaneId,
pub(crate) cached_data: InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
}
impl<T: Config<I>, I: 'static> RuntimeInboundLaneStorage<T, I> {
/// Creates new runtime inbound lane storage for given **existing** lane.
fn from_lane_id(
lane_id: T::LaneId,
check_active: bool,
) -> Result<RuntimeInboundLaneStorage<T, I>, LanesManagerError> {
let cached_data =
InboundLanes::<T, I>::get(lane_id).ok_or(LanesManagerError::UnknownInboundLane)?;
if check_active {
// check that the lane is not explicitly closed
ensure!(cached_data.state.is_active(), LanesManagerError::ClosedInboundLane);
// apart from the explicit closure, the lane may be unable to receive any messages.
// Right now we do an additional check here, but it may be done later (e.g. by
// explicitly closing the lane and reopening it from
// `pezpallet-xcm-bridge-hub::on-initialize`)
//
// The fact that we only check it here, means that the `MessageDispatch` may switch
// to inactive state during some message dispatch in the middle of message delivery
// transaction. But we treat result of `MessageDispatch::is_active()` as a hint, so
// we know that it won't drop messages - just it experiences problems with processing.
// This would allow us to check that in our signed extensions, and invalidate
// transaction early, thus avoiding losing honest relayers funds. This problem should
// gone with relayers coordination protocol.
//
// There's a limit on number of messages in the message delivery transaction, so even
// if we dispatch (enqueue) some additional messages, we'll know the maximal queue
// length;
ensure!(
T::MessageDispatch::is_active(lane_id),
LanesManagerError::LaneDispatcherInactive
);
}
Ok(RuntimeInboundLaneStorage { lane_id, cached_data: cached_data.into() })
}
/// Returns number of bytes that may be subtracted from the PoV component of
/// `receive_messages_proof` call, because the actual inbound lane state is smaller than the
/// maximal configured.
///
/// Maximal inbound lane state set size is configured by the
/// `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` constant from the pezpallet configuration. The PoV
/// of the call includes the maximal size of inbound lane state. If the actual size is smaller,
/// we may subtract extra bytes from this component.
pub fn extra_proof_size_bytes(&self) -> u64 {
let max_encoded_len = StoredInboundLaneData::<T, I>::max_encoded_len();
let relayers_count = self.data().relayers.len();
let actual_encoded_len =
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::encoded_size_hint(relayers_count)
.unwrap_or(usize::MAX);
max_encoded_len.saturating_sub(actual_encoded_len) as _
}
}
impl<T: Config<I>, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
type Relayer = AccountIdOf<BridgedChainOf<T, I>>;
type LaneId = T::LaneId;
fn id(&self) -> Self::LaneId {
self.lane_id
}
fn max_unrewarded_relayer_entries(&self) -> MessageNonce {
BridgedChainOf::<T, I>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
}
fn max_unconfirmed_messages(&self) -> MessageNonce {
BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
}
fn data(&self) -> InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>> {
self.cached_data.clone()
}
fn set_data(&mut self, data: InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>) {
self.cached_data = data.clone();
InboundLanes::<T, I>::insert(self.lane_id, StoredInboundLaneData::<T, I>(data))
}
fn purge(self) {
InboundLanes::<T, I>::remove(self.lane_id)
}
}
/// Runtime outbound lane storage.
#[derive(Debug, PartialEq, Eq)]
pub struct RuntimeOutboundLaneStorage<T: Config<I>, I: 'static> {
pub(crate) lane_id: T::LaneId,
pub(crate) cached_data: OutboundLaneData,
pub(crate) _phantom: PhantomData<(T, I)>,
}
impl<T: Config<I>, I: 'static> RuntimeOutboundLaneStorage<T, I> {
/// Creates new runtime outbound lane storage for given **existing** lane.
fn from_lane_id(lane_id: T::LaneId, check_active: bool) -> Result<Self, LanesManagerError> {
let cached_data =
OutboundLanes::<T, I>::get(lane_id).ok_or(LanesManagerError::UnknownOutboundLane)?;
ensure!(
!check_active || cached_data.state.is_active(),
LanesManagerError::ClosedOutboundLane
);
Ok(Self { lane_id, cached_data, _phantom: PhantomData })
}
}
impl<T: Config<I>, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
type StoredMessagePayload = StoredMessagePayload<T, I>;
type LaneId = T::LaneId;
fn id(&self) -> Self::LaneId {
self.lane_id
}
fn data(&self) -> OutboundLaneData {
self.cached_data.clone()
}
fn set_data(&mut self, data: OutboundLaneData) {
self.cached_data = data.clone();
OutboundLanes::<T, I>::insert(self.lane_id, data)
}
#[cfg(test)]
fn message(&self, nonce: &MessageNonce) -> Option<Self::StoredMessagePayload> {
OutboundMessages::<T, I>::get(MessageKey { lane_id: self.lane_id, nonce: *nonce })
.map(Into::into)
}
fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload) {
OutboundMessages::<T, I>::insert(
MessageKey { lane_id: self.lane_id, nonce },
message_payload,
);
}
fn remove_message(&mut self, nonce: &MessageNonce) {
OutboundMessages::<T, I>::remove(MessageKey { lane_id: self.lane_id, nonce: *nonce });
}
fn purge(self) {
OutboundLanes::<T, I>::remove(self.lane_id)
}
}
+791
View File
@@ -0,0 +1,791 @@
// Copyright (C) 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.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use inbound_lane::{InboundLane, InboundLaneStorage, StoredInboundLaneData};
pub use lanes_manager::{
LanesManager, LanesManagerError, RuntimeInboundLaneStorage, RuntimeOutboundLaneStorage,
};
pub use outbound_lane::{
OutboundLane, OutboundLaneStorage, ReceptionConfirmationError, StoredMessagePayload,
};
pub use weights::WeightInfo;
pub use weights_ext::{
ensure_able_to_receive_confirmation, ensure_able_to_receive_message,
ensure_maximal_message_dispatch, ensure_weights_are_correct, WeightInfoExt,
EXPECTED_DEFAULT_MESSAGE_LENGTH, EXTRA_STORAGE_PROOF_SIZE,
};
use bp_header_pez_chain::HeaderChain;
use bp_messages::{
source_chain::{
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
SendMessageArtifacts,
},
target_chain::{
DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch,
ProvedLaneMessages, ProvedMessages,
},
ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey,
MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails,
UnrewardedRelayersState, VerificationError,
};
use pezbp_runtime::{
AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt,
Size,
};
use codec::{Decode, Encode};
use pezframe_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound};
use pezsp_std::{marker::PhantomData, prelude::*};
mod call_ext;
mod inbound_lane;
mod lanes_manager;
mod outbound_lane;
mod proofs;
mod tests;
mod weights_ext;
pub mod weights;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod migration;
pub use call_ext::*;
pub use pezpallet::*;
#[cfg(feature = "test-helpers")]
pub use tests::*;
/// The target that will be used when publishing logs related to this pezpallet.
pub const LOG_TARGET: &str = "runtime::bridge-messages";
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::*;
use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult};
use pezbp_runtime::RangeInclusiveExt;
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
#[pezpallet::config]
pub trait Config<I: 'static = ()>: pezframe_system::Config {
// General types
/// The overarching event type.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
/// Benchmarks results from runtime we're plugged into.
type WeightInfo: WeightInfoExt;
/// This chain type.
type ThisChain: ChainWithMessages;
/// Bridged chain type.
type BridgedChain: ChainWithMessages;
/// Bridged chain headers provider.
type BridgedHeaderChain: HeaderChain<Self::BridgedChain>;
/// Payload type of outbound messages. This payload is dispatched on the bridged chain.
type OutboundPayload: Parameter + Size;
/// Payload type of inbound messages. This payload is dispatched on this chain.
type InboundPayload: Decode;
/// Lane identifier type.
type LaneId: LaneIdType;
/// Handler for relayer payments that happen during message delivery transaction.
type DeliveryPayments: DeliveryPayments<Self::AccountId>;
/// Handler for relayer payments that happen during message delivery confirmation
/// transaction.
type DeliveryConfirmationPayments: DeliveryConfirmationPayments<
Self::AccountId,
Self::LaneId,
>;
/// Delivery confirmation callback.
type OnMessagesDelivered: OnMessagesDelivered<Self::LaneId>;
/// Message dispatch handler.
type MessageDispatch: MessageDispatch<
DispatchPayload = Self::InboundPayload,
LaneId = Self::LaneId,
>;
}
/// Shortcut to this chain type for Config.
pub type ThisChainOf<T, I> = <T as Config<I>>::ThisChain;
/// Shortcut to bridged chain type for Config.
pub type BridgedChainOf<T, I> = <T as Config<I>>::BridgedChain;
/// Shortcut to bridged header chain type for Config.
pub type BridgedHeaderChainOf<T, I> = <T as Config<I>>::BridgedHeaderChain;
/// Shortcut to lane identifier type for Config.
pub type LaneIdOf<T, I> = <T as Config<I>>::LaneId;
#[pezpallet::pezpallet]
#[pezpallet::storage_version(migration::STORAGE_VERSION)]
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pezpallet<T, I> {
const LOG_TARGET: &'static str = LOG_TARGET;
type OwnerStorage = PalletOwner<T, I>;
type OperatingMode = MessagesOperatingMode;
type OperatingModeStorage = PalletOperatingMode<T, I>;
}
#[pezpallet::call]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
/// Change `PalletOwner`.
///
/// May only be called either by root, or by `PalletOwner`.
#[pezpallet::call_index(0)]
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
}
/// Halt or resume all/some pezpallet operations.
///
/// May only be called either by root, or by `PalletOwner`.
#[pezpallet::call_index(1)]
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(
origin: OriginFor<T>,
operating_mode: MessagesOperatingMode,
) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
}
/// 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.
///
/// The call fails if:
///
/// - the pezpallet is halted;
///
/// - the call origin is not `Signed(_)`;
///
/// - there are too many messages in the proof;
///
/// - the proof verification procedure returns an error - e.g. because header used to craft
/// proof is not imported by the associated finality pezpallet;
///
/// - the `dispatch_weight` argument is not sufficient to dispatch all bundled messages.
///
/// The call may succeed, but some messages may not be delivered e.g. if they are not fit
/// into the unrewarded relayers vector.
#[pezpallet::call_index(2)]
#[pezpallet::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: AccountIdOf<BridgedChainOf<T, I>>,
proof: Box<FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>>,
messages_count: u32,
dispatch_weight: Weight,
) -> DispatchResultWithPostInfo {
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
let relayer_id_at_this_chain = ensure_signed(origin)?;
// reject transactions that are declaring too many messages
ensure!(
MessageNonce::from(messages_count) <=
BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
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 message. 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 (lane_id, lane_data) =
verify_and_decode_messages_proof::<T, I>(*proof, messages_count).map_err(
|err| {
tracing::trace!(target: LOG_TARGET, error=?err, "Rejecting invalid messages proof");
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;
let mut lane = active_inbound_lane::<T, I>(lane_id)?;
// subtract extra storage proof bytes from the actual PoV size - there may be
// less unrewarded relayers than the maximal configured value
let lane_extra_proof_size_bytes = lane.storage().extra_proof_size_bytes();
actual_weight = actual_weight.set_proof_size(
actual_weight.proof_size().saturating_sub(lane_extra_proof_size_bytes),
);
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 {
tracing::trace!(
target: LOG_TARGET,
?lane_id,
latest_confirmed_nonce=%updated_latest_confirmed_nonce,
unrewarded_relayers=?UnrewardedRelayersState::from(&lane.storage().data()),
"Received state update"
);
}
}
let mut messages_received_status =
ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len()));
for mut message in lane_data.messages {
debug_assert_eq!(message.key.lane_id, lane_id);
total_messages += 1;
// 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 message_dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message);
if message_dispatch_weight.any_gt(dispatch_weight_left) {
tracing::trace!(
target: LOG_TARGET,
?lane_id,
declared=%message_dispatch_weight,
left=%dispatch_weight_left,
"Cannot dispatch any more messages"
);
fail!(Error::<T, I>::InsufficientDispatchWeight);
}
let receival_result = lane.receive_message::<T::MessageDispatch>(
&relayer_id_at_bridged_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 = match &receival_result {
ReceptionResult::Dispatched(dispatch_result) => {
valid_messages += 1;
dispatch_result.unspent_weight
},
ReceptionResult::InvalidNonce |
ReceptionResult::TooManyUnrewardedRelayers |
ReceptionResult::TooManyUnconfirmedMessages => message_dispatch_weight,
};
messages_received_status.push(message.key.nonce, receival_result);
let unspent_weight = unspent_weight.min(message_dispatch_weight);
dispatch_weight_left -= message_dispatch_weight - unspent_weight;
actual_weight = actual_weight.saturating_sub(unspent_weight);
}
// let's now deal with relayer payments
T::DeliveryPayments::pay_reward(
relayer_id_at_this_chain,
total_messages,
valid_messages,
actual_weight,
);
tracing::debug!(
target: LOG_TARGET,
total=%total_messages,
valid=%valid_messages,
%actual_weight,
%declared_weight,
"Received messages."
);
Self::deposit_event(Event::MessagesReceived(messages_received_status));
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
}
/// Receive messages delivery proof from bridged chain.
#[pezpallet::call_index(3)]
#[pezpallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
proof,
relayers_state,
))]
pub fn receive_messages_delivery_proof(
origin: OriginFor<T>,
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
mut relayers_state: UnrewardedRelayersState,
) -> DispatchResultWithPostInfo {
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
let proof_size = proof.size();
let confirmation_relayer = ensure_signed(origin)?;
let (lane_id, lane_data) = proofs::verify_messages_delivery_proof::<T, I>(proof)
.map_err(|err| {
tracing::trace!(
target: LOG_TARGET,
error=?err,
"Rejecting invalid messages delivery proof"
);
Error::<T, I>::InvalidMessagesDeliveryProof
})?;
ensure!(
relayers_state.is_valid(&lane_data),
Error::<T, I>::InvalidUnrewardedRelayersState
);
// mark messages as delivered
let mut lane = any_state_outbound_lane::<T, I>(lane_id)?;
let last_delivered_nonce = lane_data.last_delivered_nonce();
let confirmed_messages = lane
.confirm_delivery(
relayers_state.total_messages,
last_delivered_nonce,
&lane_data.relayers,
)
.map_err(Error::<T, I>::ReceptionConfirmation)?;
if let Some(confirmed_messages) = confirmed_messages {
// emit 'delivered' event
let received_range = confirmed_messages.begin..=confirmed_messages.end;
Self::deposit_event(Event::MessagesDelivered {
lane_id: lane_id.into(),
messages: confirmed_messages,
});
// if some new messages have been confirmed, reward relayers
let actually_rewarded_relayers = T::DeliveryConfirmationPayments::pay_reward(
lane_id,
lane_data.relayers,
&confirmation_relayer,
&received_range,
);
// update relayers state with actual numbers to compute actual weight below
relayers_state.unrewarded_relayer_entries = pezsp_std::cmp::min(
relayers_state.unrewarded_relayer_entries,
actually_rewarded_relayers,
);
relayers_state.total_messages = pezsp_std::cmp::min(
relayers_state.total_messages,
received_range.checked_len().unwrap_or(MessageNonce::MAX),
);
};
tracing::trace!(
target: LOG_TARGET,
?lane_id,
%last_delivered_nonce,
"Received messages delivery proof up to (and including)"
);
// notify others about messages delivery
T::OnMessagesDelivered::on_messages_delivered(
lane_id,
lane.data().queued_messages().saturating_len(),
);
// because of lags, the inbound lane state (`lane_data`) may have entries for
// already rewarded relayers and messages (if all entries are duplicated, then
// this transaction must be filtered out by our signed extension)
let actual_weight = T::WeightInfo::receive_messages_delivery_proof_weight(
&PreComputedSize(proof_size as usize),
&relayers_state,
);
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
}
}
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// Message has been accepted and is waiting to be delivered.
MessageAccepted {
/// Lane, which has accepted the message.
lane_id: T::LaneId,
/// Nonce of accepted message.
nonce: MessageNonce,
},
/// Messages have been received from the bridged chain.
MessagesReceived(
/// Result of received messages dispatch.
ReceivedMessages<
<T::MessageDispatch as MessageDispatch>::DispatchLevelResult,
T::LaneId,
>,
),
/// Messages in the inclusive range have been delivered to the bridged chain.
MessagesDelivered {
/// Lane for which the delivery has been confirmed.
lane_id: T::LaneId,
/// Delivered messages.
messages: DeliveredMessages,
},
}
#[pezpallet::error]
#[derive(PartialEq, Eq)]
pub enum Error<T, I = ()> {
/// Pezpallet is not in Normal operating mode.
NotOperatingNormally,
/// Error that is reported by the lanes manager.
LanesManager(LanesManagerError),
/// Message has been treated as invalid by the pezpallet logic.
MessageRejectedByPallet(VerificationError),
/// The transaction brings too many messages.
TooManyMessagesInTheProof,
/// Invalid messages has been submitted.
InvalidMessagesProof,
/// Invalid messages delivery proof has been submitted.
InvalidMessagesDeliveryProof,
/// The relayer has declared invalid unrewarded relayers state in the
/// `receive_messages_delivery_proof` call.
InvalidUnrewardedRelayersState,
/// The cumulative dispatch weight, passed by relayer is not enough to cover dispatch
/// of all bundled messages.
InsufficientDispatchWeight,
/// Error confirming messages receival.
ReceptionConfirmation(ReceptionConfirmationError),
/// Error generated by the `OwnedBridgeModule` trait.
BridgeModule(pezbp_runtime::OwnedBridgeModuleError),
}
/// Optional pezpallet owner.
///
/// Pezpallet owner has a right to halt all pezpallet operations and then resume it. If it is
/// `None`, then there are no direct ways to halt/resume pezpallet operations, but other
/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
/// flag directly or call the `set_operating_mode`).
#[pezpallet::storage]
pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
/// The current operating mode of the pezpallet.
///
/// Depending on the mode either all, some, or no transactions will be allowed.
#[pezpallet::storage]
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
StorageValue<_, MessagesOperatingMode, ValueQuery>;
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/89: let's limit number of
// possible opened lanes && use it to constraint maps below
/// Map of lane id => inbound lane data.
#[pezpallet::storage]
pub type InboundLanes<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData<T, I>, OptionQuery>;
/// Map of lane id => outbound lane data.
#[pezpallet::storage]
pub type OutboundLanes<T: Config<I>, I: 'static = ()> = StorageMap<
Hasher = Blake2_128Concat,
Key = T::LaneId,
Value = OutboundLaneData,
QueryKind = OptionQuery,
>;
/// All queued outbound messages.
#[pezpallet::storage]
pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, MessageKey<T::LaneId>, StoredMessagePayload<T, I>>;
#[pezpallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
/// Initial pezpallet operating mode.
pub operating_mode: MessagesOperatingMode,
/// Initial pezpallet owner.
pub owner: Option<T::AccountId>,
/// Opened lanes.
pub opened_lanes: Vec<T::LaneId>,
/// Dummy marker.
#[serde(skip)]
pub _phantom: pezsp_std::marker::PhantomData<I>,
}
#[pezpallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig 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);
}
for lane_id in &self.opened_lanes {
InboundLanes::<T, I>::insert(lane_id, InboundLaneData::opened());
OutboundLanes::<T, I>::insert(lane_id, OutboundLaneData::opened());
}
}
}
#[pezpallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
Self::do_try_state()
}
}
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
/// Get stored data of the outbound message with given nonce.
pub fn outbound_message_data(
lane: T::LaneId,
nonce: MessageNonce,
) -> Option<MessagePayload> {
OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce }).map(Into::into)
}
/// Prepare data, related to given inbound message.
pub fn inbound_message_data(
lane: T::LaneId,
payload: MessagePayload,
outbound_details: OutboundMessageDetails,
) -> InboundMessageDetails {
let mut dispatch_message = DispatchMessage {
key: MessageKey { lane_id: lane, nonce: outbound_details.nonce },
data: payload.into(),
};
InboundMessageDetails {
dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message),
}
}
/// Return outbound lane data.
pub fn outbound_lane_data(lane: T::LaneId) -> Option<OutboundLaneData> {
OutboundLanes::<T, I>::get(lane)
}
/// Return inbound lane data.
pub fn inbound_lane_data(
lane: T::LaneId,
) -> Option<InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>> {
InboundLanes::<T, I>::get(lane).map(|lane| lane.0)
}
}
#[cfg(any(feature = "try-runtime", test))]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
/// Ensure the correctness of the state of this pezpallet.
pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
Self::do_try_state_for_outbound_lanes()
}
/// Ensure the correctness of the state of outbound lanes.
pub fn do_try_state_for_outbound_lanes() -> Result<(), pezsp_runtime::TryRuntimeError> {
use pezsp_runtime::traits::One;
use pezsp_std::vec::Vec;
// collect unpruned lanes
let mut unpruned_lanes = Vec::new();
for (lane_id, lane_data) in OutboundLanes::<T, I>::iter() {
let Some(expected_last_prunned_nonce) =
lane_data.oldest_unpruned_nonce.checked_sub(One::one())
else {
continue;
};
// collect message_nonces that were supposed to be pruned
let mut unpruned_message_nonces = Vec::new();
const MAX_MESSAGES_ITERATION: u64 = 16;
let start_nonce =
expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0);
for current_nonce in start_nonce..=expected_last_prunned_nonce {
// check a message for current_nonce
if OutboundMessages::<T, I>::contains_key(MessageKey {
lane_id,
nonce: current_nonce,
}) {
unpruned_message_nonces.push(current_nonce);
}
}
if !unpruned_message_nonces.is_empty() {
tracing::warn!(
target: LOG_TARGET,
?lane_id,
?lane_data,
?unpruned_message_nonces,
"do_try_state_for_outbound_lanes found",
);
unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces));
}
}
// ensure messages before `oldest_unpruned_nonce` are really pruned.
ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!");
Ok(())
}
}
}
/// Structure, containing a validated message payload and all the info required
/// to send it on the bridge.
#[derive(Debug, PartialEq, Eq)]
pub struct SendMessageArgs<T: Config<I>, I: 'static> {
lane_id: T::LaneId,
lane: OutboundLane<RuntimeOutboundLaneStorage<T, I>>,
payload: StoredMessagePayload<T, I>,
}
impl<T, I> bp_messages::source_chain::MessagesBridge<T::OutboundPayload, T::LaneId> for Pezpallet<T, I>
where
T: Config<I>,
I: 'static,
{
type Error = Error<T, I>;
type SendMessageArgs = SendMessageArgs<T, I>;
fn validate_message(
lane_id: T::LaneId,
message: &T::OutboundPayload,
) -> Result<SendMessageArgs<T, I>, Self::Error> {
// we can't accept any messages if the pezpallet is halted
ensure_normal_operating_mode::<T, I>()?;
// check lane
let lane = active_outbound_lane::<T, I>(lane_id)?;
Ok(SendMessageArgs {
lane_id,
lane,
payload: StoredMessagePayload::<T, I>::try_from(message.encode()).map_err(|_| {
Error::<T, I>::MessageRejectedByPallet(VerificationError::MessageTooLarge)
})?,
})
}
fn send_message(args: SendMessageArgs<T, I>) -> SendMessageArtifacts {
// save message in outbound storage and emit event
let mut lane = args.lane;
let message_len = args.payload.len();
let nonce = lane.send_message(args.payload);
// return number of messages in the queue to let sender know about its state
let enqueued_messages = lane.data().queued_messages().saturating_len();
tracing::trace!(
target: LOG_TARGET,
lane_id=?args.lane_id,
%nonce,
message_size=?message_len,
"Accepted message"
);
Pezpallet::<T, I>::deposit_event(Event::MessageAccepted {
lane_id: args.lane_id.into(),
nonce,
});
SendMessageArtifacts { nonce, enqueued_messages }
}
}
/// Ensure that the pezpallet is in normal operational mode.
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
if PalletOperatingMode::<T, I>::get() ==
MessagesOperatingMode::Basic(BasicOperatingMode::Normal)
{
return Ok(());
}
Err(Error::<T, I>::NotOperatingNormally)
}
/// Creates new inbound lane object, backed by runtime storage. Lane must be active.
fn active_inbound_lane<T: Config<I>, I: 'static>(
lane_id: T::LaneId,
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, Error<T, I>> {
LanesManager::<T, I>::new()
.active_inbound_lane(lane_id)
.map_err(Error::LanesManager)
}
/// Creates new outbound lane object, backed by runtime storage. Lane must be active.
fn active_outbound_lane<T: Config<I>, I: 'static>(
lane_id: T::LaneId,
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
LanesManager::<T, I>::new()
.active_outbound_lane(lane_id)
.map_err(Error::LanesManager)
}
/// Creates new outbound lane object, backed by runtime storage.
fn any_state_outbound_lane<T: Config<I>, I: 'static>(
lane_id: T::LaneId,
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
LanesManager::<T, I>::new()
.any_state_outbound_lane(lane_id)
.map_err(Error::LanesManager)
}
/// Verify messages proof and return proved messages with decoded payload.
fn verify_and_decode_messages_proof<T: Config<I>, I: 'static>(
proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
messages_count: u32,
) -> Result<
ProvedMessages<T::LaneId, DispatchMessage<T::InboundPayload, T::LaneId>>,
VerificationError,
> {
// `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX`
// check guarantees that the `message_count` is sane and Vec<Message> may be allocated.
// (tx with too many messages will either be rejected from the pool, or will fail earlier)
proofs::verify_messages_proof::<T, I>(proof, messages_count).map(|(lane, lane_data)| {
(
lane,
ProvedLaneMessages {
lane_state: lane_data.lane_state,
messages: lane_data.messages.into_iter().map(Into::into).collect(),
},
)
})
}
@@ -0,0 +1,146 @@
// Copyright (C) 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/>.
//! A module that is responsible for migration of storage.
use crate::{Config, Pezpallet};
use pezframe_support::{
traits::{Get, StorageVersion},
weights::Weight,
};
/// The in-code storage version.
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
/// This module contains data structures that are valid for the initial state of `0`.
/// (used with v1 migration).
pub mod v0 {
use super::Config;
use crate::BridgedChainOf;
use bp_messages::{MessageNonce, UnrewardedRelayer};
use pezbp_runtime::AccountIdOf;
use codec::{Decode, Encode};
use pezsp_std::collections::vec_deque::VecDeque;
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
pub(crate) struct StoredInboundLaneData<T: Config<I>, I: 'static>(
pub(crate) InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
);
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
pub(crate) struct InboundLaneData<RelayerId> {
pub(crate) relayers: VecDeque<UnrewardedRelayer<RelayerId>>,
pub(crate) last_confirmed_nonce: MessageNonce,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
pub(crate) struct OutboundLaneData {
pub(crate) oldest_unpruned_nonce: MessageNonce,
pub(crate) latest_received_nonce: MessageNonce,
pub(crate) latest_generated_nonce: MessageNonce,
}
}
/// This migration to `1` updates the metadata of `InboundLanes` and `OutboundLanes` to the new
/// structures.
pub mod v1 {
use super::*;
use crate::{
InboundLaneData, InboundLanes, OutboundLaneData, OutboundLanes, StoredInboundLaneData,
};
use bp_messages::LaneState;
use pezframe_support::traits::UncheckedOnRuntimeUpgrade;
use pezsp_std::marker::PhantomData;
/// Migrates the pezpallet storage to v1.
pub struct UncheckedMigrationV0ToV1<T, I>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> UncheckedOnRuntimeUpgrade for UncheckedMigrationV0ToV1<T, I> {
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
// `InboundLanes` - add state to the old structs
let translate_inbound =
|pre: v0::StoredInboundLaneData<T, I>| -> Option<v1::StoredInboundLaneData<T, I>> {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
Some(v1::StoredInboundLaneData(v1::InboundLaneData {
state: LaneState::Opened,
relayers: pre.0.relayers,
last_confirmed_nonce: pre.0.last_confirmed_nonce,
}))
};
InboundLanes::<T, I>::translate_values(translate_inbound);
// `OutboundLanes` - add state to the old structs
let translate_outbound = |pre: v0::OutboundLaneData| -> Option<v1::OutboundLaneData> {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
Some(v1::OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: pre.oldest_unpruned_nonce,
latest_received_nonce: pre.latest_received_nonce,
latest_generated_nonce: pre.latest_generated_nonce,
})
};
OutboundLanes::<T, I>::translate_values(translate_outbound);
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<pezsp_std::vec::Vec<u8>, pezsp_runtime::DispatchError> {
use codec::Encode;
let number_of_inbound_to_migrate = InboundLanes::<T, I>::iter_keys().count();
let number_of_outbound_to_migrate = OutboundLanes::<T, I>::iter_keys().count();
Ok((number_of_inbound_to_migrate as u32, number_of_outbound_to_migrate as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: pezsp_std::vec::Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
use codec::Decode;
const LOG_TARGET: &str = "runtime::bridge-messages-migration";
let (number_of_inbound_to_migrate, number_of_outbound_to_migrate): (u32, u32) =
Decode::decode(&mut &state[..]).unwrap();
let number_of_inbound = InboundLanes::<T, I>::iter_keys().count();
let number_of_outbound = OutboundLanes::<T, I>::iter_keys().count();
tracing::info!(target: LOG_TARGET, %number_of_inbound_to_migrate, "post-upgrade expects inbound lanes to have been migrated.");
tracing::info!(target: LOG_TARGET, %number_of_outbound_to_migrate, "post-upgrade expects outbound lanes to have been migrated.");
pezframe_support::ensure!(
number_of_inbound_to_migrate as usize == number_of_inbound,
"must migrate all `InboundLanes`."
);
pezframe_support::ensure!(
number_of_outbound_to_migrate as usize == number_of_outbound,
"must migrate all `OutboundLanes`."
);
tracing::info!(target: LOG_TARGET, "migrated all.");
Ok(())
}
}
/// [`UncheckedMigrationV0ToV1`] wrapped in a
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
/// migration is only performed when on-chain version is 0.
pub type MigrationToV1<T, I> = pezframe_support::migrations::VersionedMigration<
0,
1,
UncheckedMigrationV0ToV1<T, I>,
Pezpallet<T, I>,
<T as pezframe_system::Config>::DbWeight,
>;
}
@@ -0,0 +1,429 @@
// Copyright (C) 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/>.
//! Everything about outgoing messages sending.
use crate::{Config, LOG_TARGET};
use bp_messages::{
ChainWithMessages, DeliveredMessages, LaneState, MessageNonce, OutboundLaneData,
UnrewardedRelayer,
};
use codec::{Decode, DecodeWithMemTracking, Encode};
use pezframe_support::{traits::Get, BoundedVec, PalletError};
use scale_info::TypeInfo;
use pezsp_runtime::RuntimeDebug;
use pezsp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
/// Outbound lane storage.
pub trait OutboundLaneStorage {
/// Stored message payload type.
type StoredMessagePayload;
/// Lane identifier type.
type LaneId: Encode;
/// Lane id.
fn id(&self) -> Self::LaneId;
/// Get lane data from the storage.
fn data(&self) -> OutboundLaneData;
/// Update lane data in the storage.
fn set_data(&mut self, data: OutboundLaneData);
/// Returns saved outbound message payload.
#[cfg(test)]
fn message(&self, nonce: &MessageNonce) -> Option<Self::StoredMessagePayload>;
/// Save outbound message in the storage.
fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload);
/// Remove outbound message from the storage.
fn remove_message(&mut self, nonce: &MessageNonce);
/// Purge lane data from the storage.
fn purge(self);
}
/// Limit for the `StoredMessagePayload` vector.
pub struct StoredMessagePayloadLimit<T, I>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> Get<u32> for StoredMessagePayloadLimit<T, I> {
fn get() -> u32 {
T::BridgedChain::maximal_incoming_message_size()
}
}
/// Outbound message data wrapper that implements `MaxEncodedLen`.
pub type StoredMessagePayload<T, I> = BoundedVec<u8, StoredMessagePayloadLimit<T, I>>;
/// Result of messages receival confirmation.
#[derive(
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
)]
pub enum ReceptionConfirmationError {
/// Bridged chain is trying to confirm more messages than we have generated. May be a result
/// of invalid bridged chain storage.
FailedToConfirmFutureMessages,
/// The unrewarded relayers vec contains an empty entry. May be a result of invalid bridged
/// chain storage.
EmptyUnrewardedRelayerEntry,
/// The unrewarded relayers vec contains non-consecutive entries. May be a result of invalid
/// bridged chain storage.
NonConsecutiveUnrewardedRelayerEntries,
/// The chain has more messages that need to be confirmed than there is in the proof.
TryingToConfirmMoreMessagesThanExpected,
}
/// Outbound messages lane.
#[derive(Debug, PartialEq, Eq)]
pub struct OutboundLane<S> {
storage: S,
}
impl<S: OutboundLaneStorage> OutboundLane<S> {
/// Create new outbound lane backed by given storage.
pub fn new(storage: S) -> Self {
OutboundLane { storage }
}
/// Get this lane data.
pub fn data(&self) -> OutboundLaneData {
self.storage.data()
}
/// Get lane state.
pub fn state(&self) -> LaneState {
self.storage.data().state
}
/// Set lane state.
pub fn set_state(&mut self, state: LaneState) {
let mut data = self.storage.data();
data.state = state;
self.storage.set_data(data);
}
/// Return nonces of all currently queued messages.
pub fn queued_messages(&self) -> RangeInclusive<MessageNonce> {
let data = self.storage.data();
data.oldest_unpruned_nonce..=data.latest_generated_nonce
}
/// Send message over lane.
///
/// Returns new message nonce.
pub fn send_message(&mut self, message_payload: S::StoredMessagePayload) -> MessageNonce {
let mut data = self.storage.data();
let nonce = data.latest_generated_nonce + 1;
data.latest_generated_nonce = nonce;
self.storage.save_message(nonce, message_payload);
self.storage.set_data(data);
nonce
}
/// Confirm messages delivery.
pub fn confirm_delivery<RelayerId>(
&mut self,
max_allowed_messages: MessageNonce,
latest_delivered_nonce: MessageNonce,
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
) -> Result<Option<DeliveredMessages>, ReceptionConfirmationError> {
let mut data = self.storage.data();
let confirmed_messages = DeliveredMessages {
begin: data.latest_received_nonce.saturating_add(1),
end: latest_delivered_nonce,
};
if confirmed_messages.total_messages() == 0 {
return Ok(None);
}
if confirmed_messages.end > data.latest_generated_nonce {
return Err(ReceptionConfirmationError::FailedToConfirmFutureMessages);
}
if confirmed_messages.total_messages() > max_allowed_messages {
// that the relayer has declared correct number of messages that the proof contains (it
// is checked outside of the function). But it may happen (but only if this/bridged
// chain storage is corrupted, though) that the actual number of confirmed messages if
// larger than declared. This would mean that 'reward loop' will take more time than the
// weight formula accounts, so we can't allow that.
tracing::trace!(
target: LOG_TARGET,
confirmed=%confirmed_messages.total_messages(),
max_allowed=%max_allowed_messages,
"Messages delivery proof contains too many messages to confirm"
);
return Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected);
}
ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?;
// prune all confirmed messages
for nonce in confirmed_messages.begin..=confirmed_messages.end {
self.storage.remove_message(&nonce);
}
data.latest_received_nonce = confirmed_messages.end;
data.oldest_unpruned_nonce = data.latest_received_nonce.saturating_add(1);
self.storage.set_data(data);
Ok(Some(confirmed_messages))
}
/// Remove message from the storage. Doesn't perform any checks.
pub fn remove_oldest_unpruned_message(&mut self) {
let mut data = self.storage.data();
self.storage.remove_message(&data.oldest_unpruned_nonce);
data.oldest_unpruned_nonce += 1;
self.storage.set_data(data);
}
/// Purge lane state from the storage.
pub fn purge(self) {
self.storage.purge()
}
}
/// Verifies unrewarded relayers vec.
///
/// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged
/// chain has invalid runtime storage.
fn ensure_unrewarded_relayers_are_correct<RelayerId>(
latest_received_nonce: MessageNonce,
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
) -> Result<(), ReceptionConfirmationError> {
let mut expected_entry_begin = relayers.front().map(|entry| entry.messages.begin);
for entry in relayers {
// unrewarded relayer entry must have at least 1 unconfirmed message
// (guaranteed by the `InboundLane::receive_message()`)
if entry.messages.end < entry.messages.begin {
return Err(ReceptionConfirmationError::EmptyUnrewardedRelayerEntry);
}
// every entry must confirm range of messages that follows previous entry range
// (guaranteed by the `InboundLane::receive_message()`)
if expected_entry_begin != Some(entry.messages.begin) {
return Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries);
}
expected_entry_begin = entry.messages.end.checked_add(1);
// entry can't confirm messages larger than `inbound_lane_data.latest_received_nonce()`
// (guaranteed by the `InboundLane::receive_message()`)
if entry.messages.end > latest_received_nonce {
return Err(ReceptionConfirmationError::FailedToConfirmFutureMessages);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
active_outbound_lane,
tests::mock::{
outbound_message_data, run_test, test_lane_id, unrewarded_relayer, TestRelayer,
TestRuntime, REGULAR_PAYLOAD,
},
};
use pezsp_std::ops::RangeInclusive;
fn unrewarded_relayers(
nonces: RangeInclusive<MessageNonce>,
) -> VecDeque<UnrewardedRelayer<TestRelayer>> {
vec![unrewarded_relayer(*nonces.start(), *nonces.end(), 0)]
.into_iter()
.collect()
}
fn delivered_messages(nonces: RangeInclusive<MessageNonce>) -> DeliveredMessages {
DeliveredMessages { begin: *nonces.start(), end: *nonces.end() }
}
fn assert_3_messages_confirmation_fails(
latest_received_nonce: MessageNonce,
relayers: &VecDeque<UnrewardedRelayer<TestRelayer>>,
) -> Result<Option<DeliveredMessages>, ReceptionConfirmationError> {
run_test(|| {
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 0);
let result = lane.confirm_delivery(3, latest_received_nonce, relayers);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 0);
result
})
}
#[test]
fn send_message_works() {
run_test(|| {
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
assert_eq!(lane.storage.data().latest_generated_nonce, 0);
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
assert!(lane.storage.message(&1).is_some());
assert_eq!(lane.storage.data().latest_generated_nonce, 1);
});
}
#[test]
fn confirm_delivery_works() {
run_test(|| {
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2);
assert_eq!(lane.send_message(outbound_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.storage.data().oldest_unpruned_nonce, 1);
assert_eq!(
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
Ok(Some(delivered_messages(1..=3))),
);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
});
}
#[test]
fn confirm_partial_delivery_works() {
run_test(|| {
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2);
assert_eq!(lane.send_message(outbound_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.storage.data().oldest_unpruned_nonce, 1);
assert_eq!(
lane.confirm_delivery(3, 2, &unrewarded_relayers(1..=2)),
Ok(Some(delivered_messages(1..=2))),
);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 2);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3);
assert_eq!(
lane.confirm_delivery(3, 3, &unrewarded_relayers(3..=3)),
Ok(Some(delivered_messages(3..=3))),
);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
});
}
#[test]
fn confirm_delivery_rejects_nonce_lesser_than_latest_received() {
run_test(|| {
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
lane.send_message(outbound_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.storage.data().oldest_unpruned_nonce, 1);
assert_eq!(
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
Ok(Some(delivered_messages(1..=3))),
);
assert_eq!(lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), Ok(None),);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
assert_eq!(lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)), Ok(None),);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
});
}
#[test]
fn confirm_delivery_rejects_nonce_larger_than_last_generated() {
assert_eq!(
assert_3_messages_confirmation_fails(10, &unrewarded_relayers(1..=10),),
Err(ReceptionConfirmationError::FailedToConfirmFutureMessages),
);
}
#[test]
fn confirm_delivery_fails_if_entry_confirms_future_messages() {
assert_eq!(
assert_3_messages_confirmation_fails(
3,
&unrewarded_relayers(1..=1)
.into_iter()
.chain(unrewarded_relayers(2..=30))
.chain(unrewarded_relayers(3..=3))
.collect(),
),
Err(ReceptionConfirmationError::FailedToConfirmFutureMessages),
);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn confirm_delivery_fails_if_entry_is_empty() {
assert_eq!(
assert_3_messages_confirmation_fails(
3,
&unrewarded_relayers(1..=1)
.into_iter()
.chain(unrewarded_relayers(2..=1))
.chain(unrewarded_relayers(2..=3))
.collect(),
),
Err(ReceptionConfirmationError::EmptyUnrewardedRelayerEntry),
);
}
#[test]
fn confirm_delivery_fails_if_entries_are_non_consecutive() {
assert_eq!(
assert_3_messages_confirmation_fails(
3,
&unrewarded_relayers(1..=1)
.into_iter()
.chain(unrewarded_relayers(3..=3))
.chain(unrewarded_relayers(2..=2))
.collect(),
),
Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries),
);
}
#[test]
fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() {
run_test(|| {
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
assert_eq!(
lane.confirm_delivery(0, 3, &unrewarded_relayers(1..=3)),
Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected),
);
assert_eq!(
lane.confirm_delivery(2, 3, &unrewarded_relayers(1..=3)),
Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected),
);
assert_eq!(
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
Ok(Some(delivered_messages(1..=3))),
);
});
}
}
+561
View File
@@ -0,0 +1,561 @@
// 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/>.
//! Tools for messages and delivery proof verification.
use crate::{BridgedChainOf, BridgedHeaderChainOf, Config};
use bp_header_pez_chain::{HeaderChain, HeaderChainError};
use bp_messages::{
source_chain::FromBridgedChainMessagesDeliveryProof,
target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages},
ChainWithMessages, InboundLaneData, Message, MessageKey, MessageNonce, MessagePayload,
OutboundLaneData, VerificationError,
};
use pezbp_runtime::{
HashOf, HasherOf, RangeInclusiveExt, RawStorageProof, StorageProofChecker, StorageProofError,
};
use codec::Decode;
use pezsp_std::vec::Vec;
/// 'Parsed' message delivery proof - inbound lane id and its state.
pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain<T, I> =
(<T as Config<I>>::LaneId, InboundLaneData<<T as pezframe_system::Config>::AccountId>);
/// Verify proof of Bridged -> This chain messages.
///
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
/// teyrchains, please use the `verify_messages_proof_from_teyrchain`.
///
/// The `messages_count` argument verification (sane limits) is supposed to be made
/// outside of this function. This function only verifies that the proof declares exactly
/// `messages_count` messages.
pub fn verify_messages_proof<T: Config<I>, I: 'static>(
proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
messages_count: u32,
) -> Result<ProvedMessages<T::LaneId, Message<T::LaneId>>, VerificationError> {
let FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
} = proof;
let mut parser: MessagesStorageProofAdapter<T, I> =
MessagesStorageProofAdapter::try_new_with_verified_storage_proof(
bridged_header_hash,
storage_proof,
)
.map_err(VerificationError::HeaderChain)?;
let nonces_range = nonces_start..=nonces_end;
// receiving proofs where end < begin is ok (if proof includes outbound lane state)
let messages_in_the_proof = nonces_range.saturating_len();
if messages_in_the_proof != MessageNonce::from(messages_count) {
return Err(VerificationError::MessagesCountMismatch);
}
// Read messages first. All messages that are claimed to be in the proof must
// be in the proof. So any error in `read_value`, or even missing value is fatal.
//
// Mind that we allow proofs with no messages if outbound lane state is proved.
let mut messages = Vec::with_capacity(messages_in_the_proof as _);
for nonce in nonces_range {
let message_key = MessageKey { lane_id: lane, nonce };
let message_payload = parser
.read_and_decode_message_payload(&message_key)
.map_err(VerificationError::MessageStorage)?;
messages.push(Message { key: message_key, payload: message_payload });
}
// Now let's check if proof contains outbound lane state proof. It is optional, so
// we simply ignore `read_value` errors and missing value.
let proved_lane_messages = ProvedLaneMessages {
lane_state: parser
.read_and_decode_outbound_lane_data(&lane)
.map_err(VerificationError::OutboundLaneStorage)?,
messages,
};
// Now we may actually check if the proof is empty or not.
if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
return Err(VerificationError::EmptyMessageProof);
}
// Check that the storage proof doesn't have any untouched keys.
parser.ensure_no_unused_keys().map_err(VerificationError::StorageProof)?;
Ok((lane, proved_lane_messages))
}
/// Verify proof of This -> Bridged chain messages delivery.
pub fn verify_messages_delivery_proof<T: Config<I>, I: 'static>(
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<T, I>, VerificationError> {
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof;
let mut parser: MessagesStorageProofAdapter<T, I> =
MessagesStorageProofAdapter::try_new_with_verified_storage_proof(
bridged_header_hash,
storage_proof,
)
.map_err(VerificationError::HeaderChain)?;
// Messages delivery proof is just proof of single storage key read => any error
// is fatal.
let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&lane,
);
let inbound_lane_data = parser
.read_and_decode_mandatory_value(&storage_inbound_lane_data_key)
.map_err(VerificationError::InboundLaneStorage)?;
// check that the storage proof doesn't have any untouched trie nodes
parser.ensure_no_unused_keys().map_err(VerificationError::StorageProof)?;
Ok((lane, inbound_lane_data))
}
/// Abstraction over storage proof manipulation, hiding implementation details of actual storage
/// proofs.
trait StorageProofAdapter<T: Config<I>, I: 'static> {
fn read_and_decode_mandatory_value<D: Decode>(
&mut self,
key: &impl AsRef<[u8]>,
) -> Result<D, StorageProofError>;
fn read_and_decode_optional_value<D: Decode>(
&mut self,
key: &impl AsRef<[u8]>,
) -> Result<Option<D>, StorageProofError>;
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError>;
fn read_and_decode_outbound_lane_data(
&mut self,
lane_id: &T::LaneId,
) -> Result<Option<OutboundLaneData>, StorageProofError> {
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
lane_id,
);
self.read_and_decode_optional_value(&storage_outbound_lane_data_key)
}
fn read_and_decode_message_payload(
&mut self,
message_key: &MessageKey<T::LaneId>,
) -> Result<MessagePayload, StorageProofError> {
let storage_message_key = bp_messages::storage_keys::message_key(
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&message_key.lane_id,
message_key.nonce,
);
self.read_and_decode_mandatory_value(&storage_message_key)
}
}
/// Actual storage proof adapter for messages proofs.
type MessagesStorageProofAdapter<T, I> = StorageProofCheckerAdapter<T, I>;
/// A `StorageProofAdapter` implementation for raw storage proofs.
struct StorageProofCheckerAdapter<T: Config<I>, I: 'static> {
storage: StorageProofChecker<HasherOf<BridgedChainOf<T, I>>>,
_dummy: pezsp_std::marker::PhantomData<(T, I)>,
}
impl<T: Config<I>, I: 'static> StorageProofCheckerAdapter<T, I> {
fn try_new_with_verified_storage_proof(
bridged_header_hash: HashOf<BridgedChainOf<T, I>>,
storage_proof: RawStorageProof,
) -> Result<Self, HeaderChainError> {
BridgedHeaderChainOf::<T, I>::verify_storage_proof(bridged_header_hash, storage_proof).map(
|storage| StorageProofCheckerAdapter::<T, I> { storage, _dummy: Default::default() },
)
}
}
impl<T: Config<I>, I: 'static> StorageProofAdapter<T, I> for StorageProofCheckerAdapter<T, I> {
fn read_and_decode_optional_value<D: Decode>(
&mut self,
key: &impl AsRef<[u8]>,
) -> Result<Option<D>, StorageProofError> {
self.storage.read_and_decode_opt_value(key.as_ref())
}
fn read_and_decode_mandatory_value<D: Decode>(
&mut self,
key: &impl AsRef<[u8]>,
) -> Result<D, StorageProofError> {
self.storage.read_and_decode_mandatory_value(key.as_ref())
}
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError> {
self.storage.ensure_no_unused_nodes()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{
messages_generation::{
encode_all_messages, encode_lane_data, generate_dummy_message,
prepare_messages_storage_proof,
},
mock::*,
};
use bp_header_pez_chain::{HeaderChainError, StoredHeaderDataBuilder};
use bp_messages::LaneState;
use pezbp_runtime::{HeaderId, StorageProofError};
use codec::Encode;
use pezsp_runtime::traits::Header;
fn using_messages_proof<R>(
nonces_end: MessageNonce,
outbound_lane_data: Option<OutboundLaneData>,
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
add_duplicate_key: bool,
add_unused_key: bool,
test: impl Fn(FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>) -> R,
) -> R {
let (state_root, storage_proof) =
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
test_lane_id(),
1..=nonces_end,
outbound_lane_data,
pezbp_runtime::UnverifiedStorageProofParams::default(),
generate_dummy_message,
encode_message,
encode_outbound_lane_data,
add_duplicate_key,
add_unused_key,
);
pezsp_io::TestExternalities::new(Default::default()).execute_with(move || {
let bridged_header = BridgedChainHeader::new(
0,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
0,
bridged_header_hash,
));
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
bridged_header.build(),
);
test(FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane: test_lane_id(),
nonces_start: 1,
nonces_end,
})
})
}
#[test]
fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() {
assert_eq!(
using_messages_proof(
10,
None,
encode_all_messages,
encode_lane_data,
false,
false,
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 5) }
),
Err(VerificationError::MessagesCountMismatch),
);
}
#[test]
fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
assert_eq!(
using_messages_proof(
10,
None,
encode_all_messages,
encode_lane_data,
false,
false,
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 15) }
),
Err(VerificationError::MessagesCountMismatch),
);
}
#[test]
fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
assert_eq!(
using_messages_proof(
10,
None,
encode_all_messages,
encode_lane_data,
false,
false,
|proof| {
let bridged_header_hash =
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(
bridged_header_hash,
);
verify_messages_proof::<TestRuntime, ()>(proof, 10)
}
),
Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)),
);
}
#[test]
fn message_proof_is_rejected_if_header_state_root_mismatches() {
assert_eq!(
using_messages_proof(
10,
None,
encode_all_messages,
encode_lane_data,
false,
false,
|proof| {
let bridged_header_hash =
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
BridgedChainHeader::new(
0,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.build(),
);
verify_messages_proof::<TestRuntime, ()>(proof, 10)
}
),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::StorageRootMismatch
))),
);
}
#[test]
fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() {
assert_eq!(
using_messages_proof(
10,
None,
encode_all_messages,
encode_lane_data,
true,
false,
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 10) },
),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::DuplicateNodes
))),
);
}
#[test]
fn message_proof_is_rejected_if_it_has_unused_trie_nodes() {
assert_eq!(
using_messages_proof(
10,
None,
encode_all_messages,
encode_lane_data,
false,
true,
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 10) },
),
Err(VerificationError::StorageProof(StorageProofError::UnusedKey)),
);
}
#[test]
fn message_proof_is_rejected_if_required_message_is_missing() {
matches!(
using_messages_proof(
10,
None,
|n, m| if n != 5 { Some(m.encode()) } else { None },
encode_lane_data,
false,
false,
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10)
),
Err(VerificationError::MessageStorage(StorageProofError::EmptyVal)),
);
}
#[test]
fn message_proof_is_rejected_if_message_decode_fails() {
matches!(
using_messages_proof(
10,
None,
|n, m| {
let mut m = m.encode();
if n == 5 {
m = vec![42]
}
Some(m)
},
encode_lane_data,
false,
false,
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10),
),
Err(VerificationError::MessageStorage(StorageProofError::DecodeError)),
);
}
#[test]
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
matches!(
using_messages_proof(
10,
Some(OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
|d| {
let mut d = d.encode();
d.truncate(1);
d
},
false,
false,
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10),
),
Err(VerificationError::OutboundLaneStorage(StorageProofError::DecodeError)),
);
}
#[test]
fn message_proof_is_rejected_if_it_is_empty() {
assert_eq!(
using_messages_proof(
0,
None,
encode_all_messages,
encode_lane_data,
false,
false,
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 0) },
),
Err(VerificationError::EmptyMessageProof),
);
}
#[test]
fn non_empty_message_proof_without_messages_is_accepted() {
assert_eq!(
using_messages_proof(
0,
Some(OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
false,
false,
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 0),
),
Ok((
test_lane_id(),
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: Vec::new(),
},
)),
);
}
#[test]
fn non_empty_message_proof_is_accepted() {
assert_eq!(
using_messages_proof(
1,
Some(OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
false,
false,
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 1),
),
Ok((
test_lane_id(),
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: vec![Message {
key: MessageKey { lane_id: test_lane_id(), nonce: 1 },
payload: vec![42],
}],
},
))
);
}
#[test]
fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() {
assert_eq!(
using_messages_proof(
1,
None,
encode_all_messages,
encode_lane_data,
false,
false,
|mut proof| {
proof.nonces_end = u64::MAX;
verify_messages_proof::<TestRuntime, ()>(proof, u32::MAX)
},
),
Err(VerificationError::MessagesCountMismatch),
);
}
}
@@ -0,0 +1,171 @@
// Copyright (C) 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/>.
//! Helpers for generating message storage proofs, that are used by tests and by benchmarks.
use bp_messages::{
storage_keys, ChainWithMessages, InboundLaneData, MessageKey, MessageNonce, MessagePayload,
OutboundLaneData,
};
use pezbp_runtime::{
grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf,
RawStorageProof, UnverifiedStorageProofParams,
};
use codec::Encode;
use pezsp_std::{ops::RangeInclusive, prelude::*};
use pezsp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
/// Dummy message generation function.
pub fn generate_dummy_message(_: MessageNonce) -> MessagePayload {
vec![42]
}
/// Simple and correct message data encode function.
pub fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option<Vec<u8>> {
Some(m.encode())
}
/// Simple and correct outbound lane data encode function.
pub fn encode_lane_data(d: &OutboundLaneData) -> Vec<u8> {
d.encode()
}
/// Prepare storage proof of given messages.
///
/// Returns state trie root and nodes with prepared messages.
#[allow(clippy::too_many_arguments)]
pub fn prepare_messages_storage_proof<
BridgedChain: Chain,
ThisChain: ChainWithMessages,
LaneId: Encode + Copy,
>(
lane: LaneId,
message_nonces: RangeInclusive<MessageNonce>,
outbound_lane_data: Option<OutboundLaneData>,
proof_params: UnverifiedStorageProofParams,
generate_message: impl Fn(MessageNonce) -> MessagePayload,
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
add_duplicate_key: bool,
add_unused_key: bool,
) -> (HashOf<BridgedChain>, RawStorageProof)
where
HashOf<BridgedChain>: Copy + Default,
{
// prepare Bridged chain storage with messages and (optionally) outbound lane state
let message_count = message_nonces.end().saturating_sub(*message_nonces.start()) + 1;
let mut storage_keys = Vec::with_capacity(message_count as usize + 1);
let mut root = Default::default();
let mut mdb = MemoryDB::default();
{
let mut trie =
TrieDBMutBuilderV1::<HasherOf<BridgedChain>>::new(&mut mdb, &mut root).build();
// insert messages
for (i, nonce) in message_nonces.into_iter().enumerate() {
let message_key = MessageKey { lane_id: lane, nonce };
let message_payload = match encode_message(nonce, &generate_message(nonce)) {
Some(message_payload) =>
if i == 0 {
grow_storage_value(message_payload, &proof_params)
} else {
message_payload
},
None => continue,
};
let storage_key = storage_keys::message_key(
ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&message_key.lane_id,
message_key.nonce,
)
.0;
trie.insert(&storage_key, &message_payload)
.map_err(|_| "TrieMut::insert has failed")
.expect("TrieMut::insert should not fail in benchmarks");
storage_keys.push(storage_key);
}
// insert outbound lane state
if let Some(outbound_lane_data) = outbound_lane_data.as_ref().map(encode_outbound_lane_data)
{
let storage_key = storage_keys::outbound_lane_data_key(
ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&lane,
)
.0;
trie.insert(&storage_key, &outbound_lane_data)
.map_err(|_| "TrieMut::insert has failed")
.expect("TrieMut::insert should not fail in benchmarks");
storage_keys.push(storage_key);
}
}
// generate storage proof to be delivered to This chain
let mut storage_proof =
record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain>>, _>(&mdb, &root)
.map_err(|_| "record_all_trie_keys has failed")
.expect("record_all_trie_keys should not fail in benchmarks");
if add_duplicate_key {
assert!(!storage_proof.is_empty());
let node = storage_proof.pop().unwrap();
storage_proof.push(node.clone());
storage_proof.push(node);
}
if add_unused_key {
storage_proof.push(b"unused_value".to_vec());
}
(root, storage_proof)
}
/// Prepare storage proof of given messages delivery.
///
/// Returns state trie root and nodes with prepared messages.
pub fn prepare_message_delivery_storage_proof<
BridgedChain: Chain,
ThisChain: ChainWithMessages,
LaneId: Encode,
>(
lane: LaneId,
inbound_lane_data: InboundLaneData<AccountIdOf<ThisChain>>,
proof_params: UnverifiedStorageProofParams,
) -> (HashOf<BridgedChain>, RawStorageProof)
where
HashOf<BridgedChain>: Copy + Default,
{
// prepare Bridged chain storage with inbound lane state
let storage_key =
storage_keys::inbound_lane_data_key(ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, &lane).0;
let mut root = Default::default();
let mut mdb = MemoryDB::default();
{
let mut trie =
TrieDBMutBuilderV1::<HasherOf<BridgedChain>>::new(&mut mdb, &mut root).build();
let inbound_lane_data = grow_storage_value(inbound_lane_data.encode(), &proof_params);
trie.insert(&storage_key, &inbound_lane_data)
.map_err(|_| "TrieMut::insert has failed")
.expect("TrieMut::insert should not fail in benchmarks");
}
// generate storage proof to be delivered to This chain
let storage_proof = record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain>>, _>(&mdb, &root)
.map_err(|_| "record_all_trie_keys has failed")
.expect("record_all_trie_keys should not fail in benchmarks");
(root, storage_proof)
}
@@ -0,0 +1,561 @@
// Copyright (C) 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/>.
// From construct_runtime macro
#![allow(clippy::from_over_into)]
use crate::{
tests::messages_generation::{
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
prepare_messages_storage_proof,
},
Config, StoredMessagePayload,
};
use bp_header_pez_chain::{ChainWithGrandpa, StoredHeaderData};
use bp_messages::{
calc_relayers_rewards,
source_chain::{
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
},
target_chain::{
DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof,
MessageDispatch,
},
ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState,
Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
};
use pezbp_runtime::{
messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams,
};
use codec::{Decode, DecodeWithMemTracking, Encode};
use pezframe_support::{
derive_impl,
weights::{constants::RocksDbWeight, Weight},
};
use scale_info::TypeInfo;
use pezsp_core::H256;
use pezsp_runtime::{
testing::Header as BizinikiwiHeader,
traits::{BlakeTwo256, ConstU32},
BuildStorage, StateVersion,
};
use std::{collections::VecDeque, ops::RangeInclusive};
pub type AccountId = u64;
pub type Balance = u64;
#[derive(Decode, DecodeWithMemTracking, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
pub struct TestPayload {
/// Field that may be used to identify messages.
pub id: u64,
/// Dispatch weight that is declared by the message sender.
pub declared_weight: Weight,
/// Message dispatch result.
///
/// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`,
/// but for test purposes we'll be making it larger than `declared_weight` sometimes.
pub dispatch_result: MessageDispatchResult<TestDispatchLevelResult>,
/// Extra bytes that affect payload size.
pub extra: Vec<u8>,
}
pub type TestMessageFee = u64;
pub type TestRelayer = u64;
pub type TestDispatchLevelResult = ();
pub struct ThisChain;
impl Chain for ThisChain {
const ID: ChainId = *b"ttch";
type BlockNumber = u64;
type Hash = H256;
type Hasher = BlakeTwo256;
type Header = BizinikiwiHeader;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = u64;
type Signature = pezsp_runtime::MultiSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
u32::MAX
}
fn max_extrinsic_weight() -> Weight {
Weight::MAX
}
}
impl ChainWithMessages for ThisChain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
}
pub struct BridgedChain;
pub type BridgedHeaderHash = H256;
pub type BridgedChainHeader = BizinikiwiHeader;
impl Chain for BridgedChain {
const ID: ChainId = *b"tbch";
type BlockNumber = u64;
type Hash = BridgedHeaderHash;
type Hasher = BlakeTwo256;
type Header = BridgedChainHeader;
type AccountId = TestRelayer;
type Balance = Balance;
type Nonce = u64;
type Signature = pezsp_runtime::MultiSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
4096
}
fn max_extrinsic_weight() -> Weight {
Weight::MAX
}
}
impl ChainWithGrandpa for BridgedChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "WithBridgedChainBridgeGrandpa";
const MAX_AUTHORITIES_COUNT: u32 = 16;
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 4;
const MAX_MANDATORY_HEADER_SIZE: u32 = 4096;
const AVERAGE_HEADER_SIZE: u32 = 4096;
}
impl ChainWithMessages for BridgedChain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
}
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
use crate as pezpallet_bridge_messages;
pezframe_support::construct_runtime! {
pub enum TestRuntime
{
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
Balances: pezpallet_balances::{Pezpallet, Call, Event<T>},
BridgedChainGrandpa: pezpallet_bridge_grandpa::{Pezpallet, Call, Event<T>},
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
}
}
pub type DbWeight = RocksDbWeight;
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for TestRuntime {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<Balance>;
type DbWeight = DbWeight;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for TestRuntime {
type AccountStore = System;
}
impl pezpallet_bridge_grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = BridgedChain;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<1_024>;
type HeadersToKeep = ConstU32<8>;
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
/// weights of messages pezpallet calls we use in tests.
pub type TestWeightInfo = ();
impl Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = TestWeightInfo;
type ThisChain = ThisChain;
type BridgedChain = BridgedChain;
type BridgedHeaderChain = BridgedChainGrandpa;
type OutboundPayload = TestPayload;
type InboundPayload = TestPayload;
type LaneId = TestLaneIdType;
type DeliveryPayments = TestDeliveryPayments;
type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments;
type OnMessagesDelivered = TestOnMessagesDelivered;
type MessageDispatch = TestMessageDispatch;
}
#[cfg(feature = "runtime-benchmarks")]
impl crate::benchmarking::Config<()> for TestRuntime {
fn bench_lane_id() -> Self::LaneId {
test_lane_id()
}
fn prepare_message_proof(
params: crate::benchmarking::MessageProofParams<Self::LaneId>,
) -> (FromBridgedChainMessagesProof<BridgedHeaderHash, Self::LaneId>, Weight) {
use pezbp_runtime::RangeInclusiveExt;
let dispatch_weight =
REGULAR_PAYLOAD.declared_weight * params.message_nonces.saturating_len();
(
*prepare_messages_proof(
params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).collect(),
params.outbound_lane_data,
),
dispatch_weight,
)
}
fn prepare_message_delivery_proof(
params: crate::benchmarking::MessageDeliveryProofParams<AccountId, Self::LaneId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, Self::LaneId> {
// in mock run we only care about benchmarks correctness, not the benchmark results
// => ignore size related arguments
prepare_messages_delivery_proof(params.lane, params.inbound_lane_data)
}
fn is_relayer_rewarded(_relayer: &AccountId) -> bool {
true
}
}
impl Size for TestPayload {
fn size(&self) -> u32 {
16 + self.extra.len() as u32
}
}
/// Account that has balance to use in tests.
pub const ENDOWED_ACCOUNT: AccountId = 0xDEAD;
/// Account id of test relayer.
pub const TEST_RELAYER_A: AccountId = 100;
/// Account id of additional test relayer - B.
pub const TEST_RELAYER_B: AccountId = 101;
/// Account id of additional test relayer - C.
pub const TEST_RELAYER_C: AccountId = 102;
/// Lane identifier type used for tests.
pub type TestLaneIdType = HashedLaneId;
/// Lane that we're using in tests.
pub fn test_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 2).unwrap()
}
/// Lane that is completely unknown to our runtime.
pub fn unknown_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 3).unwrap()
}
/// Lane that is registered, but it is closed.
pub fn closed_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 4).unwrap()
}
/// Regular message payload.
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
/// Reward payments at the target chain during delivery transaction.
#[derive(Debug, Default)]
pub struct TestDeliveryPayments;
impl TestDeliveryPayments {
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
/// cleared after the call.
pub fn is_reward_paid(relayer: AccountId) -> bool {
let key = (b":delivery-relayer-reward:", relayer).encode();
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl DeliveryPayments<AccountId> for TestDeliveryPayments {
type Error = &'static str;
fn pay_reward(
relayer: AccountId,
_total_messages: MessageNonce,
_valid_messages: MessageNonce,
_actual_weight: Weight,
) {
let key = (b":delivery-relayer-reward:", relayer).encode();
pezframe_support::storage::unhashed::put(&key, &true);
}
}
/// Reward payments at the source chain during delivery confirmation transaction.
#[derive(Debug, Default)]
pub struct TestDeliveryConfirmationPayments;
impl TestDeliveryConfirmationPayments {
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
/// cleared after the call.
pub fn is_reward_paid(relayer: AccountId, fee: TestMessageFee) -> bool {
let key = (b":relayer-reward:", relayer, fee).encode();
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl DeliveryConfirmationPayments<AccountId, TestLaneIdType> for TestDeliveryConfirmationPayments {
type Error = &'static str;
fn pay_reward(
_lane_id: TestLaneIdType,
pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
_confirmation_relayer: &AccountId,
received_range: &RangeInclusive<MessageNonce>,
) -> MessageNonce {
let relayers_rewards = calc_relayers_rewards(pez_messages_relayers, received_range);
let rewarded_relayers = relayers_rewards.len();
for (relayer, reward) in &relayers_rewards {
let key = (b":relayer-reward:", relayer, reward).encode();
pezframe_support::storage::unhashed::put(&key, &true);
}
rewarded_relayers as _
}
}
/// Test message dispatcher.
#[derive(Debug)]
pub struct TestMessageDispatch;
impl TestMessageDispatch {
pub fn deactivate(lane: TestLaneIdType) {
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1;
for _ in 1..=latest_received_nonce {
Self::emulate_enqueued_message(lane);
}
}
pub fn emulate_enqueued_message(lane: TestLaneIdType) {
let key = (b"dispatched", lane).encode();
let dispatched = pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(&key[..]);
pezframe_support::storage::unhashed::put(&key[..], &(dispatched + 1));
}
}
impl MessageDispatch for TestMessageDispatch {
type DispatchPayload = TestPayload;
type DispatchLevelResult = TestDispatchLevelResult;
type LaneId = TestLaneIdType;
fn is_active(lane: Self::LaneId) -> bool {
pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(
&(b"dispatched", lane).encode()[..],
) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
}
fn dispatch_weight(message: &mut DispatchMessage<TestPayload, Self::LaneId>) -> Weight {
match message.data.payload.as_ref() {
Ok(payload) => payload.declared_weight,
Err(_) => Weight::zero(),
}
}
fn dispatch(
message: DispatchMessage<TestPayload, Self::LaneId>,
) -> MessageDispatchResult<TestDispatchLevelResult> {
match message.data.payload.as_ref() {
Ok(payload) => {
Self::emulate_enqueued_message(message.key.lane_id);
payload.dispatch_result.clone()
},
Err(_) => dispatch_result(0),
}
}
}
/// Test callback, called during message delivery confirmation transaction.
pub struct TestOnMessagesDelivered;
impl TestOnMessagesDelivered {
pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> {
pezframe_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
}
}
impl OnMessagesDelivered<TestLaneIdType> for TestOnMessagesDelivered {
fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) {
pezframe_support::storage::unhashed::put(
b"TestOnMessagesDelivered.OnMessagesDelivered",
&(lane, enqueued_messages),
);
}
}
/// Return test lane message with given nonce and payload.
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message<TestLaneIdType> {
Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() }
}
/// Return valid outbound message data, constructed from given payload.
pub fn outbound_message_data(payload: TestPayload) -> StoredMessagePayload<TestRuntime, ()> {
StoredMessagePayload::<TestRuntime, ()>::try_from(payload.encode()).expect("payload too large")
}
/// Return valid inbound (dispatch) message data, constructed from given payload.
pub fn inbound_message_data(payload: TestPayload) -> DispatchMessageData<TestPayload> {
DispatchMessageData { payload: Ok(payload) }
}
/// Constructs message payload using given arguments and zero unspent weight.
pub const fn message_payload(id: u64, declared_weight: u64) -> TestPayload {
TestPayload {
id,
declared_weight: Weight::from_parts(declared_weight, 0),
dispatch_result: dispatch_result(0),
extra: Vec::new(),
}
}
/// Returns message dispatch result with given unspent weight.
pub const fn dispatch_result(
unspent_weight: u64,
) -> MessageDispatchResult<TestDispatchLevelResult> {
MessageDispatchResult {
unspent_weight: Weight::from_parts(unspent_weight, 0),
dispatch_level_result: (),
}
}
/// Constructs unrewarded relayer entry from nonces range and relayer id.
pub fn unrewarded_relayer(
begin: MessageNonce,
end: MessageNonce,
relayer: TestRelayer,
) -> UnrewardedRelayer<TestRelayer> {
UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } }
}
/// Returns unrewarded relayers state at given lane.
pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState {
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).unwrap().0;
UnrewardedRelayersState::from(&inbound_lane_data)
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
pezpallet_balances::GenesisConfig::<TestRuntime> {
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
pezsp_io::TestExternalities::new(t)
}
/// Run pezpallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(|| {
crate::InboundLanes::<TestRuntime, ()>::insert(test_lane_id(), InboundLaneData::opened());
crate::OutboundLanes::<TestRuntime, ()>::insert(test_lane_id(), OutboundLaneData::opened());
crate::InboundLanes::<TestRuntime, ()>::insert(
closed_lane_id(),
InboundLaneData { state: LaneState::Closed, ..Default::default() },
);
crate::OutboundLanes::<TestRuntime, ()>::insert(
closed_lane_id(),
OutboundLaneData { state: LaneState::Closed, ..Default::default() },
);
test()
})
}
/// Prepare valid storage proof for given messages and insert appropriate header to the
/// bridged header chain.
///
/// Since this function changes the runtime storage, you can't "inline" it in the
/// `asset_noop` macro calls.
pub fn prepare_messages_proof(
messages: Vec<Message<TestLaneIdType>>,
outbound_lane_data: Option<OutboundLaneData>,
) -> Box<FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>> {
// first - let's generate storage proof
let lane = messages.first().unwrap().key.lane_id;
let nonces_start = messages.first().unwrap().key.nonce;
let nonces_end = messages.last().unwrap().key.nonce;
let (storage_root, storage_proof) =
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
lane,
nonces_start..=nonces_end,
outbound_lane_data,
UnverifiedStorageProofParams::default(),
|nonce| messages[(nonce - nonces_start) as usize].payload.clone(),
encode_all_messages,
encode_lane_data,
false,
false,
);
// let's now insert bridged chain header into the storage
let bridged_header_hash = Default::default();
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
StoredHeaderData { number: 0, state_root: storage_root },
);
Box::new(FromBridgedChainMessagesProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
})
}
/// Prepare valid storage proof for given messages and insert appropriate header to the
/// bridged header chain.
///
/// Since this function changes the runtime storage, you can't "inline" it in the
/// `asset_noop` macro calls.
pub fn prepare_messages_delivery_proof(
lane: TestLaneIdType,
inbound_lane_data: InboundLaneData<AccountId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType> {
// first - let's generate storage proof
let (storage_root, storage_proof) =
prepare_message_delivery_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
lane,
inbound_lane_data,
UnverifiedStorageProofParams::default(),
);
// let's now insert bridged chain header into the storage
let bridged_header_hash = Default::default();
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
StoredHeaderData { number: 0, state_root: storage_root },
);
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
}
}
@@ -0,0 +1,26 @@
// 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/>.
//! Tests and test helpers for messages pezpallet.
#![cfg(any(feature = "test-helpers", test))]
#[cfg(test)]
pub(crate) mod mock;
#[cfg(test)]
mod pezpallet_tests;
pub mod messages_generation;
File diff suppressed because it is too large Load Diff
+530
View File
@@ -0,0 +1,530 @@
// Copyright (C) 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/>.
//! Autogenerated weights for pezpallet_bridge_messages
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-06-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `serban-ROG-Zephyrus`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// target/release/unknown-bridge-node
// benchmark
// pezpallet
// --chain=dev
// --steps=50
// --repeat=20
// --pezpallet=pezpallet_bridge_messages
// --extrinsic=*
// --execution=wasm
// --wasm-execution=Compiled
// --heap-pages=4096
// --output=./modules/messages/src/weights.rs
// --template=./.maintain/bridge-weight-template.hbs
#![allow(clippy::all)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use pezframe_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use pezsp_std::marker::PhantomData;
/// Weight functions needed for pezpallet_bridge_messages.
pub trait WeightInfo {
fn receive_single_message_proof() -> Weight;
fn receive_n_messages_proof(n: u32) -> Weight;
fn receive_single_message_proof_with_outbound_lane_state() -> Weight;
fn receive_single_n_bytes_message_proof(n: u32) -> Weight;
fn receive_delivery_proof_for_single_message() -> Weight;
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight;
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight;
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight;
}
/// Weights for `pezpallet_bridge_messages` that are generated using one of the Bridge testnets.
///
/// Those weights are test only and must never be used in production.
pub struct BridgeWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
fn receive_single_message_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 38_724 nanoseconds.
Weight::from_parts(40_650_000, 52673)
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
/// 51683, mode: MaxEncodedLen)
///
/// The range of component `n` is `[1, 1004]`.
///
/// The range of component `n` is `[1, 1004]`.
fn receive_n_messages_proof(n: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 39_354 nanoseconds.
Weight::from_parts(29_708_543, 52673)
// Standard Error: 1_185
.saturating_add(Weight::from_parts(7_648_787, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 45_578 nanoseconds.
Weight::from_parts(47_161_000, 52673)
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
///
/// The range of component `n` is `[1, 16384]`.
fn receive_single_n_bytes_message_proof(n: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 38_702 nanoseconds.
Weight::from_parts(41_040_143, 52673)
// Standard Error: 5
.saturating_add(Weight::from_parts(1_174, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
/// 539, mode: MaxEncodedLen)
///
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
/// mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:1)
///
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
/// added: 68071, mode: MaxEncodedLen)
fn receive_delivery_proof_for_single_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `701`
// Estimated: `3558`
// Minimum execution time: 37_197 nanoseconds.
Weight::from_parts(38_371_000, 3558)
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
/// 539, mode: MaxEncodedLen)
///
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
/// mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
///
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
/// added: 68071, mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
// Proof Size summary in bytes:
// Measured: `701`
// Estimated: `3558`
// Minimum execution time: 38_684 nanoseconds.
Weight::from_parts(39_929_000, 3558)
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(4_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
/// 539, mode: MaxEncodedLen)
///
/// Storage: BridgeRelayers RelayerRewards (r:2 w:2)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
/// mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
///
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
/// added: 68071, mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
// Proof Size summary in bytes:
// Measured: `701`
// Estimated: `6126`
// Minimum execution time: 41_363 nanoseconds.
Weight::from_parts(42_621_000, 6126)
.saturating_add(T::DbWeight::get().reads(5_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
///
/// The range of component `n` is `[1, 16384]`.
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 38_925 nanoseconds.
Weight::from_parts(39_617_000, 52673)
// Standard Error: 612
.saturating_add(Weight::from_parts(372_813, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
fn receive_single_message_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 38_724 nanoseconds.
Weight::from_parts(40_650_000, 52673)
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
/// 51683, mode: MaxEncodedLen)
///
/// The range of component `n` is `[1, 1004]`.
///
/// The range of component `n` is `[1, 1004]`.
fn receive_n_messages_proof(n: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 39_354 nanoseconds.
Weight::from_parts(29_708_543, 52673)
// Standard Error: 1_185
.saturating_add(Weight::from_parts(7_648_787, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 45_578 nanoseconds.
Weight::from_parts(47_161_000, 52673)
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
/// 51683, mode: MaxEncodedLen)
///
/// The range of component `n` is `[1, 16384]`.
///
/// The range of component `n` is `[1, 16384]`.
fn receive_single_n_bytes_message_proof(n: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 38_702 nanoseconds.
Weight::from_parts(41_040_143, 52673)
// Standard Error: 5
.saturating_add(Weight::from_parts(1_174, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
/// 539, mode: MaxEncodedLen)
///
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
/// mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:1)
///
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
/// added: 68071, mode: MaxEncodedLen)
fn receive_delivery_proof_for_single_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `701`
// Estimated: `3558`
// Minimum execution time: 37_197 nanoseconds.
Weight::from_parts(38_371_000, 3558)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
/// 539, mode: MaxEncodedLen)
///
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
/// mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
///
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
/// added: 68071, mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
// Proof Size summary in bytes:
// Measured: `701`
// Estimated: `3558`
// Minimum execution time: 38_684 nanoseconds.
Weight::from_parts(39_929_000, 3558)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
/// 539, mode: MaxEncodedLen)
///
/// Storage: BridgeRelayers RelayerRewards (r:2 w:2)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
/// mode: MaxEncodedLen)
///
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
///
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
/// added: 68071, mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
// Proof Size summary in bytes:
// Measured: `701`
// Estimated: `6126`
// Minimum execution time: 41_363 nanoseconds.
Weight::from_parts(42_621_000, 6126)
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
/// added: 497, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
///
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
/// added: 2048, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
///
/// The range of component `n` is `[1, 16384]`.
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `653`
// Estimated: `52673`
// Minimum execution time: 38_925 nanoseconds.
Weight::from_parts(39_617_000, 52673)
// Standard Error: 612
.saturating_add(Weight::from_parts(372_813, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}
@@ -0,0 +1,470 @@
// Copyright (C) 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/>.
//! Weight-related utilities.
use crate::weights::WeightInfo;
use bp_messages::{MessageNonce, UnrewardedRelayersState};
use pezbp_runtime::{PreComputedSize, Size};
use pezframe_support::weights::Weight;
/// Size of the message being delivered in benchmarks.
pub const EXPECTED_DEFAULT_MESSAGE_LENGTH: u32 = 128;
/// We assume that size of signed extensions on all our chains and size of all 'small' arguments of
/// calls we're checking here would fit 1KB.
const SIGNED_EXTENSIONS_SIZE: u32 = 1024;
/// Number of extra bytes (excluding size of storage value itself) of storage proof.
/// This mostly depends on number of entries (and their density) in the storage trie.
/// Some reserve is reserved to account future chain growth.
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
/// Ensure that weights from `WeightInfoExt` implementation are looking correct.
pub fn ensure_weights_are_correct<W: WeightInfoExt>() {
// all components of weight formulae must have zero `proof_size`, because the `proof_size` is
// benchmarked using `MaxEncodedLen` approach and there are no components that cause additional
// db reads
// W::receive_messages_proof_outbound_lane_state_overhead().ref_time() may be zero because:
// the outbound lane state processing code (`InboundLane::receive_state_update`) is minimal and
// may not be accounted by our benchmarks
assert_eq!(W::receive_messages_proof_outbound_lane_state_overhead().proof_size(), 0);
assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
// verify `receive_messages_delivery_proof` weight components
assert_ne!(W::receive_messages_delivery_proof_overhead().ref_time(), 0);
assert_ne!(W::receive_messages_delivery_proof_overhead().proof_size(), 0);
// W::receive_messages_delivery_proof_messages_overhead(1).ref_time() may be zero because:
// there's no code that iterates over confirmed messages in confirmation transaction
assert_eq!(W::receive_messages_delivery_proof_messages_overhead(1).proof_size(), 0);
// W::receive_messages_delivery_proof_relayers_overhead(1).ref_time() may be zero because:
// runtime **can** choose not to pay any rewards to relayers
// W::receive_messages_delivery_proof_relayers_overhead(1).proof_size() is an exception
// it may or may not cause additional db reads, so proof size may vary
assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
// verify `receive_message_proof` weight
let receive_messages_proof_weight =
W::receive_messages_proof_weight(&PreComputedSize(1), 10, Weight::zero());
assert_ne!(receive_messages_proof_weight.ref_time(), 0);
assert_ne!(receive_messages_proof_weight.proof_size(), 0);
messages_proof_size_does_not_affect_proof_size::<W>();
messages_count_does_not_affect_proof_size::<W>();
// verify `receive_message_proof` weight
let receive_messages_delivery_proof_weight = W::receive_messages_delivery_proof_weight(
&PreComputedSize(1),
&UnrewardedRelayersState::default(),
);
assert_ne!(receive_messages_delivery_proof_weight.ref_time(), 0);
assert_ne!(receive_messages_delivery_proof_weight.proof_size(), 0);
messages_delivery_proof_size_does_not_affect_proof_size::<W>();
total_messages_in_delivery_proof_does_not_affect_proof_size::<W>();
}
/// Ensure that we are able to dispatch maximal size messages.
pub fn ensure_maximal_message_dispatch<W: WeightInfoExt>(
max_incoming_message_size: u32,
max_incoming_message_dispatch_weight: Weight,
) {
let message_dispatch_weight = W::message_dispatch_weight(max_incoming_message_size);
assert!(
message_dispatch_weight.all_lte(max_incoming_message_dispatch_weight),
"Dispatch weight of maximal message {message_dispatch_weight:?} must be lower \
than the hardcoded {max_incoming_message_dispatch_weight:?}",
);
}
/// Ensure that we're able to receive maximal (by-size and by-weight) message from other chain.
pub fn ensure_able_to_receive_message<W: WeightInfoExt>(
max_extrinsic_size: u32,
max_extrinsic_weight: Weight,
max_incoming_message_proof_size: u32,
max_incoming_message_dispatch_weight: Weight,
) {
// verify that we're able to receive proof of maximal-size message
let max_delivery_transaction_size =
max_incoming_message_proof_size.saturating_add(SIGNED_EXTENSIONS_SIZE);
assert!(
max_delivery_transaction_size <= max_extrinsic_size,
"Size of maximal message delivery transaction {max_incoming_message_proof_size} + \
{SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
);
// verify that we're able to receive proof of maximal-size message with maximal dispatch weight
let max_delivery_transaction_dispatch_weight = W::receive_messages_proof_weight(
&PreComputedSize(
(max_incoming_message_proof_size + W::expected_extra_storage_proof_size()) as usize,
),
1,
max_incoming_message_dispatch_weight,
);
assert!(
max_delivery_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
"Weight of maximal message delivery transaction + {max_delivery_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
);
}
/// Ensure that we're able to receive maximal confirmation from other chain.
pub fn ensure_able_to_receive_confirmation<W: WeightInfoExt>(
max_extrinsic_size: u32,
max_extrinsic_weight: Weight,
max_inbound_lane_data_proof_size_from_peer_chain: u32,
max_unrewarded_relayer_entries_at_peer_inbound_lane: MessageNonce,
max_unconfirmed_messages_at_inbound_lane: MessageNonce,
) {
// verify that we're able to receive confirmation of maximal-size
let max_confirmation_transaction_size =
max_inbound_lane_data_proof_size_from_peer_chain.saturating_add(SIGNED_EXTENSIONS_SIZE);
assert!(
max_confirmation_transaction_size <= max_extrinsic_size,
"Size of maximal message delivery confirmation transaction {max_inbound_lane_data_proof_size_from_peer_chain} + {SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
);
// verify that we're able to reward maximal number of relayers that have delivered maximal
// number of messages
let max_confirmation_transaction_dispatch_weight = W::receive_messages_delivery_proof_weight(
&PreComputedSize(max_inbound_lane_data_proof_size_from_peer_chain as usize),
&UnrewardedRelayersState {
unrewarded_relayer_entries: max_unrewarded_relayer_entries_at_peer_inbound_lane,
total_messages: max_unconfirmed_messages_at_inbound_lane,
..Default::default()
},
);
assert!(
max_confirmation_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
"Weight of maximal confirmation transaction {max_confirmation_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
);
}
/// Panics if `proof_size` of message delivery call depends on the message proof size.
fn messages_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
let dispatch_weight = Weight::zero();
let weight_when_proof_size_is_8k =
W::receive_messages_proof_weight(&PreComputedSize(8 * 1024), 1, dispatch_weight);
let weight_when_proof_size_is_16k =
W::receive_messages_proof_weight(&PreComputedSize(16 * 1024), 1, dispatch_weight);
ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
ensure_proof_size_is_the_same(
weight_when_proof_size_is_8k,
weight_when_proof_size_is_16k,
"Messages proof size does not affect values that we read from our storage",
);
}
/// Panics if `proof_size` of message delivery call depends on the messages count.
///
/// In practice, it will depend on the messages count, because most probably every
/// message will read something from db during dispatch. But this must be accounted
/// by the `dispatch_weight`.
fn messages_count_does_not_affect_proof_size<W: WeightInfoExt>() {
let messages_proof_size = PreComputedSize(8 * 1024);
let dispatch_weight = Weight::zero();
let weight_of_one_incoming_message =
W::receive_messages_proof_weight(&messages_proof_size, 1, dispatch_weight);
let weight_of_two_incoming_messages =
W::receive_messages_proof_weight(&messages_proof_size, 2, dispatch_weight);
ensure_weight_components_are_not_zero(weight_of_one_incoming_message);
ensure_weight_components_are_not_zero(weight_of_two_incoming_messages);
ensure_proof_size_is_the_same(
weight_of_one_incoming_message,
weight_of_two_incoming_messages,
"Number of same-lane incoming messages does not affect values that we read from our storage",
);
}
/// Panics if `proof_size` of delivery confirmation call depends on the delivery proof size.
fn messages_delivery_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
let relayers_state = UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1,
last_delivered_nonce: 1,
};
let weight_when_proof_size_is_8k =
W::receive_messages_delivery_proof_weight(&PreComputedSize(8 * 1024), &relayers_state);
let weight_when_proof_size_is_16k =
W::receive_messages_delivery_proof_weight(&PreComputedSize(16 * 1024), &relayers_state);
ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
ensure_proof_size_is_the_same(
weight_when_proof_size_is_8k,
weight_when_proof_size_is_16k,
"Messages delivery proof size does not affect values that we read from our storage",
);
}
/// Panics if `proof_size` of delivery confirmation call depends on the number of confirmed
/// messages.
fn total_messages_in_delivery_proof_does_not_affect_proof_size<W: WeightInfoExt>() {
let proof_size = PreComputedSize(8 * 1024);
let weight_when_1k_messages_confirmed = W::receive_messages_delivery_proof_weight(
&proof_size,
&UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1024,
last_delivered_nonce: 1,
},
);
let weight_when_2k_messages_confirmed = W::receive_messages_delivery_proof_weight(
&proof_size,
&UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 2048,
last_delivered_nonce: 1,
},
);
ensure_weight_components_are_not_zero(weight_when_1k_messages_confirmed);
ensure_weight_components_are_not_zero(weight_when_2k_messages_confirmed);
ensure_proof_size_is_the_same(
weight_when_1k_messages_confirmed,
weight_when_2k_messages_confirmed,
"More messages in delivery proof does not affect values that we read from our storage",
);
}
/// Panics if either Weight' `proof_size` or `ref_time` are zero.
fn ensure_weight_components_are_not_zero(weight: Weight) {
assert_ne!(weight.ref_time(), 0);
assert_ne!(weight.proof_size(), 0);
}
/// Panics if `proof_size` of `weight1` is not equal to `proof_size` of `weight2`.
fn ensure_proof_size_is_the_same(weight1: Weight, weight2: Weight, msg: &str) {
assert_eq!(
weight1.proof_size(),
weight2.proof_size(),
"{msg}: {} must be equal to {}",
weight1.proof_size(),
weight2.proof_size(),
);
}
/// Extended weight info.
pub trait WeightInfoExt: WeightInfo {
/// Size of proof that is already included in the single message delivery weight.
///
/// The message submitter (at source chain) has already covered this cost. But there are two
/// factors that may increase proof size: (1) the message size may be larger than predefined
/// and (2) relayer may add extra trie nodes to the proof. So if proof size is larger than
/// this value, we're going to charge relayer for that.
fn expected_extra_storage_proof_size() -> u32;
// Our configuration assumes that the runtime has special signed extensions used to:
//
// 1) reject obsolete delivery and confirmation transactions;
//
// 2) refund transaction cost to relayer and register his rewards.
//
// The checks in (1) are trivial, so its computation weight may be ignored. And we only touch
// storage values that are read during the call. So we may ignore the weight of this check.
//
// However, during (2) we read and update storage values of other pallets
// (`pezpallet-bridge-relayers` and balances/assets pezpallet). So we need to add this weight to the
// weight of our call. Hence two following methods.
/// Extra weight that is added to the `receive_messages_proof` call weight by signed extensions
/// that are declared at runtime level.
fn receive_messages_proof_overhead_from_runtime() -> Weight;
/// Extra weight that is added to the `receive_messages_delivery_proof` call weight by signed
/// extensions that are declared at runtime level.
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight;
// Functions that are directly mapped to extrinsics weights.
/// Weight of message delivery extrinsic.
fn receive_messages_proof_weight(
proof: &impl Size,
messages_count: u32,
dispatch_weight: Weight,
) -> Weight {
// basic components of extrinsic weight
let base_weight = Self::receive_n_messages_proof(messages_count);
let transaction_overhead_from_runtime =
Self::receive_messages_proof_overhead_from_runtime();
let outbound_state_delivery_weight =
Self::receive_messages_proof_outbound_lane_state_overhead();
let messages_dispatch_weight = dispatch_weight;
// proof size overhead weight
let expected_proof_size = EXPECTED_DEFAULT_MESSAGE_LENGTH
.saturating_mul(messages_count.saturating_sub(1))
.saturating_add(Self::expected_extra_storage_proof_size());
let actual_proof_size = proof.size();
let proof_size_overhead = Self::storage_proof_size_overhead(
actual_proof_size.saturating_sub(expected_proof_size),
);
base_weight
.saturating_add(transaction_overhead_from_runtime)
.saturating_add(outbound_state_delivery_weight)
.saturating_add(messages_dispatch_weight)
.saturating_add(proof_size_overhead)
}
/// Weight of confirmation delivery extrinsic.
fn receive_messages_delivery_proof_weight(
proof: &impl Size,
relayers_state: &UnrewardedRelayersState,
) -> Weight {
// basic components of extrinsic weight
let transaction_overhead = Self::receive_messages_delivery_proof_overhead();
let transaction_overhead_from_runtime =
Self::receive_messages_delivery_proof_overhead_from_runtime();
let messages_overhead =
Self::receive_messages_delivery_proof_messages_overhead(relayers_state.total_messages);
let relayers_overhead = Self::receive_messages_delivery_proof_relayers_overhead(
relayers_state.unrewarded_relayer_entries,
);
// proof size overhead weight
let expected_proof_size = Self::expected_extra_storage_proof_size();
let actual_proof_size = proof.size();
let proof_size_overhead = Self::storage_proof_size_overhead(
actual_proof_size.saturating_sub(expected_proof_size),
);
transaction_overhead
.saturating_add(transaction_overhead_from_runtime)
.saturating_add(messages_overhead)
.saturating_add(relayers_overhead)
.saturating_add(proof_size_overhead)
}
// Functions that are used by extrinsics weights formulas.
/// Returns weight that needs to be accounted when message delivery transaction
/// (`receive_messages_proof`) is carrying outbound lane state proof.
fn receive_messages_proof_outbound_lane_state_overhead() -> Weight {
let weight_of_single_message_and_lane_state =
Self::receive_single_message_proof_with_outbound_lane_state();
let weight_of_single_message = Self::receive_single_message_proof();
weight_of_single_message_and_lane_state.saturating_sub(weight_of_single_message)
}
/// Returns weight overhead of delivery confirmation transaction
/// (`receive_messages_delivery_proof`).
fn receive_messages_delivery_proof_overhead() -> Weight {
let weight_of_two_messages_and_two_tx_overheads =
Self::receive_delivery_proof_for_single_message().saturating_mul(2);
let weight_of_two_messages_and_single_tx_overhead =
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
weight_of_two_messages_and_two_tx_overheads
.saturating_sub(weight_of_two_messages_and_single_tx_overhead)
}
/// Returns weight that needs to be accounted when receiving confirmations for given a number of
/// messages with delivery confirmation transaction (`receive_messages_delivery_proof`).
fn receive_messages_delivery_proof_messages_overhead(messages: MessageNonce) -> Weight {
let weight_of_two_messages =
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
let weight_of_single_message = Self::receive_delivery_proof_for_single_message();
weight_of_two_messages
.saturating_sub(weight_of_single_message)
.saturating_mul(messages as _)
}
/// Returns weight that needs to be accounted when receiving confirmations for given a number of
/// relayers entries with delivery confirmation transaction (`receive_messages_delivery_proof`).
fn receive_messages_delivery_proof_relayers_overhead(relayers: MessageNonce) -> Weight {
let weight_of_two_messages_by_two_relayers =
Self::receive_delivery_proof_for_two_messages_by_two_relayers();
let weight_of_two_messages_by_single_relayer =
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
weight_of_two_messages_by_two_relayers
.saturating_sub(weight_of_two_messages_by_single_relayer)
.saturating_mul(relayers as _)
}
/// Returns weight that needs to be accounted when storage proof of given size is received
/// (either in `receive_messages_proof` or `receive_messages_delivery_proof`).
///
/// **IMPORTANT**: this overhead is already included in the 'base' transaction cost - e.g. proof
/// size depends on messages count or number of entries in the unrewarded relayers set. So this
/// shouldn't be added to cost of transaction, but instead should act as a minimal cost that the
/// relayer must pay when it relays proof of given size (even if cost based on other parameters
/// is less than that cost).
fn storage_proof_size_overhead(proof_size: u32) -> Weight {
let proof_size_in_bytes = proof_size;
let byte_weight = Self::receive_single_n_bytes_message_proof(2) -
Self::receive_single_n_bytes_message_proof(1);
proof_size_in_bytes * byte_weight
}
// Functions that may be used by runtime developers.
/// Returns dispatch weight of message of given size.
///
/// This function would return correct value only if your runtime is configured to run
/// `receive_single_message_proof_with_dispatch` benchmark. See its requirements for
/// details.
fn message_dispatch_weight(message_size: u32) -> Weight {
let message_size_in_bytes = message_size;
Self::receive_single_n_bytes_message_proof_with_dispatch(message_size_in_bytes)
.saturating_sub(Self::receive_single_n_bytes_message_proof(message_size_in_bytes))
}
}
impl WeightInfoExt for () {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
fn receive_messages_proof_overhead_from_runtime() -> Weight {
Weight::zero()
}
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
Weight::zero()
}
}
impl<T: pezframe_system::Config> WeightInfoExt for crate::weights::BridgeWeight<T> {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
fn receive_messages_proof_overhead_from_runtime() -> Weight {
Weight::zero()
}
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
Weight::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{tests::mock::TestRuntime, weights::BridgeWeight};
#[test]
fn ensure_default_weights_are_correct() {
ensure_weights_are_correct::<BridgeWeight<TestRuntime>>();
}
}