feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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"]
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -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))),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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>>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user