feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit 286de54384
6841 changed files with 1848356 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
[package]
name = "pallet-bridge-messages"
description = "Module that allows bridged chains to exchange messages using lane concept."
version = "0.7.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
tracing = { workspace = true }
# Bridge dependencies
bp-header-chain = { workspace = true }
bp-messages = { workspace = true }
bp-runtime = { workspace = true }
# Substrate Dependencies
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
sp-trie = { optional = true, workspace = true }
[dev-dependencies]
bp-runtime = { features = ["test-helpers"], workspace = true }
bp-test-utils = { workspace = true }
pallet-balances = { workspace = true }
pallet-bridge-grandpa = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-messages/std",
"bp-runtime/std",
"bp-test-utils/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"pallet-balances/std",
"pallet-bridge-grandpa/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
"tracing/std",
]
runtime-benchmarks = [
"bp-header-chain/runtime-benchmarks",
"bp-messages/runtime-benchmarks",
"bp-runtime/runtime-benchmarks",
"bp-runtime/test-helpers",
"bp-test-utils/runtime-benchmarks",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-bridge-grandpa/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-trie?/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-bridge-grandpa/try-runtime",
"sp-runtime/try-runtime",
]
test-helpers = ["bp-runtime/test-helpers", "sp-trie"]
+202
View File
@@ -0,0 +1,202 @@
# Bridge Messages Pallet
The messages pallet is used to deliver messages from source chain to target chain. Message is (almost) opaque to the
module and the final goal is to hand message to the message dispatch mechanism.
## Contents
- [Overview](#overview)
- [Message Workflow](#message-workflow)
- [Integrating Message Lane Module into Runtime](#integrating-messages-module-into-runtime)
- [Non-Essential Functionality](#non-essential-functionality)
- [Weights of Module Extrinsics](#weights-of-module-extrinsics)
## Overview
Message lane is a unidirectional channel, where messages are sent from source chain to the target chain. At the same
time, a single instance of messages module supports both outbound lanes and inbound lanes. So the chain where the module
is deployed (this chain), may act as a source chain for outbound messages (heading to a bridged chain) and as a target
chain for inbound messages (coming from a bridged chain).
Messages module supports multiple message lanes. Every message lane is identified with a 4-byte identifier. Messages
sent through the lane are assigned unique (for this lane) increasing integer value that is known as nonce ("number that
can only be used once"). Messages that are sent over the same lane are guaranteed to be delivered to the target chain in
the same order they're sent from the source chain. In other words, message with nonce `N` will be delivered right before
delivering a message with nonce `N+1`.
Single message lane may be seen as a transport channel for single application (onchain, offchain or mixed). At the same
time the module itself never dictates any lane or message rules. In the end, it is the runtime developer who defines
what message lane and message mean for this runtime.
In our [Kusama<>PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md) we are using lane
as a channel of communication between two teyrchains of different relay chains. For example, lane
`[0, 0, 0, 0]` is used for PezkuwiChain <> Kusama Asset Hub communications. Other lanes may be used to
bridge other teyrchains.
## Message Workflow
The pallet is not intended to be used by end users and provides no public calls to send the message. Instead, it
provides runtime-internal method that allows other pallets (or other runtime code) to queue outbound messages.
The message "appears" when some runtime code calls the `send_message()` method of the pallet. The submitter specifies
the lane that they're willing to use and the message itself. If some fee must be paid for sending the message, it must
be paid outside of the pallet. If a message passes all checks (that include, for example, message size check, disabled
lane check, ...), the nonce is assigned and the message is stored in the module storage. The message is in an
"undelivered" state now.
We assume that there are external, offchain actors, called relayers, that are submitting module related transactions to
both target and source chains. The pallet itself has no assumptions about relayers incentivization scheme, but it has
some callbacks for paying rewards. See [Integrating Messages Module into
runtime](#Integrating-Messages-Module-into-runtime) for details.
Eventually, some relayer would notice this message in the "undelivered" state and it would decide to deliver this
message. Relayer then crafts `receive_messages_proof()` transaction (aka delivery transaction) for the messages module
instance, deployed at the target chain. Relayer provides its account id at the source chain, the proof of message (or
several messages), the number of messages in the transaction and their cumulative dispatch weight. Once a transaction is
mined, the message is considered "delivered".
Once a message is delivered, the relayer may want to confirm delivery back to the source chain. There are two reasons
why it would want to do that. The first is that we intentionally limit number of "delivered", but not yet "confirmed"
messages at inbound lanes (see [What about other Constants in the Messages Module Configuration
Trait](#What-about-other-Constants-in-the-Messages-Module-Configuration-Trait) for explanation). So at some point, the
target chain may stop accepting new messages until relayers confirm some of these. The second is that if the relayer
wants to be rewarded for delivery, it must prove the fact that it has actually delivered the message. And this proof may
only be generated after the delivery transaction is mined. So relayer crafts the `receive_messages_delivery_proof()`
transaction (aka confirmation transaction) for the messages module instance, deployed at the source chain. Once this
transaction is mined, the message is considered "confirmed".
The "confirmed" state is the final state of the message. But there's one last thing related to the message - the fact
that it is now "confirmed" and reward has been paid to the relayer (or at least callback for this has been called), must
be confirmed to the target chain. Otherwise, we may reach the limit of "unconfirmed" messages at the target chain and it
will stop accepting new messages. So relayer sometimes includes a nonce of the latest "confirmed" message in the next
`receive_messages_proof()` transaction, proving that some messages have been confirmed.
## Integrating Messages Module into Runtime
As it has been said above, the messages module supports both outbound and inbound message lanes. So if we will integrate
a module in some runtime, it may act as the source chain runtime for outbound messages and as the target chain runtime
for inbound messages. In this section, we'll sometimes refer to the chain we're currently integrating with, as "this
chain" and the other chain as "bridged chain".
Messages module doesn't simply accept transactions that are claiming that the bridged chain has some updated data for
us. Instead of this, the module assumes that the bridged chain is able to prove that updated data in some way. The proof
is abstracted from the module and may be of any kind. In our Substrate-to-Substrate bridge we're using runtime storage
proofs. Other bridges may use transaction proofs, Substrate header digests or anything else that may be proved.
**IMPORTANT NOTE**: everything below in this chapter describes details of the messages module configuration. But if
you're interested in well-probed and relatively easy integration of two Substrate-based chains, you may want to look at
the [bridge-runtime-common](../../bin/runtime-common/) crate. This crate is providing a lot of helpers for integration,
which may be directly used from within your runtime. Then if you'll decide to change something in this scheme, get back
here for detailed information.
### General Information
The messages module supports instances. Every module instance is supposed to bridge this chain and some bridged chain.
To bridge with another chain, using another instance is suggested (this isn't forced anywhere in the code, though). Keep
in mind, that the pallet may be used to build virtual channels between multiple chains, as we do in our [PezkuwiChain <>
Kusama bridge](../../docs/pezkuwi-kusama-bridge-overview.md). There, the pallet actually bridges only two teyrchains -
Kusama Bridge Hub and PezkuwiChain Bridge Hub. However, other Kusama and PezkuwiChain teyrchains are able to send (XCM) messages
to their Bridge Hubs. The messages will be delivered to the other side of the bridge and routed to the proper
destination teyrchain within the bridged chain consensus.
Message submitters may track message progress by inspecting module events. When Message is accepted, the
`MessageAccepted` event is emitted. The event contains both message lane identifier and nonce that has been assigned to
the message. When a message is delivered to the target chain, the `MessagesDelivered` event is emitted from the
`receive_messages_delivery_proof()` transaction. The `MessagesDelivered` contains the message lane identifier and
inclusive range of delivered message nonces.
The pallet provides no means to get the result of message dispatch at the target chain. If that is
required, it must be done outside of the pallet. For example, XCM messages, when dispatched, have
special instructions to send some data back to the sender. Other dispatchers may use similar
mechanism for that.
### How to plug-in Messages Module to Send and Receive Messages from the Bridged Chain?
The `pallet_bridge_messages::Config` trait has 2 main associated types that are used to work with
inbound messages. The `pallet_bridge_messages::BridgedChain` defines basic primitives of the bridged
chain. The `pallet_bridge_messages::BridgedHeaderChain` defines the way we access the bridged chain
headers in our runtime. You may use `pallet_bridge_grandpa` if you're bridging with chain that uses
GRANDPA finality or `pallet_bridge_teyrchains::TeyrchainHeaders` if you're bridging with teyrchain.
The `pallet_bridge_messages::Config::MessageDispatch` defines a way on how to dispatch delivered
messages. Apart from actually dispatching the message, the implementation must return the correct
dispatch weight of the message before dispatch is called.
The last type is the `pallet_bridge_messages::Config::DeliveryConfirmationPayments`. When confirmation
transaction is received, we call the `pay_reward()` method, passing the range of delivered messages.
You may use the [`pallet-bridge-relayers`](../relayers/) pallet and its
[`DeliveryConfirmationPaymentsAdapter`](../relayers/src/payment_adapter.rs) adapter as a possible
implementation. It allows you to pay fixed reward for relaying the message and some of its portion
for confirming delivery.
### I have a Messages Module in my Runtime, but I Want to Reject all Outbound Messages. What shall I do?
You should be looking at the `bp_messages::source_chain::ForbidOutboundMessages` structure
[`bp_messages::source_chain`](../../primitives/messages/src/source_chain.rs). It implements all required traits and will
simply reject all transactions, related to outbound messages.
### I have a Messages Module in my Runtime, but I Want to Reject all Inbound Messages. What shall I do?
You should be looking at the `bp_messages::target_chain::ForbidInboundMessages` structure from the
[`bp_messages::target_chain`](../../primitives/messages/src/target_chain.rs) module. It implements all required traits
and will simply reject all transactions, related to inbound messages.
### What about other Constants in the Messages Module Configuration Trait?
`pallet_bridge_messages::Config::MaximalOutboundPayloadSize` constant defines the maximal size
of outbound message that may be sent. If the message size is above this limit, the message is
rejected.
To be able to reward the relayer for delivering messages, we store a map of message nonces range =>
identifier of the relayer that has delivered this range at the target chain runtime storage. If a
relayer delivers multiple consequent ranges, they're merged into single entry. So there may be more
than one entry for the same relayer. Eventually, this whole map must be delivered back to the source
chain to confirm delivery and pay rewards. So to make sure we are able to craft this confirmation
transaction, we need to: (1) keep the size of this map below a certain limit and (2) make sure that
the weight of processing this map is below a certain limit. Both size and processing weight mostly
depend on the number of entries. The number of entries is limited with the
`pallet_bridge_messages::Config::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` parameter.
Processing weight also depends on the total number of messages that are being confirmed, because every
confirmed message needs to be read. So there's another
`pallet_bridge_messages::Config::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` parameter
for that.
When choosing values for these parameters, you must also keep in mind that if proof in your scheme
is based on finality of headers (and it is the most obvious option for Substrate-based chains with
finality notion), then choosing too small values for these parameters may cause significant delays
in message delivery. That's because there are too many actors involved in this scheme: 1) authorities
that are finalizing headers of the target chain need to finalize header with non-empty map; 2) the
headers relayer then needs to submit this header and its finality proof to the source chain; 3) the
messages relayer must then send confirmation transaction (storage proof of this map) to the source
chain; 4) when the confirmation transaction will be mined at some header, source chain authorities
must finalize this header; 5) the headers relay then needs to submit this header and its finality
proof to the target chain; 6) only now the messages relayer may submit new messages from the source
to target chain and prune the entry from the map.
Delivery transaction requires the relayer to provide both number of entries and total number of
messages in the map. This means that the module never charges an extra cost for delivering a map -
the relayer would need to pay exactly for the number of entries+messages it has delivered. So the
best guess for values of these parameters would be the pair that would occupy `N` percent of the
maximal transaction size and weight of the source chain. The `N` should be large enough to process
large maps, at the same time keeping reserve for future source chain upgrades.
## Non-Essential Functionality
There may be a special account in every runtime where the messages module is deployed. This account, named 'module
owner', is like a module-level sudo account - he's able to halt and resume all module operations without requiring
runtime upgrade. Calls that are related to this account are:
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to pause/resume
pallet operations. Owner may halt the pallet by calling this method with
`MessagesOperatingMode::Basic(BasicOperatingMode::Halted)` argument - all message-related
transactions will be rejected. Owner may then resume pallet operations by passing the
`MessagesOperatingMode::Basic(BasicOperatingMode::Normal)` argument. There's also
`MessagesOperatingMode::RejectingOutboundMessages` pallet mode, where it still accepts all incoming
messages, but all outbound messages are rejected.
If pallet owner is not defined, the governance may be used to make those calls.
## Messages Relay
We have an offchain actor, who is watching for new messages and submits them to the bridged chain. It is the messages
relay - you may look at the [crate level documentation and the code](../../relays/messages/).
@@ -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 pallet 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 bp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams};
use codec::Decode;
use frame_benchmarking::{account, v2::*};
use frame_support::weights::Weight;
use frame_system::RawOrigin;
use sp_runtime::{traits::TrailingZeroInput, BoundedVec};
use sp_std::{ops::RangeInclusive, prelude::*};
const SEED: u32 = 0;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static = ()>(crate::Pallet<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: sp_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 pallet 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!(
Pallet,
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, Pallet, LOG_TARGET};
use bp_messages::{
target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData,
MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
UnrewardedRelayerOccupation,
};
use bp_runtime::{AccountIdOf, OwnedBridgeModule};
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
use sp_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: sp_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 `pallet_bridge_messages::Call`.
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
IsSubType<CallableCallFor<Pallet<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 pallet rules obsolete (like
/// submitting a call when a pallet 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<Pallet<T, I>, T>>,
T: frame_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 = Pallet::<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 pallet"
);
return sp_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 sp_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 sp_runtime::transaction_validity::InvalidTransaction::Stale.into();
},
_ => {},
}
Ok(sp_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 sp_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: frame_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 {
sp_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 bp_runtime::AccountIdOf;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use scale_info::{Type, TypeInfo};
use sp_runtime::RuntimeDebug;
use sp_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> sp_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> sp_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 bp_runtime::AccountIdOf;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use frame_support::{ensure, sp_runtime::RuntimeDebug, PalletError};
use scale_info::TypeInfo;
use sp_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
// `pallet-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 pallet 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_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 bp_runtime::{
AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt,
Size,
};
use codec::{Decode, Encode};
use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound};
use sp_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 pallet::*;
#[cfg(feature = "test-helpers")]
pub use tests::*;
/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-messages";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult};
use bp_runtime::RangeInclusiveExt;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
// General types
/// The overarching event type.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_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;
#[pallet::pallet]
#[pallet::storage_version(migration::STORAGE_VERSION)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pallet<T, I> {
const LOG_TARGET: &'static str = LOG_TARGET;
type OwnerStorage = PalletOwner<T, I>;
type OperatingMode = MessagesOperatingMode;
type OperatingModeStorage = PalletOperatingMode<T, I>;
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Change `PalletOwner`.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::call_index(0)]
#[pallet::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 pallet operations.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::call_index(1)]
#[pallet::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 pallet 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 pallet;
///
/// - 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.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::receive_messages_proof_weight(&**proof, *messages_count, *dispatch_weight))]
pub fn receive_messages_proof(
origin: OriginFor<T>,
relayer_id_at_bridged_chain: 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.
#[pallet::call_index(3)]
#[pallet::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 = sp_std::cmp::min(
relayers_state.unrewarded_relayer_entries,
actually_rewarded_relayers,
);
relayers_state.total_messages = sp_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 })
}
}
#[pallet::event]
#[pallet::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,
},
}
#[pallet::error]
#[derive(PartialEq, Eq)]
pub enum Error<T, I = ()> {
/// Pallet 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 pallet 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(bp_runtime::OwnedBridgeModuleError),
}
/// Optional pallet owner.
///
/// Pallet owner has a right to halt all pallet operations and then resume it. If it is
/// `None`, then there are no direct ways to halt/resume pallet operations, but other
/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
/// flag directly or call the `set_operating_mode`).
#[pallet::storage]
pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
/// The current operating mode of the pallet.
///
/// Depending on the mode either all, some, or no transactions will be allowed.
#[pallet::storage]
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
StorageValue<_, MessagesOperatingMode, ValueQuery>;
// TODO: https://github.com/paritytech/parity-bridges-common/pull/2213: let's limit number of
// possible opened lanes && use it to constraint maps below
/// Map of lane id => inbound lane data.
#[pallet::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.
#[pallet::storage]
pub type OutboundLanes<T: Config<I>, I: 'static = ()> = StorageMap<
Hasher = Blake2_128Concat,
Key = T::LaneId,
Value = OutboundLaneData,
QueryKind = OptionQuery,
>;
/// All queued outbound messages.
#[pallet::storage]
pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, MessageKey<T::LaneId>, StoredMessagePayload<T, I>>;
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
/// Initial pallet operating mode.
pub operating_mode: MessagesOperatingMode,
/// Initial pallet owner.
pub owner: Option<T::AccountId>,
/// Opened lanes.
pub opened_lanes: Vec<T::LaneId>,
/// Dummy marker.
#[serde(skip)]
pub _phantom: sp_std::marker::PhantomData<I>,
}
#[pallet::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());
}
}
}
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
Self::do_try_state()
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get stored data of the outbound message with given nonce.
pub fn outbound_message_data(
lane: 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> Pallet<T, I> {
/// Ensure the correctness of the state of this pallet.
pub fn do_try_state() -> Result<(), sp_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<(), sp_runtime::TryRuntimeError> {
use sp_runtime::traits::One;
use sp_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 Pallet<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 pallet 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"
);
Pallet::<T, I>::deposit_event(Event::MessageAccepted {
lane_id: args.lane_id.into(),
nonce,
});
SendMessageArtifacts { nonce, enqueued_messages }
}
}
/// Ensure that the pallet is in normal operational mode.
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
if PalletOperatingMode::<T, I>::get() ==
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(),
},
)
})
}
+146
View File
@@ -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, Pallet};
use frame_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 bp_runtime::AccountIdOf;
use codec::{Decode, Encode};
use sp_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 frame_support::traits::UncheckedOnRuntimeUpgrade;
use sp_std::marker::PhantomData;
/// Migrates the pallet 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<sp_std::vec::Vec<u8>, sp_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: sp_std::vec::Vec<u8>) -> Result<(), sp_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.");
frame_support::ensure!(
number_of_inbound_to_migrate as usize == number_of_inbound,
"must migrate all `InboundLanes`."
);
frame_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`](frame_support::migrations::VersionedMigration), ensuring the
/// migration is only performed when on-chain version is 0.
pub type MigrationToV1<T, I> = frame_support::migrations::VersionedMigration<
0,
1,
UncheckedMigrationV0ToV1<T, I>,
Pallet<T, I>,
<T as frame_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 frame_support::{traits::Get, BoundedVec, PalletError};
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use sp_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 sp_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_chain::{HeaderChain, HeaderChainError};
use bp_messages::{
source_chain::FromBridgedChainMessagesDeliveryProof,
target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages},
ChainWithMessages, InboundLaneData, Message, MessageKey, MessageNonce, MessagePayload,
OutboundLaneData, VerificationError,
};
use bp_runtime::{
HashOf, HasherOf, RangeInclusiveExt, RawStorageProof, StorageProofChecker, StorageProofError,
};
use codec::Decode;
use sp_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 frame_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: sp_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_chain::{HeaderChainError, StoredHeaderDataBuilder};
use bp_messages::LaneState;
use bp_runtime::{HeaderId, StorageProofError};
use codec::Encode;
use sp_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,
bp_runtime::UnverifiedStorageProofParams::default(),
generate_dummy_message,
encode_message,
encode_outbound_lane_data,
add_duplicate_key,
add_unused_key,
);
sp_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();
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
0,
bridged_header_hash,
));
pallet_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 =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_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 =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_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 bp_runtime::{
grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf,
RawStorageProof, UnverifiedStorageProofParams,
};
use codec::Encode;
use sp_std::{ops::RangeInclusive, prelude::*};
use sp_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)
}
+561
View File
@@ -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_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 bp_runtime::{
messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams,
};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::{
derive_impl,
weights::{constants::RocksDbWeight, Weight},
};
use scale_info::TypeInfo;
use sp_core::H256;
use sp_runtime::{
testing::Header as SubstrateHeader,
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 = SubstrateHeader;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = u64;
type Signature = sp_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 = SubstrateHeader;
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 = sp_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 = frame_system::mocking::MockBlock<TestRuntime>;
use crate as pallet_bridge_messages;
frame_support::construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Event<T>},
BridgedChainGrandpa: pallet_bridge_grandpa::{Pallet, Call, Event<T>},
Messages: pallet_bridge_messages::{Pallet, Call, Event<T>},
}
}
pub type DbWeight = RocksDbWeight;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
type AccountData = pallet_balances::AccountData<Balance>;
type DbWeight = DbWeight;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for TestRuntime {
type AccountStore = System;
}
impl pallet_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 = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
/// weights of messages pallet 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 bp_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();
frame_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();
frame_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();
frame_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl DeliveryConfirmationPayments<AccountId, TestLaneIdType> for TestDeliveryConfirmationPayments {
type Error = &'static str;
fn pay_reward(
_lane_id: TestLaneIdType,
messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
_confirmation_relayer: &AccountId,
received_range: &RangeInclusive<MessageNonce>,
) -> MessageNonce {
let relayers_rewards = calc_relayers_rewards(messages_relayers, received_range);
let rewarded_relayers = relayers_rewards.len();
for (relayer, reward) in &relayers_rewards {
let key = (b":relayer-reward:", relayer, reward).encode();
frame_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 = frame_support::storage::unhashed::get_or_default::<MessageNonce>(&key[..]);
frame_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 {
frame_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)> {
frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
}
}
impl OnMessagesDelivered<TestLaneIdType> for TestOnMessagesDelivered {
fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) {
frame_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() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<TestRuntime> {
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
sp_io::TestExternalities::new(t)
}
/// Run pallet 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();
pallet_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();
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
StoredHeaderData { number: 0, state_root: storage_root },
);
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
}
}
+26
View File
@@ -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 pallet.
#![cfg(any(feature = "test-helpers", test))]
#[cfg(test)]
pub(crate) mod mock;
#[cfg(test)]
mod pallet_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 pallet_bridge_messages
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE 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
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_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 frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_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 `pallet_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: frame_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))
}
}
+470
View File
@@ -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 bp_runtime::{PreComputedSize, Size};
use frame_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
// (`pallet-bridge-relayers` and balances/assets pallet). 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: frame_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>>();
}
}