mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 18:41:03 +00:00
Adding Bridges code as git subtree. (#2515)
* Add instructions. * Squashed 'bridges/' content from commit 345e84a21 git-subtree-dir: bridges git-subtree-split: 345e84a2146b56628e9888c9f5e129cb40e868a9 * Remove bridges workspace file to avoid confusing Cargo. * Add some bridges primitives to Polkadot workspace. * Improve docs.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "pallet-message-lane"
|
||||
description = "Module that allows bridged chains to exchange messages using lane concept."
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
serde = { version = "1.0.101", optional = true, features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-message-lane = { path = "../../primitives/message-lane", default-features = false }
|
||||
bp-rialto = { path = "../../primitives/rialto", default-features = false }
|
||||
bp-runtime = { path = "../../primitives/runtime", default-features = false }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false, optional = true }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.3"
|
||||
sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-message-lane/std",
|
||||
"bp-runtime/std",
|
||||
"bp-rialto/std",
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"num-traits/std",
|
||||
"serde",
|
||||
"sp-core/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
]
|
||||
@@ -0,0 +1,391 @@
|
||||
# Message Lane Module
|
||||
|
||||
The message lane module 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-message-lane-module-into-runtime)
|
||||
- [Non-Essential Functionality](#non-essential-functionality)
|
||||
- [Weights of Module Extrinsics](#weights-of-module-extrinsics)
|
||||
|
||||
## Overview
|
||||
|
||||
Message lane is an unidirectional channel, where messages are sent from source chain to the target
|
||||
chain. At the same time, a single instance of message lane 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).
|
||||
|
||||
Message lane 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.
|
||||
|
||||
## Message Workflow
|
||||
|
||||
The message "appears" when its submitter calls the `send_message()` function of the module. The
|
||||
submitter specifies the lane that he's willing to use, the message itself and the fee that he's
|
||||
willing to pay for the message delivery and dispatch. If a message passes all checks, 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 Message Lane Module into runtime](#Integrating-Message-Lane-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 message lane module instance, deployed at the target chain. Relayer provides
|
||||
his 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 he 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 Message Lane Module Configuration Trait](#What-about-other-Constants-in-the-Message-Lane-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, he must prove the fact
|
||||
that he 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 message lane 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 Message Lane Module into Runtime
|
||||
|
||||
As it has been said above, the message lane 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.
|
||||
|
||||
Message lane 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 message lane module
|
||||
configuration. But if you 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/README.md) 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 message lane 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).
|
||||
|
||||
Message submitters may track message progress by inspecting module events. When Message is accepted,
|
||||
the `MessageAccepted` event is emitted in the `send_message()` transaction. 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.
|
||||
|
||||
### How to plug-in Message Lane Module to Send Messages to the Bridged Chain?
|
||||
|
||||
The `pallet_message_lane::Config` trait has 3 main associated types that are used to work with
|
||||
outbound messages. The `pallet_message_lane::Config::TargetHeaderChain` defines how we see the
|
||||
bridged chain as the target for our outbound messages. It must be able to check that the bridged
|
||||
chain may accept our message - like that the message has size below maximal possible transaction
|
||||
size of the chain and so on. And when the relayer sends us a confirmation transaction, this
|
||||
implementation must be able to parse and verify the proof of messages delivery. Normally, you would
|
||||
reuse the same (configurable) type on all chains that are sending messages to the same bridged
|
||||
chain.
|
||||
|
||||
The `pallet_message_lane::Config::LaneMessageVerifier` defines a single callback to verify outbound
|
||||
messages. The simplest callback may just accept all messages. But in this case you'll need to answer
|
||||
many questions first. Who will pay for the delivery and confirmation transaction? Are we sure that
|
||||
someone will ever deliver this message to the bridged chain? Are we sure that we don't bloat our
|
||||
runtime storage by accepting this message? What if the message is improperly encoded or has some
|
||||
fields set to invalid values? Answering all those (and similar) questions would lead to correct
|
||||
implementation.
|
||||
|
||||
There's another thing to consider when implementing type for use in
|
||||
`pallet_message_lane::Config::LaneMessageVerifier`. It is whether we treat all message lanes
|
||||
identically, or they'll have different sets of verification rules? For example, you may reserve
|
||||
lane#1 for messages coming from some 'wrapped-token' pallet - then you may verify in your
|
||||
implementation that the origin is associated with this pallet. Lane#2 may be reserved for 'system'
|
||||
messages and you may charge zero fee for such messages. You may have some rate limiting for messages
|
||||
sent over the lane#3. Or you may just verify the same rules set for all outbound messages - it is
|
||||
all up to the `pallet_message_lane::Config::LaneMessageVerifier` implementation.
|
||||
|
||||
The last type is the `pallet_message_lane::Config::MessageDeliveryAndDispatchPayment`. When all
|
||||
checks are made and we have decided to accept the message, we're calling the
|
||||
`pay_delivery_and_dispatch_fee()` callback, passing the corresponding argument of the `send_message`
|
||||
function. Later, when message delivery is confirmed, we're calling `pay_relayers_rewards()`
|
||||
callback, passing accounts of relayers and messages that they have delivered. The simplest
|
||||
implementation of this trait is in the [`instant_payments.rs`](./src/instant_payments.rs) module and
|
||||
simply calls `Currency::transfer()` when those callbacks are called. So `Currency` units are
|
||||
transferred between submitter, 'relayers fund' and relayers accounts. Other implementations may use
|
||||
more or less sophisticated techniques - the whole relayers incentivization scheme is not a part of
|
||||
the message lane module.
|
||||
|
||||
### I have a Message Lane Module in my Runtime, but I Want to Reject all Outbound Messages. What shall I do?
|
||||
|
||||
You should be looking at the `bp_message_lane::source_chain::ForbidOutboundMessages` structure
|
||||
[`bp_message_lane::source_chain`](../../primitives/message-lane/src/source_chain.rs). It implements
|
||||
all required traits and will simply reject all transactions, related to outbound messages.
|
||||
|
||||
### How to plug-in Message Lane Module to Receive Messages from the Bridged Chain?
|
||||
|
||||
The `pallet_message_lane::Config` trait has 2 main associated types that are used to work with
|
||||
inbound messages. The `pallet_message_lane::Config::SourceHeaderChain` defines how we see the
|
||||
bridged chain as the source or our inbound messages. When relayer sends us a delivery transaction,
|
||||
this implementation must be able to parse and verify the proof of messages wrapped in this
|
||||
transaction. Normally, you would reuse the same (configurable) type on all chains that are sending
|
||||
messages to the same bridged chain.
|
||||
|
||||
The `pallet_message_lane::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.
|
||||
|
||||
### I have a Message Lane Module in my Runtime, but I Want to Reject all Inbound Messages. What
|
||||
shall I do?
|
||||
|
||||
You should be looking at the `bp_message_lane::target_chain::ForbidInboundMessages` structure from
|
||||
the [`bp_message_lane::target_chain`](../../primitives/message-lane/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 Message Lane Module Configuration Trait?
|
||||
|
||||
Message is being stored in the source chain storage until its delivery will be confirmed. After
|
||||
that, we may safely remove the message from the storage. Lane messages are removed (pruned) when
|
||||
someone sends a new message using the same lane. So the message submitter pays for that pruning. To
|
||||
avoid pruning too many messages in a single transaction, there's
|
||||
`pallet_message_lane::Config::MaxMessagesToPruneAtOnce` configuration parameter. We will never prune
|
||||
more than this number of messages in the single transaction. That said, the value should not be too
|
||||
big to avoid waste of resources when there are no messages to prune.
|
||||
|
||||
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_message_lane::ConfigMaxUnrewardedRelayerEntriesAtInboundLane` 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_message_lane::Config::MaxUnconfirmedMessagesAtInboundLane` 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 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
|
||||
|
||||
Apart from the message related calls, the module exposes a set of auxiliary calls. They fall in two
|
||||
groups, described in the next two paragraphs.
|
||||
|
||||
There may be a special account in every runtime where the message lane module is deployed. This
|
||||
account, named 'module owner', is like a module-level sudo account - he's able to halt all and
|
||||
result all module operations without requiring runtime upgrade. The module may have no message
|
||||
owner, but we suggest to use it at least for initial deployment. To calls that are related to this
|
||||
account are:
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
- `fn halt_operations()`: the module owner (or sudo account) may call this function to stop all
|
||||
module operations. After this call, all message-related transactions will be rejected until
|
||||
further `resume_operations` call'. This call may be used when something extraordinary happens with
|
||||
the bridge;
|
||||
- `fn resume_operations()`: module owner may call this function to resume bridge operations. The
|
||||
module will resume its regular operations after this call.
|
||||
|
||||
Apart from halting and resuming the bridge, the module owner may also tune module configuration
|
||||
parameters without runtime upgrades. The set of parameters needs to be designed in advance, though.
|
||||
The module configuration trait has associated `Parameter` type, which may be e.g. enum and represent
|
||||
a set of parameters that may be updated by the module owner. For example, if your bridge needs to
|
||||
convert sums between different tokens, you may define a 'conversion rate' parameter and let the
|
||||
module owner update this parameter when there are significant changes in the rate. The corresponding
|
||||
module call is `fn update_pallet_parameter()`.
|
||||
|
||||
## Weights of Module Extrinsics
|
||||
|
||||
The main assumptions behind weight formulas is:
|
||||
- all possible costs are paid in advance by the message submitter;
|
||||
- whenever possible, relayer tries to minimize cost of its transactions. So e.g. even though sender
|
||||
always pays for delivering outbound lane state proof, relayer may not include it in the delivery
|
||||
transaction (unless message lane module on target chain requires that);
|
||||
- weight formula should incentivize relayer to not to submit any redundant data in the extrinsics
|
||||
arguments;
|
||||
- the extrinsic shall never be executing slower (i.e. has larger actual weight) than defined by the
|
||||
formula.
|
||||
|
||||
### Weight of `send_message` call
|
||||
|
||||
#### Related benchmarks
|
||||
|
||||
| Benchmark | Description |
|
||||
|-----------------------------------|-----------------------------------------------------|
|
||||
`send_minimal_message_worst_case` | Sends 0-size message with worst possible conditions |
|
||||
`send_1_kb_message_worst_case` | Sends 1KB-size message with worst possible conditions |
|
||||
`send_16_kb_message_worst_case` | Sends 16KB-size message with worst possible conditions |
|
||||
|
||||
#### Weight formula
|
||||
|
||||
The weight formula is:
|
||||
```
|
||||
Weight = BaseWeight + MessageSizeInKilobytes * MessageKiloByteSendWeight
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
| Component | How it is computed? | Description |
|
||||
|-----------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `SendMessageOverhead` | `send_minimal_message_worst_case` | Weight of sending minimal (0 bytes) message |
|
||||
| `MessageKiloByteSendWeight` | `(send_16_kb_message_worst_case - send_1_kb_message_worst_case)/15` | Weight of sending every additional kilobyte of the message |
|
||||
|
||||
### Weight of `receive_messages_proof` call
|
||||
|
||||
#### Related benchmarks
|
||||
|
||||
| Benchmark | Description* |
|
||||
|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| `receive_single_message_proof` | Receives proof of single `EXPECTED_DEFAULT_MESSAGE_LENGTH` message |
|
||||
| `receive_two_messages_proof` | Receives proof of two identical `EXPECTED_DEFAULT_MESSAGE_LENGTH` messages |
|
||||
| `receive_single_message_proof_with_outbound_lane_state` | Receives proof of single `EXPECTED_DEFAULT_MESSAGE_LENGTH` message and proof of outbound lane state at the source chain |
|
||||
| `receive_single_message_proof_1_kb` | Receives proof of single message. The proof has size of approximately 1KB** |
|
||||
| `receive_single_message_proof_16_kb` | Receives proof of single message. The proof has size of approximately 16KB** |
|
||||
|
||||
*\* - In all benchmarks all received messages are dispatched and their dispatch cost is near to zero*
|
||||
|
||||
*\*\* - Trie leafs are assumed to have minimal values. The proof is derived from the minimal proof
|
||||
by including more trie nodes. That's because according to `receive_message_proofs_with_large_leaf`
|
||||
and `receive_message_proofs_with_extra_nodes` benchmarks, increasing proof by including more nodes
|
||||
has slightly larger impact on performance than increasing values stored in leafs*.
|
||||
|
||||
#### Weight formula
|
||||
|
||||
The weight formula is:
|
||||
```
|
||||
Weight = BaseWeight + OutboundStateDeliveryWeight
|
||||
+ MessagesCount * MessageDeliveryWeight
|
||||
+ MessagesDispatchWeight
|
||||
+ Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
| Component | How it is computed? | Description |
|
||||
|-------------------------------|------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `BaseWeight` | `2*receive_single_message_proof - receive_two_messages_proof` | Weight of receiving and parsing minimal proof |
|
||||
| `OutboundStateDeliveryWeight` | `receive_single_message_proof_with_outbound_lane_state - receive_single_message_proof` | Additional weight when proof includes outbound lane state |
|
||||
| `MessageDeliveryWeight` | `receive_two_messages_proof - receive_single_message_proof` | Weight of of parsing and dispatching (without actual dispatch cost) of every message |
|
||||
| `MessagesCount` | | Provided by relayer |
|
||||
| `MessagesDispatchWeight` | | Provided by relayer |
|
||||
| `ActualProofSize` | | Provided by relayer |
|
||||
| `ExpectedProofSize` | `EXPECTED_DEFAULT_MESSAGE_LENGTH * MessagesCount + EXTRA_STORAGE_PROOF_SIZE` | Size of proof that we are expecting. This only includes `EXTRA_STORAGE_PROOF_SIZE` once, because we assume that intermediate nodes likely to be included in the proof only once. This may be wrong, but since weight of processing proof with many nodes is almost equal to processing proof with large leafs, additional cost will be covered because we're charging for extra proof bytes anyway |
|
||||
| `ProofByteDeliveryWeight` | `(receive_single_message_proof_16_kb - receive_single_message_proof_1_kb) / (15 * 1024)` | Weight of processing every additional proof byte over `ExpectedProofSize` limit |
|
||||
|
||||
#### Why for every message sent using `send_message` we will be able to craft `receive_messages_proof` transaction?
|
||||
|
||||
We have following checks in `send_message` transaction on the source chain:
|
||||
- message size should be less than or equal to `2/3` of maximal extrinsic size on the target chain;
|
||||
- message dispatch weight should be less than or equal to the `1/2` of maximal extrinsic dispatch
|
||||
weight on the target chain.
|
||||
|
||||
Delivery transaction is an encoded delivery call and signed extensions. So we have `1/3` of maximal
|
||||
extrinsic size reserved for:
|
||||
- storage proof, excluding the message itself. Currently, on our test chains, the overhead is always
|
||||
within `EXTRA_STORAGE_PROOF_SIZE` limits (1024 bytes);
|
||||
- signed extras and other call arguments (`relayer_id: SourceChain::AccountId`, `messages_count:
|
||||
u32`, `dispatch_weight: u64`).
|
||||
|
||||
On Millau chain, maximal extrinsic size is `0.75 * 2MB`, so `1/3` is `512KB` (`524_288` bytes). This
|
||||
should be enough to cover these extra arguments and signed extensions.
|
||||
|
||||
Let's exclude message dispatch cost from single message delivery transaction weight formula:
|
||||
```
|
||||
Weight = BaseWeight + OutboundStateDeliveryWeight + MessageDeliveryWeight
|
||||
+ Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight
|
||||
```
|
||||
|
||||
So we have `1/2` of maximal extrinsic weight to cover these components. `BaseWeight`,
|
||||
`OutboundStateDeliveryWeight` and `MessageDeliveryWeight` are determined using benchmarks and are
|
||||
hardcoded into runtime. Adequate relayer would only include required trie nodes into the proof. So
|
||||
if message size would be maximal (`2/3` of `MaximalExtrinsicSize`), then the extra proof size would
|
||||
be `MaximalExtrinsicSize / 3 * 2 - EXPECTED_DEFAULT_MESSAGE_LENGTH`.
|
||||
|
||||
Both conditions are verified by `pallet_message_lane::ensure_weights_are_correct` and
|
||||
`pallet_message_lane::ensure_able_to_receive_messages` functions, which must be called from every
|
||||
runtime's tests.
|
||||
|
||||
### Weight of `receive_messages_delivery_proof` call
|
||||
|
||||
#### Related benchmarks
|
||||
|
||||
| Benchmark | Description |
|
||||
|-------------------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `receive_delivery_proof_for_single_message` | Receives proof of single message delivery |
|
||||
| `receive_delivery_proof_for_two_messages_by_single_relayer` | Receives proof of two messages delivery. Both messages are delivered by the same relayer |
|
||||
| `receive_delivery_proof_for_two_messages_by_two_relayers` | Receives proof of two messages delivery. Messages are delivered by different relayers |
|
||||
|
||||
#### Weight formula
|
||||
|
||||
The weight formula is:
|
||||
```
|
||||
Weight = BaseWeight + MessagesCount * MessageConfirmationWeight
|
||||
+ RelayersCount * RelayerRewardWeight
|
||||
+ Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
| Component | How it is computed? | Description |
|
||||
|---------------------------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `BaseWeight` | `2*receive_delivery_proof_for_single_message - receive_delivery_proof_for_two_messages_by_single_relayer` | Weight of receiving and parsing minimal delivery proof |
|
||||
| `MessageDeliveryWeight` | `receive_delivery_proof_for_two_messages_by_single_relayer - receive_delivery_proof_for_single_message` | Weight of confirming every additional message |
|
||||
| `MessagesCount` | | Provided by relayer |
|
||||
| `RelayerRewardWeight` | `receive_delivery_proof_for_two_messages_by_two_relayers - receive_delivery_proof_for_two_messages_by_single_relayer` | Weight of rewarding every additional relayer |
|
||||
| `RelayersCount` | | Provided by relayer |
|
||||
| `ActualProofSize` | | Provided by relayer |
|
||||
| `ExpectedProofSize` | `EXTRA_STORAGE_PROOF_SIZE` | Size of proof that we are expecting |
|
||||
| `ProofByteDeliveryWeight` | `(receive_single_message_proof_16_kb - receive_single_message_proof_1_kb) / (15 * 1024)` | Weight of processing every additional proof byte over `ExpectedProofSize` limit. We're using the same formula, as for message delivery, because proof mechanism is assumed to be the same in both cases |
|
||||
|
||||
#### Why we're always able to craft `receive_messages_delivery_proof` transaction?
|
||||
|
||||
There can be at most `<PeerRuntime as pallet_message_lane::Config>::MaxUnconfirmedMessagesAtInboundLane`
|
||||
messages and at most
|
||||
`<PeerRuntime as pallet_message_lane::Config>::MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded
|
||||
relayers in the single delivery confirmation transaction.
|
||||
|
||||
We're checking that this transaction may be crafted in the
|
||||
`pallet_message_lane::ensure_able_to_receive_confirmation` function, which must be called from every
|
||||
runtime' tests.
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "pallet-message-lane-rpc"
|
||||
description = "Module that provides RPC methods specific to message-lane pallet."
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
derive_more = "0.99.2"
|
||||
futures = { version = "0.3.5", features = ["compat"] }
|
||||
jsonrpc-core = "15.1.0"
|
||||
jsonrpc-core-client = "15.1.0"
|
||||
jsonrpc-derive = "15.1.0"
|
||||
log = "0.4.11"
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-runtime = { path = "../../../primitives/runtime" }
|
||||
bp-message-lane = { path = "../../../primitives/message-lane" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
sc-client-api = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-blockchain = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-state-machine = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-trie = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Possible errors and results of message-lane RPC calls.
|
||||
|
||||
/// Future Result type.
|
||||
pub type FutureResult<T> = jsonrpc_core::BoxFuture<T>;
|
||||
|
||||
/// State RPC errors.
|
||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||
pub enum Error {
|
||||
/// When unknown instance id is passed.
|
||||
#[display(fmt = "Message lane instance is unknown")]
|
||||
UnknownInstance,
|
||||
/// Client error.
|
||||
#[display(fmt = "Client error: {}", _0)]
|
||||
Client(Box<dyn std::error::Error + Send>),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Error::UnknownInstance => None,
|
||||
Error::Client(ref err) => Some(&**err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for jsonrpc_core::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
const UNKNOW_INSTANCE_CODE: i64 = 1;
|
||||
|
||||
match e {
|
||||
Error::UnknownInstance => jsonrpc_core::Error {
|
||||
code: jsonrpc_core::ErrorCode::ServerError(UNKNOW_INSTANCE_CODE),
|
||||
message: "Unknown instance passed".into(),
|
||||
data: None,
|
||||
},
|
||||
Error::Client(e) => jsonrpc_core::Error {
|
||||
code: jsonrpc_core::ErrorCode::InternalError,
|
||||
message: format!("Unknown error occured: {}", e),
|
||||
data: Some(format!("{:?}", e).into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Module that provides RPC methods specific to message-lane pallet.
|
||||
|
||||
use crate::error::{Error, FutureResult};
|
||||
|
||||
use bp_message_lane::{LaneId, MessageNonce};
|
||||
use bp_runtime::InstanceId;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use jsonrpc_core::futures::Future as _;
|
||||
use jsonrpc_derive::rpc;
|
||||
use sc_client_api::Backend as BackendT;
|
||||
use sp_blockchain::{Error as BlockchainError, HeaderBackend};
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
use sp_runtime::{codec::Encode, generic::BlockId, traits::Block as BlockT};
|
||||
use sp_state_machine::prove_read;
|
||||
use sp_trie::StorageProof;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod error;
|
||||
|
||||
/// Trie-based storage proof that the message(s) with given key(s) have been sent by the bridged chain.
|
||||
/// SCALE-encoded trie nodes array `Vec<Vec<u8>>`.
|
||||
pub type MessagesProof = Bytes;
|
||||
|
||||
/// Trie-based storage proof that the message(s) with given key(s) have been received by the bridged chain.
|
||||
/// SCALE-encoded trie nodes array `Vec<Vec<u8>>`.
|
||||
pub type MessagesDeliveryProof = Bytes;
|
||||
|
||||
/// Runtime adapter.
|
||||
pub trait Runtime: Send + Sync + 'static {
|
||||
/// Return runtime storage key for given message. May return None if instance is unknown.
|
||||
fn message_key(&self, instance: &InstanceId, lane: &LaneId, nonce: MessageNonce) -> Option<StorageKey>;
|
||||
/// Return runtime storage key for outbound lane state. May return None if instance is unknown.
|
||||
fn outbound_lane_data_key(&self, instance: &InstanceId, lane: &LaneId) -> Option<StorageKey>;
|
||||
/// Return runtime storage key for inbound lane state. May return None if instance is unknown.
|
||||
fn inbound_lane_data_key(&self, instance: &InstanceId, lane: &LaneId) -> Option<StorageKey>;
|
||||
}
|
||||
|
||||
/// Provides RPC methods for interacting with message-lane pallet.
|
||||
#[rpc]
|
||||
pub trait MessageLaneApi<BlockHash> {
|
||||
/// Returns storage proof of messages in given inclusive range. The state of outbound
|
||||
/// lane is included in the proof if `include_outbound_lane_state` is true.
|
||||
#[rpc(name = "messageLane_proveMessages")]
|
||||
fn prove_messages(
|
||||
&self,
|
||||
instance: InstanceId,
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
include_outbound_lane_state: bool,
|
||||
block: Option<BlockHash>,
|
||||
) -> FutureResult<MessagesProof>;
|
||||
|
||||
/// Returns proof-of-message(s) delivery.
|
||||
#[rpc(name = "messageLane_proveMessagesDelivery")]
|
||||
fn prove_messages_delivery(
|
||||
&self,
|
||||
instance: InstanceId,
|
||||
lane: LaneId,
|
||||
block: Option<BlockHash>,
|
||||
) -> FutureResult<MessagesDeliveryProof>;
|
||||
}
|
||||
|
||||
/// Implements the MessageLaneApi trait for interacting with message lanes.
|
||||
pub struct MessageLaneRpcHandler<Block, Backend, R> {
|
||||
backend: Arc<Backend>,
|
||||
runtime: Arc<R>,
|
||||
_phantom: std::marker::PhantomData<Block>,
|
||||
}
|
||||
|
||||
impl<Block, Backend, R> MessageLaneRpcHandler<Block, Backend, R> {
|
||||
/// Creates new mesage lane RPC handler.
|
||||
pub fn new(backend: Arc<Backend>, runtime: Arc<R>) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
runtime,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, Backend, R> MessageLaneApi<Block::Hash> for MessageLaneRpcHandler<Block, Backend, R>
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: BackendT<Block> + 'static,
|
||||
R: Runtime,
|
||||
{
|
||||
fn prove_messages(
|
||||
&self,
|
||||
instance: InstanceId,
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
include_outbound_lane_state: bool,
|
||||
block: Option<Block::Hash>,
|
||||
) -> FutureResult<MessagesProof> {
|
||||
let runtime = self.runtime.clone();
|
||||
let outbound_lane_data_key = if include_outbound_lane_state {
|
||||
Some(runtime.outbound_lane_data_key(&instance, &lane))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let messages_count = if end >= begin { end - begin + 1 } else { 0 };
|
||||
Box::new(
|
||||
prove_keys_read(
|
||||
self.backend.clone(),
|
||||
block,
|
||||
(begin..=end)
|
||||
.map(move |nonce| runtime.message_key(&instance, &lane, nonce))
|
||||
.chain(outbound_lane_data_key.into_iter()),
|
||||
)
|
||||
.boxed()
|
||||
.compat()
|
||||
.map(move |proof| {
|
||||
let serialized_proof = serialize_storage_proof(proof);
|
||||
log::trace!(
|
||||
"Generated proof of {} messages. Size: {}",
|
||||
messages_count,
|
||||
serialized_proof.len()
|
||||
);
|
||||
serialized_proof
|
||||
})
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
|
||||
fn prove_messages_delivery(
|
||||
&self,
|
||||
instance: InstanceId,
|
||||
lane: LaneId,
|
||||
block: Option<Block::Hash>,
|
||||
) -> FutureResult<MessagesDeliveryProof> {
|
||||
Box::new(
|
||||
prove_keys_read(
|
||||
self.backend.clone(),
|
||||
block,
|
||||
vec![self.runtime.inbound_lane_data_key(&instance, &lane)],
|
||||
)
|
||||
.boxed()
|
||||
.compat()
|
||||
.map(|proof| {
|
||||
let serialized_proof = serialize_storage_proof(proof);
|
||||
log::trace!("Generated message delivery proof. Size: {}", serialized_proof.len());
|
||||
serialized_proof
|
||||
})
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn prove_keys_read<Block, Backend>(
|
||||
backend: Arc<Backend>,
|
||||
block: Option<Block::Hash>,
|
||||
keys: impl IntoIterator<Item = Option<StorageKey>>,
|
||||
) -> Result<StorageProof, Error>
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: BackendT<Block> + 'static,
|
||||
{
|
||||
let block = unwrap_or_best(&*backend, block);
|
||||
let state = backend.state_at(BlockId::Hash(block)).map_err(blockchain_err)?;
|
||||
let keys = keys
|
||||
.into_iter()
|
||||
.map(|key| key.ok_or(Error::UnknownInstance).map(|key| key.0))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let storage_proof = prove_read(state, keys)
|
||||
.map_err(BlockchainError::Execution)
|
||||
.map_err(blockchain_err)?;
|
||||
Ok(storage_proof)
|
||||
}
|
||||
|
||||
fn serialize_storage_proof(proof: StorageProof) -> Bytes {
|
||||
let raw_nodes: Vec<Vec<_>> = proof.iter_nodes().map(Into::into).collect();
|
||||
raw_nodes.encode().into()
|
||||
}
|
||||
|
||||
fn unwrap_or_best<Block: BlockT>(backend: &impl BackendT<Block>, block: Option<Block::Hash>) -> Block::Hash {
|
||||
match block {
|
||||
Some(block) => block,
|
||||
None => backend.blockchain().info().best_hash,
|
||||
}
|
||||
}
|
||||
|
||||
fn blockchain_err(err: BlockchainError) -> Error {
|
||||
Error::Client(Box::new(err))
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Message lane pallet benchmarking.
|
||||
|
||||
use crate::weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH;
|
||||
use crate::{inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane, Call, Instance};
|
||||
|
||||
use bp_message_lane::{
|
||||
source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, InboundLaneData, LaneId, MessageData,
|
||||
MessageNonce, OutboundLaneData, UnrewardedRelayersState,
|
||||
};
|
||||
use frame_benchmarking::{account, benchmarks_instance};
|
||||
use frame_support::{traits::Get, weights::Weight};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, ops::RangeInclusive, prelude::*};
|
||||
|
||||
/// Fee paid by submitter for single message delivery.
|
||||
pub const MESSAGE_FEE: u64 = 10_000_000_000;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
/// Module we're benchmarking here.
|
||||
pub struct Module<T: Config<I>, I: crate::Instance>(crate::Module<T, I>);
|
||||
|
||||
/// Proof size requirements.
|
||||
pub enum ProofSize {
|
||||
/// The proof is expected to be minimal. If value size may be changed, then it is expected to
|
||||
/// have given size.
|
||||
Minimal(u32),
|
||||
/// The proof is expected to have at least given size and grow by increasing number of trie nodes
|
||||
/// included in the proof.
|
||||
HasExtraNodes(u32),
|
||||
/// The proof is expected to have at least given size and grow by increasing value that is stored
|
||||
/// in the trie.
|
||||
HasLargeLeaf(u32),
|
||||
}
|
||||
|
||||
/// Benchmark-specific message parameters.
|
||||
pub struct MessageParams<ThisAccountId> {
|
||||
/// Size of the message payload.
|
||||
pub size: u32,
|
||||
/// Message sender account.
|
||||
pub sender_account: ThisAccountId,
|
||||
}
|
||||
|
||||
/// Benchmark-specific message proof parameters.
|
||||
pub struct MessageProofParams {
|
||||
/// 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>,
|
||||
/// Proof size requirements.
|
||||
pub size: ProofSize,
|
||||
}
|
||||
|
||||
/// Benchmark-specific message delivery proof parameters.
|
||||
pub struct MessageDeliveryProofParams<ThisChainAccountId> {
|
||||
/// 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 size: ProofSize,
|
||||
}
|
||||
|
||||
/// Trait that must be implemented by runtime.
|
||||
pub trait Config<I: Instance>: crate::Config<I> {
|
||||
/// Lane id to use in benchmarks.
|
||||
fn bench_lane_id() -> LaneId {
|
||||
Default::default()
|
||||
}
|
||||
/// Get maximal size of the message payload.
|
||||
fn maximal_message_size() -> u32;
|
||||
/// Return id of relayer account at the bridged chain.
|
||||
fn bridged_relayer_id() -> Self::InboundRelayer;
|
||||
/// Return balance of given account.
|
||||
fn account_balance(account: &Self::AccountId) -> Self::OutboundMessageFee;
|
||||
/// Create given account and give it enough balance for test purposes.
|
||||
fn endow_account(account: &Self::AccountId);
|
||||
/// Prepare message to send over lane.
|
||||
fn prepare_outbound_message(
|
||||
params: MessageParams<Self::AccountId>,
|
||||
) -> (Self::OutboundPayload, Self::OutboundMessageFee);
|
||||
/// Prepare messages proof to receive by the module.
|
||||
fn prepare_message_proof(
|
||||
params: MessageProofParams,
|
||||
) -> (
|
||||
<Self::SourceHeaderChain as SourceHeaderChain<Self::InboundMessageFee>>::MessagesProof,
|
||||
Weight,
|
||||
);
|
||||
/// Prepare messages delivery proof to receive by the module.
|
||||
fn prepare_message_delivery_proof(
|
||||
params: MessageDeliveryProofParams<Self::AccountId>,
|
||||
) -> <Self::TargetHeaderChain as TargetHeaderChain<Self::OutboundPayload, Self::AccountId>>::MessagesDeliveryProof;
|
||||
}
|
||||
|
||||
benchmarks_instance! {
|
||||
//
|
||||
// Benchmarks that are used directly by the runtime.
|
||||
//
|
||||
|
||||
// Benchmark `send_message` extrinsic with the worst possible conditions:
|
||||
// * outbound lane already has state, so it needs to be read and decoded;
|
||||
// * relayers fund account does not exists (in practice it needs to exist in production environment);
|
||||
// * maximal number of messages is being pruned during the call;
|
||||
// * message size is minimal for the target chain.
|
||||
//
|
||||
// Result of this benchmark is used as a base weight for `send_message` call. Then the 'message weight'
|
||||
// (estimated using `send_half_maximal_message_worst_case` and `send_maximal_message_worst_case`) is
|
||||
// added.
|
||||
send_minimal_message_worst_case {
|
||||
let lane_id = T::bench_lane_id();
|
||||
let sender = account("sender", 0, SEED);
|
||||
T::endow_account(&sender);
|
||||
|
||||
// 'send' messages that are to be pruned when our message is sent
|
||||
for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() {
|
||||
send_regular_message::<T, I>();
|
||||
}
|
||||
confirm_message_delivery::<T, I>(T::MaxMessagesToPruneAtOnce::get());
|
||||
|
||||
let (payload, fee) = T::prepare_outbound_message(MessageParams {
|
||||
size: 0,
|
||||
sender_account: sender.clone(),
|
||||
});
|
||||
}: send_message(RawOrigin::Signed(sender), lane_id, payload, fee)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::outbound_latest_generated_nonce(T::bench_lane_id()),
|
||||
T::MaxMessagesToPruneAtOnce::get() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `send_message` extrinsic with the worst possible conditions:
|
||||
// * outbound lane already has state, so it needs to be read and decoded;
|
||||
// * relayers fund account does not exists (in practice it needs to exist in production environment);
|
||||
// * maximal number of messages is being pruned during the call;
|
||||
// * message size is 1KB.
|
||||
//
|
||||
// With single KB of message size, the weight of the call is increased (roughly) by
|
||||
// `(send_16_kb_message_worst_case - send_1_kb_message_worst_case) / 15`.
|
||||
send_1_kb_message_worst_case {
|
||||
let lane_id = T::bench_lane_id();
|
||||
let sender = account("sender", 0, SEED);
|
||||
T::endow_account(&sender);
|
||||
|
||||
// 'send' messages that are to be pruned when our message is sent
|
||||
for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() {
|
||||
send_regular_message::<T, I>();
|
||||
}
|
||||
confirm_message_delivery::<T, I>(T::MaxMessagesToPruneAtOnce::get());
|
||||
|
||||
let size = 1024;
|
||||
assert!(
|
||||
T::maximal_message_size() > size,
|
||||
"This benchmark can only be used with runtime that accepts 1KB messages",
|
||||
);
|
||||
|
||||
let (payload, fee) = T::prepare_outbound_message(MessageParams {
|
||||
size,
|
||||
sender_account: sender.clone(),
|
||||
});
|
||||
}: send_message(RawOrigin::Signed(sender), lane_id, payload, fee)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::outbound_latest_generated_nonce(T::bench_lane_id()),
|
||||
T::MaxMessagesToPruneAtOnce::get() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `send_message` extrinsic with the worst possible conditions:
|
||||
// * outbound lane already has state, so it needs to be read and decoded;
|
||||
// * relayers fund account does not exists (in practice it needs to exist in production environment);
|
||||
// * maximal number of messages is being pruned during the call;
|
||||
// * message size is 16KB.
|
||||
//
|
||||
// With single KB of message size, the weight of the call is increased (roughly) by
|
||||
// `(send_16_kb_message_worst_case - send_1_kb_message_worst_case) / 15`.
|
||||
send_16_kb_message_worst_case {
|
||||
let lane_id = T::bench_lane_id();
|
||||
let sender = account("sender", 0, SEED);
|
||||
T::endow_account(&sender);
|
||||
|
||||
// 'send' messages that are to be pruned when our message is sent
|
||||
for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() {
|
||||
send_regular_message::<T, I>();
|
||||
}
|
||||
confirm_message_delivery::<T, I>(T::MaxMessagesToPruneAtOnce::get());
|
||||
|
||||
let size = 16 * 1024;
|
||||
assert!(
|
||||
T::maximal_message_size() > size,
|
||||
"This benchmark can only be used with runtime that accepts 16KB messages",
|
||||
);
|
||||
|
||||
let (payload, fee) = T::prepare_outbound_message(MessageParams {
|
||||
size,
|
||||
sender_account: sender.clone(),
|
||||
});
|
||||
}: send_message(RawOrigin::Signed(sender), lane_id, payload, fee)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::outbound_latest_generated_nonce(T::bench_lane_id()),
|
||||
T::MaxMessagesToPruneAtOnce::get() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `increase_message_fee` with following conditions:
|
||||
// * message has maximal message;
|
||||
// * submitter account is killed because its balance is less than ED after payment.
|
||||
increase_message_fee {
|
||||
let sender = account("sender", 42, SEED);
|
||||
T::endow_account(&sender);
|
||||
|
||||
let additional_fee = T::account_balance(&sender);
|
||||
let lane_id = T::bench_lane_id();
|
||||
let nonce = 1;
|
||||
|
||||
send_regular_message_with_payload::<T, I>(vec![42u8; T::maximal_message_size() as _]);
|
||||
}: increase_message_fee(RawOrigin::Signed(sender.clone()), lane_id, nonce, additional_fee)
|
||||
verify {
|
||||
assert_eq!(T::account_balance(&sender), 0.into());
|
||||
}
|
||||
|
||||
// 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 successfully dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// This is base benchmark for all other message delivery benchmarks.
|
||||
receive_single_message_proof {
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=21,
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
|
||||
});
|
||||
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
21,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with two 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 successfully dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// The weight of single message delivery could be approximated as
|
||||
// `weight(receive_two_messages_proof) - 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.
|
||||
receive_two_messages_proof {
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=22,
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
|
||||
});
|
||||
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 2, dispatch_weight)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
22,
|
||||
);
|
||||
}
|
||||
|
||||
// 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;
|
||||
// * 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.
|
||||
receive_single_message_proof_with_outbound_lane_state {
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=21,
|
||||
outbound_lane_data: Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 21,
|
||||
latest_received_nonce: 20,
|
||||
latest_generated_nonce: 21,
|
||||
}),
|
||||
size: ProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
|
||||
});
|
||||
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
21,
|
||||
);
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_confirmed_nonce(T::bench_lane_id()),
|
||||
20,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions:
|
||||
// * the proof has many redundand trie nodes with total size of approximately 1KB;
|
||||
// * 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.
|
||||
//
|
||||
// With single KB of messages proof, the weight of the call is increased (roughly) by
|
||||
// `(receive_single_message_proof_16KB - receive_single_message_proof_1_kb) / 15`.
|
||||
receive_single_message_proof_1_kb {
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=21,
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::HasExtraNodes(1024),
|
||||
});
|
||||
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
21,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions:
|
||||
// * the proof has many redundand trie nodes with total size of approximately 16KB;
|
||||
// * 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.
|
||||
//
|
||||
// Size of proof grows because it contains extra trie nodes in it.
|
||||
//
|
||||
// With single KB of messages proof, the weight of the call is increased (roughly) by
|
||||
// `(receive_single_message_proof_16KB - receive_single_message_proof) / 15`.
|
||||
receive_single_message_proof_16_kb {
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=21,
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::HasExtraNodes(16 * 1024),
|
||||
});
|
||||
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
21,
|
||||
);
|
||||
}
|
||||
|
||||
// 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.
|
||||
receive_delivery_proof_for_single_message {
|
||||
let relayers_fund_id = crate::Module::<T, I>::relayer_fund_account_id();
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
let relayer_balance = T::account_balance(&relayer_id);
|
||||
T::endow_account(&relayers_fund_id);
|
||||
|
||||
// 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,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
relayers: vec![(1, 1, relayer_id.clone())].into_iter().collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
size: ProofSize::Minimal(0),
|
||||
});
|
||||
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
|
||||
verify {
|
||||
assert_eq!(
|
||||
T::account_balance(&relayer_id),
|
||||
relayer_balance + MESSAGE_FEE.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// 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)`.
|
||||
receive_delivery_proof_for_two_messages_by_single_relayer {
|
||||
let relayers_fund_id = crate::Module::<T, I>::relayer_fund_account_id();
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
let relayer_balance = T::account_balance(&relayer_id);
|
||||
T::endow_account(&relayers_fund_id);
|
||||
|
||||
// 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,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
relayers: vec![(1, 2, relayer_id.clone())].into_iter().collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
size: ProofSize::Minimal(0),
|
||||
});
|
||||
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
|
||||
verify {
|
||||
ensure_relayer_rewarded::<T, I>(&relayer_id, &relayer_balance);
|
||||
}
|
||||
|
||||
// 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)`.
|
||||
receive_delivery_proof_for_two_messages_by_two_relayers {
|
||||
let relayers_fund_id = crate::Module::<T, I>::relayer_fund_account_id();
|
||||
let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
|
||||
let relayer1_balance = T::account_balance(&relayer1_id);
|
||||
let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
|
||||
let relayer2_balance = T::account_balance(&relayer2_id);
|
||||
T::endow_account(&relayers_fund_id);
|
||||
|
||||
// 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,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
relayers: vec![
|
||||
(1, 1, relayer1_id.clone()),
|
||||
(2, 2, relayer2_id.clone()),
|
||||
].into_iter().collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
size: ProofSize::Minimal(0),
|
||||
});
|
||||
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer1_id.clone()), proof, relayers_state)
|
||||
verify {
|
||||
ensure_relayer_rewarded::<T, I>(&relayer1_id, &relayer1_balance);
|
||||
ensure_relayer_rewarded::<T, I>(&relayer2_id, &relayer2_balance);
|
||||
}
|
||||
|
||||
//
|
||||
// Benchmarks for manual checks.
|
||||
//
|
||||
|
||||
// Benchmark `send_message` extrinsic with following conditions:
|
||||
// * outbound lane already has state, so it needs to be read and decoded;
|
||||
// * relayers fund account does not exists (in practice it needs to exist in production environment);
|
||||
// * maximal number of messages is being pruned during the call;
|
||||
// * message size varies from minimal to maximal for the target chain.
|
||||
//
|
||||
// Results of this benchmark may be used to check how message size affects `send_message` performance.
|
||||
send_messages_of_various_lengths {
|
||||
let i in 0..T::maximal_message_size().try_into().unwrap_or_default();
|
||||
|
||||
let lane_id = T::bench_lane_id();
|
||||
let sender = account("sender", 0, SEED);
|
||||
T::endow_account(&sender);
|
||||
|
||||
// 'send' messages that are to be pruned when our message is sent
|
||||
for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() {
|
||||
send_regular_message::<T, I>();
|
||||
}
|
||||
confirm_message_delivery::<T, I>(T::MaxMessagesToPruneAtOnce::get());
|
||||
|
||||
let (payload, fee) = T::prepare_outbound_message(MessageParams {
|
||||
size: i as _,
|
||||
sender_account: sender.clone(),
|
||||
});
|
||||
}: send_message(RawOrigin::Signed(sender), lane_id, payload, fee)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::outbound_latest_generated_nonce(T::bench_lane_id()),
|
||||
T::MaxMessagesToPruneAtOnce::get() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with multiple 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 successfully dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// This benchmarks gives us an approximation of single message delivery weight. It is similar to the
|
||||
// `weight(receive_two_messages_proof) - weight(receive_single_message_proof)`. So it may be used
|
||||
// to verify that the other approximation is correct.
|
||||
receive_multiple_messages_proof {
|
||||
let i in 1..64;
|
||||
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
let messages_count = i as _;
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=(20 + i as MessageNonce),
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
|
||||
});
|
||||
}: receive_messages_proof(
|
||||
RawOrigin::Signed(relayer_id_on_target),
|
||||
relayer_id_on_source,
|
||||
proof,
|
||||
messages_count,
|
||||
dispatch_weight
|
||||
)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
20 + i as MessageNonce,
|
||||
);
|
||||
}
|
||||
|
||||
// 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 successfully dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// Results of this benchmark may be used to check how proof size affects `receive_message_proof` performance.
|
||||
receive_message_proofs_with_extra_nodes {
|
||||
let i in 0..T::maximal_message_size();
|
||||
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
let messages_count = 1u32;
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=21,
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::HasExtraNodes(i as _),
|
||||
});
|
||||
}: receive_messages_proof(
|
||||
RawOrigin::Signed(relayer_id_on_target),
|
||||
relayer_id_on_source,
|
||||
proof,
|
||||
messages_count,
|
||||
dispatch_weight
|
||||
)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
21,
|
||||
);
|
||||
}
|
||||
|
||||
// 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 successfully dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// Results of this benchmark may be used to check how message size affects `receive_message_proof` performance.
|
||||
receive_message_proofs_with_large_leaf {
|
||||
let i in 0..T::maximal_message_size();
|
||||
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
let messages_count = 1u32;
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=21,
|
||||
outbound_lane_data: None,
|
||||
size: ProofSize::HasLargeLeaf(i as _),
|
||||
});
|
||||
}: receive_messages_proof(
|
||||
RawOrigin::Signed(relayer_id_on_target),
|
||||
relayer_id_on_source,
|
||||
proof,
|
||||
messages_count,
|
||||
dispatch_weight
|
||||
)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
21,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with multiple minimal-weight messages 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;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// This benchmarks gives us an approximation of outbound lane state delivery weight. It is similar to the
|
||||
// `weight(receive_single_message_proof_with_outbound_lane_state) - weight(receive_single_message_proof)`.
|
||||
// So it may be used to verify that the other approximation is correct.
|
||||
receive_multiple_messages_proof_with_outbound_lane_state {
|
||||
let i in 1..128;
|
||||
|
||||
let relayer_id_on_source = T::bridged_relayer_id();
|
||||
let relayer_id_on_target = account("relayer", 0, SEED);
|
||||
let messages_count = i as _;
|
||||
|
||||
// mark messages 1..=20 as delivered
|
||||
receive_messages::<T, I>(20);
|
||||
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: 21..=20 + i as MessageNonce,
|
||||
outbound_lane_data: Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 21,
|
||||
latest_received_nonce: 20,
|
||||
latest_generated_nonce: 21,
|
||||
}),
|
||||
size: ProofSize::Minimal(0),
|
||||
});
|
||||
}: receive_messages_proof(
|
||||
RawOrigin::Signed(relayer_id_on_target),
|
||||
relayer_id_on_source,
|
||||
proof,
|
||||
messages_count,
|
||||
dispatch_weight
|
||||
)
|
||||
verify {
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_received_nonce(T::bench_lane_id()),
|
||||
20 + i as MessageNonce,
|
||||
);
|
||||
assert_eq!(
|
||||
crate::Module::<T, I>::inbound_latest_confirmed_nonce(T::bench_lane_id()),
|
||||
20,
|
||||
);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic where single relayer delivers multiple messages.
|
||||
receive_delivery_proof_for_multiple_messages_by_single_relayer {
|
||||
// there actually should be used value of `MaxUnrewardedRelayerEntriesAtInboundLane` from the bridged
|
||||
// chain, but we're more interested in additional weight/message than in max weight
|
||||
let i in 1..T::MaxUnrewardedRelayerEntriesAtInboundLane::get()
|
||||
.try_into()
|
||||
.expect("Value of MaxUnrewardedRelayerEntriesAtInboundLane is too large");
|
||||
|
||||
let relayers_fund_id = crate::Module::<T, I>::relayer_fund_account_id();
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
let relayer_balance = T::account_balance(&relayer_id);
|
||||
T::endow_account(&relayers_fund_id);
|
||||
|
||||
// send messages that we're going to confirm
|
||||
for _ in 1..=i {
|
||||
send_regular_message::<T, I>();
|
||||
}
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: i as MessageNonce,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
relayers: vec![(1, i as MessageNonce, relayer_id.clone())].into_iter().collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
size: ProofSize::Minimal(0),
|
||||
});
|
||||
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
|
||||
verify {
|
||||
ensure_relayer_rewarded::<T, I>(&relayer_id, &relayer_balance);
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic where every relayer delivers single messages.
|
||||
receive_delivery_proof_for_multiple_messages_by_multiple_relayers {
|
||||
// there actually should be used value of `MaxUnconfirmedMessagesAtInboundLane` from the bridged
|
||||
// chain, but we're more interested in additional weight/message than in max weight
|
||||
let i in 1..T::MaxUnconfirmedMessagesAtInboundLane::get()
|
||||
.try_into()
|
||||
.expect("Value of MaxUnconfirmedMessagesAtInboundLane is too large ");
|
||||
|
||||
let relayers_fund_id = crate::Module::<T, I>::relayer_fund_account_id();
|
||||
let confirmation_relayer_id = account("relayer", 0, SEED);
|
||||
let relayers: BTreeMap<T::AccountId, T::OutboundMessageFee> = (1..=i)
|
||||
.map(|j| {
|
||||
let relayer_id = account("relayer", j + 1, SEED);
|
||||
let relayer_balance = T::account_balance(&relayer_id);
|
||||
(relayer_id, relayer_balance)
|
||||
})
|
||||
.collect();
|
||||
T::endow_account(&relayers_fund_id);
|
||||
|
||||
// send messages that we're going to confirm
|
||||
for _ in 1..=i {
|
||||
send_regular_message::<T, I>();
|
||||
}
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: i as MessageNonce,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: i as MessageNonce,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
relayers: relayers
|
||||
.keys()
|
||||
.enumerate()
|
||||
.map(|(j, relayer_id)| (j as MessageNonce + 1, j as MessageNonce + 1, relayer_id.clone()))
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
size: ProofSize::Minimal(0),
|
||||
});
|
||||
}: receive_messages_delivery_proof(RawOrigin::Signed(confirmation_relayer_id), proof, relayers_state)
|
||||
verify {
|
||||
for (relayer_id, prev_balance) in relayers {
|
||||
ensure_relayer_rewarded::<T, I>(&relayer_id, &prev_balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_regular_message<T: Config<I>, I: Instance>() {
|
||||
let mut outbound_lane = outbound_lane::<T, I>(T::bench_lane_id());
|
||||
outbound_lane.send_message(MessageData {
|
||||
payload: vec![],
|
||||
fee: MESSAGE_FEE.into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn send_regular_message_with_payload<T: Config<I>, I: Instance>(payload: Vec<u8>) {
|
||||
let mut outbound_lane = outbound_lane::<T, I>(T::bench_lane_id());
|
||||
outbound_lane.send_message(MessageData {
|
||||
payload,
|
||||
fee: MESSAGE_FEE.into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn confirm_message_delivery<T: Config<I>, I: Instance>(nonce: MessageNonce) {
|
||||
let mut outbound_lane = outbound_lane::<T, I>(T::bench_lane_id());
|
||||
assert!(outbound_lane.confirm_delivery(nonce).is_some());
|
||||
}
|
||||
|
||||
fn receive_messages<T: Config<I>, I: Instance>(nonce: MessageNonce) {
|
||||
let mut inbound_lane_storage = inbound_lane_storage::<T, I>(T::bench_lane_id());
|
||||
inbound_lane_storage.set_data(InboundLaneData {
|
||||
relayers: vec![(1, nonce, T::bridged_relayer_id())].into_iter().collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
});
|
||||
}
|
||||
|
||||
fn ensure_relayer_rewarded<T: Config<I>, I: Instance>(relayer_id: &T::AccountId, old_balance: &T::OutboundMessageFee) {
|
||||
let new_balance = T::account_balance(relayer_id);
|
||||
assert!(
|
||||
new_balance > *old_balance,
|
||||
"Relayer haven't received reward for relaying message: old balance = {:?}, new balance = {:?}",
|
||||
old_balance,
|
||||
new_balance,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// Copyright 2019-2020 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 bp_message_lane::{
|
||||
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
||||
InboundLaneData, LaneId, MessageKey, MessageNonce, OutboundLaneData,
|
||||
};
|
||||
use sp_std::prelude::PartialEq;
|
||||
|
||||
/// Inbound lane storage.
|
||||
pub trait InboundLaneStorage {
|
||||
/// Delivery and dispatch fee type on source chain.
|
||||
type MessageFee;
|
||||
/// Id of relayer on source chain.
|
||||
type Relayer: PartialEq;
|
||||
|
||||
/// Lane id.
|
||||
fn id(&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>);
|
||||
}
|
||||
|
||||
/// 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 }
|
||||
}
|
||||
|
||||
/// 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(|(_, nonce_high, _)| *nonce_high <= 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((nonce_low, _, _)) if *nonce_low < new_confirmed_nonce => {
|
||||
*nonce_low = new_confirmed_nonce + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.storage.set_data(data);
|
||||
Some(outbound_lane_data.latest_received_nonce)
|
||||
}
|
||||
|
||||
/// Receive new message.
|
||||
pub fn receive_message<P: MessageDispatch<S::MessageFee>>(
|
||||
&mut self,
|
||||
relayer: S::Relayer,
|
||||
nonce: MessageNonce,
|
||||
message_data: DispatchMessageData<P::DispatchPayload, S::MessageFee>,
|
||||
) -> bool {
|
||||
let mut data = self.storage.data();
|
||||
let is_correct_message = nonce == data.last_delivered_nonce() + 1;
|
||||
if !is_correct_message {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
let push_new = match data.relayers.back_mut() {
|
||||
Some((_, nonce_high, last_relayer)) if last_relayer == &relayer => {
|
||||
*nonce_high = nonce;
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
if push_new {
|
||||
data.relayers.push_back((nonce, nonce, relayer));
|
||||
}
|
||||
|
||||
self.storage.set_data(data);
|
||||
|
||||
P::dispatch(DispatchMessage {
|
||||
key: MessageKey {
|
||||
lane_id: self.storage.id(),
|
||||
nonce,
|
||||
},
|
||||
data: message_data,
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
inbound_lane,
|
||||
mock::{
|
||||
message_data, run_test, TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A,
|
||||
TEST_RELAYER_B, TEST_RELAYER_C,
|
||||
},
|
||||
DefaultInstance, RuntimeInboundLaneStorage,
|
||||
};
|
||||
|
||||
fn receive_regular_message(
|
||||
lane: &mut InboundLane<RuntimeInboundLaneStorage<TestRuntime, DefaultInstance>>,
|
||||
nonce: MessageNonce,
|
||||
) {
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
nonce,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_ignores_status_from_the_future() {
|
||||
run_test(|| {
|
||||
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
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![(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![(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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
let mut seed_storage_data = lane.storage.data();
|
||||
// Prepare data
|
||||
seed_storage_data.last_confirmed_nonce = 0;
|
||||
seed_storage_data.relayers.push_back((1, 1, TEST_RELAYER_A));
|
||||
// Simulate messages batch (2, 3, 4) from relayer #2
|
||||
seed_storage_data.relayers.push_back((2, 4, TEST_RELAYER_B));
|
||||
seed_storage_data.relayers.push_back((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![(4, 4, TEST_RELAYER_B), (5, 5, TEST_RELAYER_C)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_message_with_incorrect_nonce() {
|
||||
run_test(|| {
|
||||
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
assert!(!lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
10,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
let max_nonce = <TestRuntime as crate::Config>::MaxUnrewardedRelayerEntriesAtInboundLane::get();
|
||||
for current_nonce in 1..max_nonce + 1 {
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A + current_nonce,
|
||||
current_nonce,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
}
|
||||
// Fails to dispatch new message from different than latest relayer.
|
||||
assert_eq!(
|
||||
false,
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A + max_nonce + 1,
|
||||
max_nonce + 1,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
)
|
||||
);
|
||||
// Fails to dispatch new messages from latest relayer. Prevents griefing attacks.
|
||||
assert_eq!(
|
||||
false,
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A + max_nonce,
|
||||
max_nonce + 1,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() {
|
||||
run_test(|| {
|
||||
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
let max_nonce = <TestRuntime as crate::Config>::MaxUnconfirmedMessagesAtInboundLane::get();
|
||||
for current_nonce in 1..=max_nonce {
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
current_nonce,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
}
|
||||
// Fails to dispatch new message from different than latest relayer.
|
||||
assert_eq!(
|
||||
false,
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_B,
|
||||
max_nonce + 1,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
)
|
||||
);
|
||||
// Fails to dispatch new messages from latest relayer.
|
||||
assert_eq!(
|
||||
false,
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
max_nonce + 1,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_receives_following_messages_from_two_relayers_alternately() {
|
||||
run_test(|| {
|
||||
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
1,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_B,
|
||||
2,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
3,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![(1, 1, TEST_RELAYER_A), (2, 2, TEST_RELAYER_B), (3, 3, TEST_RELAYER_A)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_same_message_from_two_different_relayers() {
|
||||
run_test(|| {
|
||||
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
assert!(lane.receive_message::<TestMessageDispatch>(
|
||||
TEST_RELAYER_A,
|
||||
1,
|
||||
message_data(REGULAR_PAYLOAD).into()
|
||||
));
|
||||
assert_eq!(
|
||||
false,
|
||||
lane.receive_message::<TestMessageDispatch>(TEST_RELAYER_B, 1, message_data(REGULAR_PAYLOAD).into())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_message_is_processed_instantly() {
|
||||
run_test(|| {
|
||||
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
receive_regular_message(&mut lane, 1);
|
||||
assert_eq!(lane.storage.data().last_delivered_nonce(), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Implementation of `MessageDeliveryAndDispatchPayment` trait on top of `Currency` trait.
|
||||
//!
|
||||
//! The payment is first transferred to a special `relayers-fund` account and only transferred
|
||||
//! to the actual relayer in case confirmation is received.
|
||||
|
||||
use bp_message_lane::{
|
||||
source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards, Sender},
|
||||
MessageNonce,
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_support::traits::{Currency as CurrencyT, ExistenceRequirement, Get};
|
||||
use num_traits::Zero;
|
||||
use sp_runtime::traits::Saturating;
|
||||
use sp_std::fmt::Debug;
|
||||
|
||||
/// Instant message payments made in given currency.
|
||||
///
|
||||
/// The balance is initally reserved in a special `relayers-fund` account, and transferred
|
||||
/// to the relayer when message delivery is confirmed.
|
||||
///
|
||||
/// Additionaly, confirmation transaction submitter (`confirmation_relayer`) is reimbursed
|
||||
/// with the confirmation rewards (part of message fee, reserved to pay for delivery confirmation).
|
||||
///
|
||||
/// NOTE The `relayers-fund` account must always exist i.e. be over Existential Deposit (ED; the
|
||||
/// pallet enforces that) to make sure that even if the message cost is below ED it is still payed
|
||||
/// to the relayer account.
|
||||
/// NOTE It's within relayer's interest to keep their balance above ED as well, to make sure they
|
||||
/// can receive the payment.
|
||||
pub struct InstantCurrencyPayments<T, Currency, GetConfirmationFee, RootAccount> {
|
||||
_phantom: sp_std::marker::PhantomData<(T, Currency, GetConfirmationFee, RootAccount)>,
|
||||
}
|
||||
|
||||
impl<T, Currency, GetConfirmationFee, RootAccount> MessageDeliveryAndDispatchPayment<T::AccountId, Currency::Balance>
|
||||
for InstantCurrencyPayments<T, Currency, GetConfirmationFee, RootAccount>
|
||||
where
|
||||
T: frame_system::Config,
|
||||
Currency: CurrencyT<T::AccountId>,
|
||||
Currency::Balance: From<MessageNonce>,
|
||||
GetConfirmationFee: Get<Currency::Balance>,
|
||||
RootAccount: Get<Option<T::AccountId>>,
|
||||
{
|
||||
type Error = &'static str;
|
||||
|
||||
fn initialize(relayer_fund_account: &T::AccountId) -> usize {
|
||||
assert!(
|
||||
frame_system::Module::<T>::account_exists(relayer_fund_account),
|
||||
"The relayer fund account ({:?}) must exist for the message lanes pallet to work correctly.",
|
||||
relayer_fund_account,
|
||||
);
|
||||
1
|
||||
}
|
||||
|
||||
fn pay_delivery_and_dispatch_fee(
|
||||
submitter: &Sender<T::AccountId>,
|
||||
fee: &Currency::Balance,
|
||||
relayer_fund_account: &T::AccountId,
|
||||
) -> Result<(), Self::Error> {
|
||||
let root_account = RootAccount::get();
|
||||
let account = match submitter {
|
||||
Sender::Signed(submitter) => submitter,
|
||||
Sender::Root | Sender::None => root_account
|
||||
.as_ref()
|
||||
.ok_or("Sending messages using Root or None origin is disallowed.")?,
|
||||
};
|
||||
|
||||
Currency::transfer(
|
||||
account,
|
||||
relayer_fund_account,
|
||||
*fee,
|
||||
// it's fine for the submitter to go below Existential Deposit and die.
|
||||
ExistenceRequirement::AllowDeath,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn pay_relayers_rewards(
|
||||
confirmation_relayer: &T::AccountId,
|
||||
relayers_rewards: RelayersRewards<T::AccountId, Currency::Balance>,
|
||||
relayer_fund_account: &T::AccountId,
|
||||
) {
|
||||
pay_relayers_rewards::<Currency, _>(
|
||||
confirmation_relayer,
|
||||
relayers_rewards,
|
||||
relayer_fund_account,
|
||||
GetConfirmationFee::get(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pay rewards to given relayers, optionally rewarding confirmation relayer.
|
||||
fn pay_relayers_rewards<Currency, AccountId>(
|
||||
confirmation_relayer: &AccountId,
|
||||
relayers_rewards: RelayersRewards<AccountId, Currency::Balance>,
|
||||
relayer_fund_account: &AccountId,
|
||||
confirmation_fee: Currency::Balance,
|
||||
) where
|
||||
AccountId: Debug + Default + Encode + PartialEq,
|
||||
Currency: CurrencyT<AccountId>,
|
||||
Currency::Balance: From<u64>,
|
||||
{
|
||||
// reward every relayer except `confirmation_relayer`
|
||||
let mut confirmation_relayer_reward = Currency::Balance::zero();
|
||||
for (relayer, reward) in relayers_rewards {
|
||||
let mut relayer_reward = reward.reward;
|
||||
|
||||
if relayer != *confirmation_relayer {
|
||||
// If delivery confirmation is submitted by other relayer, let's deduct confirmation fee
|
||||
// from relayer reward.
|
||||
//
|
||||
// If confirmation fee has been increased (or if it was the only component of message fee),
|
||||
// then messages relayer may receive zero reward.
|
||||
let mut confirmation_reward = confirmation_fee.saturating_mul(reward.messages.into());
|
||||
if confirmation_reward > relayer_reward {
|
||||
confirmation_reward = relayer_reward;
|
||||
}
|
||||
relayer_reward = relayer_reward.saturating_sub(confirmation_reward);
|
||||
confirmation_relayer_reward = confirmation_relayer_reward.saturating_add(confirmation_reward);
|
||||
} else {
|
||||
// If delivery confirmation is submitted by this relayer, let's add confirmation fee
|
||||
// from other relayers to this relayer reward.
|
||||
confirmation_relayer_reward = confirmation_relayer_reward.saturating_add(reward.reward);
|
||||
continue;
|
||||
}
|
||||
|
||||
pay_relayer_reward::<Currency, _>(relayer_fund_account, &relayer, relayer_reward);
|
||||
}
|
||||
|
||||
// finally - pay reward to confirmation relayer
|
||||
pay_relayer_reward::<Currency, _>(relayer_fund_account, confirmation_relayer, confirmation_relayer_reward);
|
||||
}
|
||||
|
||||
/// Transfer funds from relayers fund account to given relayer.
|
||||
fn pay_relayer_reward<Currency, AccountId>(
|
||||
relayer_fund_account: &AccountId,
|
||||
relayer_account: &AccountId,
|
||||
reward: Currency::Balance,
|
||||
) where
|
||||
AccountId: Debug,
|
||||
Currency: CurrencyT<AccountId>,
|
||||
{
|
||||
if reward.is_zero() {
|
||||
return;
|
||||
}
|
||||
|
||||
let pay_result = Currency::transfer(
|
||||
relayer_fund_account,
|
||||
relayer_account,
|
||||
reward,
|
||||
// the relayer fund account must stay above ED (needs to be pre-funded)
|
||||
ExistenceRequirement::KeepAlive,
|
||||
);
|
||||
|
||||
match pay_result {
|
||||
Ok(_) => frame_support::debug::trace!(
|
||||
target: "runtime",
|
||||
"Rewarded relayer {:?} with {:?}",
|
||||
relayer_account,
|
||||
reward,
|
||||
),
|
||||
Err(error) => frame_support::debug::trace!(
|
||||
target: "runtime",
|
||||
"Failed to pay relayer {:?} reward {:?}: {:?}",
|
||||
relayer_account,
|
||||
reward,
|
||||
error,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{run_test, AccountId as TestAccountId, Balance as TestBalance, TestRuntime};
|
||||
use bp_message_lane::source_chain::RelayerRewards;
|
||||
|
||||
type Balances = pallet_balances::Module<TestRuntime>;
|
||||
|
||||
const RELAYER_1: TestAccountId = 1;
|
||||
const RELAYER_2: TestAccountId = 2;
|
||||
const RELAYER_3: TestAccountId = 3;
|
||||
const RELAYERS_FUND_ACCOUNT: TestAccountId = crate::mock::ENDOWED_ACCOUNT;
|
||||
|
||||
fn relayers_rewards() -> RelayersRewards<TestAccountId, TestBalance> {
|
||||
vec![
|
||||
(
|
||||
RELAYER_1,
|
||||
RelayerRewards {
|
||||
reward: 100,
|
||||
messages: 2,
|
||||
},
|
||||
),
|
||||
(
|
||||
RELAYER_2,
|
||||
RelayerRewards {
|
||||
reward: 100,
|
||||
messages: 3,
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() {
|
||||
run_test(|| {
|
||||
pay_relayers_rewards::<Balances, _>(&RELAYER_2, relayers_rewards(), &RELAYERS_FUND_ACCOUNT, 10);
|
||||
|
||||
assert_eq!(Balances::free_balance(&RELAYER_1), 80);
|
||||
assert_eq!(Balances::free_balance(&RELAYER_2), 120);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() {
|
||||
run_test(|| {
|
||||
pay_relayers_rewards::<Balances, _>(&RELAYER_3, relayers_rewards(), &RELAYERS_FUND_ACCOUNT, 10);
|
||||
|
||||
assert_eq!(Balances::free_balance(&RELAYER_1), 80);
|
||||
assert_eq!(Balances::free_balance(&RELAYER_2), 70);
|
||||
assert_eq!(Balances::free_balance(&RELAYER_3), 50);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() {
|
||||
run_test(|| {
|
||||
pay_relayers_rewards::<Balances, _>(&RELAYER_3, relayers_rewards(), &RELAYERS_FUND_ACCOUNT, 1000);
|
||||
|
||||
assert_eq!(Balances::free_balance(&RELAYER_1), 0);
|
||||
assert_eq!(Balances::free_balance(&RELAYER_2), 0);
|
||||
assert_eq!(Balances::free_balance(&RELAYER_3), 200);
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,405 @@
|
||||
// Copyright 2019-2020 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::Config;
|
||||
|
||||
use bp_message_lane::{
|
||||
source_chain::{
|
||||
LaneMessageVerifier, MessageDeliveryAndDispatchPayment, RelayersRewards, Sender, TargetHeaderChain,
|
||||
},
|
||||
target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
|
||||
InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData,
|
||||
Parameter as MessageLaneParameter,
|
||||
};
|
||||
use bp_runtime::Size;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{parameter_types, weights::Weight};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header as SubstrateHeader,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
FixedU128, Perbill,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u64;
|
||||
#[derive(Decode, Encode, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TestPayload(pub u64, pub Weight);
|
||||
pub type TestMessageFee = u64;
|
||||
pub type TestRelayer = u64;
|
||||
|
||||
pub struct AccountIdConverter;
|
||||
|
||||
impl sp_runtime::traits::Convert<H256, AccountId> for AccountIdConverter {
|
||||
fn convert(hash: H256) -> AccountId {
|
||||
hash.to_low_u64_ne()
|
||||
}
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||
|
||||
use crate as pallet_message_lane;
|
||||
|
||||
frame_support::construct_runtime! {
|
||||
pub enum TestRuntime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Module, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Module, Call, Event<T>},
|
||||
MessageLane: pallet_message_lane::{Module, Call, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl frame_system::Config for TestRuntime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Call = Call;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = SubstrateHeader;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type BaseCallFilter = ();
|
||||
type SystemWeightInfo = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type SS58Prefix = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for TestRuntime {
|
||||
type MaxLocks = ();
|
||||
type Balance = Balance;
|
||||
type DustRemoval = ();
|
||||
type Event = Event;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = frame_system::Module<TestRuntime>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxMessagesToPruneAtOnce: u64 = 10;
|
||||
pub const MaxUnrewardedRelayerEntriesAtInboundLane: u64 = 16;
|
||||
pub const MaxUnconfirmedMessagesAtInboundLane: u64 = 32;
|
||||
pub storage TokenConversionRate: FixedU128 = 1.into();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub enum TestMessageLaneParameter {
|
||||
TokenConversionRate(FixedU128),
|
||||
}
|
||||
|
||||
impl MessageLaneParameter for TestMessageLaneParameter {
|
||||
fn save(&self) {
|
||||
match *self {
|
||||
TestMessageLaneParameter::TokenConversionRate(conversion_rate) => {
|
||||
TokenConversionRate::set(&conversion_rate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for TestRuntime {
|
||||
type Event = Event;
|
||||
type WeightInfo = ();
|
||||
type Parameter = TestMessageLaneParameter;
|
||||
type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce;
|
||||
type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane;
|
||||
type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane;
|
||||
|
||||
type OutboundPayload = TestPayload;
|
||||
type OutboundMessageFee = TestMessageFee;
|
||||
|
||||
type InboundPayload = TestPayload;
|
||||
type InboundMessageFee = TestMessageFee;
|
||||
type InboundRelayer = TestRelayer;
|
||||
|
||||
type AccountIdConverter = AccountIdConverter;
|
||||
|
||||
type TargetHeaderChain = TestTargetHeaderChain;
|
||||
type LaneMessageVerifier = TestLaneMessageVerifier;
|
||||
type MessageDeliveryAndDispatchPayment = TestMessageDeliveryAndDispatchPayment;
|
||||
|
||||
type SourceHeaderChain = TestSourceHeaderChain;
|
||||
type MessageDispatch = TestMessageDispatch;
|
||||
}
|
||||
|
||||
impl Size for TestPayload {
|
||||
fn size_hint(&self) -> u32 {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Error that is returned by all test implementations.
|
||||
pub const TEST_ERROR: &str = "Test error";
|
||||
|
||||
/// Lane that we're using in tests.
|
||||
pub const TEST_LANE_ID: LaneId = [0, 0, 0, 1];
|
||||
|
||||
/// Regular message payload.
|
||||
pub const REGULAR_PAYLOAD: TestPayload = TestPayload(0, 50);
|
||||
|
||||
/// Payload that is rejected by `TestTargetHeaderChain`.
|
||||
pub const PAYLOAD_REJECTED_BY_TARGET_CHAIN: TestPayload = TestPayload(1, 50);
|
||||
|
||||
/// Vec of proved messages, grouped by lane.
|
||||
pub type MessagesByLaneVec = Vec<(LaneId, ProvedLaneMessages<Message<TestMessageFee>>)>;
|
||||
|
||||
/// Test messages proof.
|
||||
#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub struct TestMessagesProof {
|
||||
pub result: Result<MessagesByLaneVec, ()>,
|
||||
}
|
||||
|
||||
impl Size for TestMessagesProof {
|
||||
fn size_hint(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Result<Vec<Message<TestMessageFee>>, ()>> for TestMessagesProof {
|
||||
fn from(result: Result<Vec<Message<TestMessageFee>>, ()>) -> Self {
|
||||
Self {
|
||||
result: result.map(|messages| {
|
||||
let mut messages_by_lane: BTreeMap<LaneId, ProvedLaneMessages<Message<TestMessageFee>>> =
|
||||
BTreeMap::new();
|
||||
for message in messages {
|
||||
messages_by_lane
|
||||
.entry(message.key.lane_id)
|
||||
.or_default()
|
||||
.messages
|
||||
.push(message);
|
||||
}
|
||||
messages_by_lane.into_iter().collect()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages delivery proof used in tests.
|
||||
#[derive(Debug, Encode, Decode, Eq, Clone, PartialEq)]
|
||||
pub struct TestMessagesDeliveryProof(pub Result<(LaneId, InboundLaneData<TestRelayer>), ()>);
|
||||
|
||||
impl Size for TestMessagesDeliveryProof {
|
||||
fn size_hint(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Target header chain that is used in tests.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestTargetHeaderChain;
|
||||
|
||||
impl TargetHeaderChain<TestPayload, TestRelayer> for TestTargetHeaderChain {
|
||||
type Error = &'static str;
|
||||
|
||||
type MessagesDeliveryProof = TestMessagesDeliveryProof;
|
||||
|
||||
fn verify_message(payload: &TestPayload) -> Result<(), Self::Error> {
|
||||
if *payload == PAYLOAD_REJECTED_BY_TARGET_CHAIN {
|
||||
Err(TEST_ERROR)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_messages_delivery_proof(
|
||||
proof: Self::MessagesDeliveryProof,
|
||||
) -> Result<(LaneId, InboundLaneData<TestRelayer>), Self::Error> {
|
||||
proof.0.map_err(|_| TEST_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lane message verifier that is used in tests.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestLaneMessageVerifier;
|
||||
|
||||
impl LaneMessageVerifier<AccountId, TestPayload, TestMessageFee> for TestLaneMessageVerifier {
|
||||
type Error = &'static str;
|
||||
|
||||
fn verify_message(
|
||||
_submitter: &Sender<AccountId>,
|
||||
delivery_and_dispatch_fee: &TestMessageFee,
|
||||
_lane: &LaneId,
|
||||
_lane_outbound_data: &OutboundLaneData,
|
||||
_payload: &TestPayload,
|
||||
) -> Result<(), Self::Error> {
|
||||
if *delivery_and_dispatch_fee != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TEST_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message fee payment system that is used in tests.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestMessageDeliveryAndDispatchPayment;
|
||||
|
||||
impl TestMessageDeliveryAndDispatchPayment {
|
||||
/// Reject all payments.
|
||||
pub fn reject_payments() {
|
||||
frame_support::storage::unhashed::put(b":reject-message-fee:", &true);
|
||||
}
|
||||
|
||||
/// Returns true if given fee has been paid by given submitter.
|
||||
pub fn is_fee_paid(submitter: AccountId, fee: TestMessageFee) -> bool {
|
||||
frame_support::storage::unhashed::get(b":message-fee:") == Some((Sender::Signed(submitter), fee))
|
||||
}
|
||||
|
||||
/// 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 MessageDeliveryAndDispatchPayment<AccountId, TestMessageFee> for TestMessageDeliveryAndDispatchPayment {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_delivery_and_dispatch_fee(
|
||||
submitter: &Sender<AccountId>,
|
||||
fee: &TestMessageFee,
|
||||
_relayer_fund_account: &AccountId,
|
||||
) -> Result<(), Self::Error> {
|
||||
if frame_support::storage::unhashed::get(b":reject-message-fee:") == Some(true) {
|
||||
return Err(TEST_ERROR);
|
||||
}
|
||||
|
||||
frame_support::storage::unhashed::put(b":message-fee:", &(submitter, fee));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pay_relayers_rewards(
|
||||
_confirmation_relayer: &AccountId,
|
||||
relayers_rewards: RelayersRewards<AccountId, TestMessageFee>,
|
||||
_relayer_fund_account: &AccountId,
|
||||
) {
|
||||
for (relayer, reward) in relayers_rewards {
|
||||
let key = (b":relayer-reward:", relayer, reward.reward).encode();
|
||||
frame_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Source header chain that is used in tests.
|
||||
#[derive(Debug)]
|
||||
pub struct TestSourceHeaderChain;
|
||||
|
||||
impl SourceHeaderChain<TestMessageFee> for TestSourceHeaderChain {
|
||||
type Error = &'static str;
|
||||
|
||||
type MessagesProof = TestMessagesProof;
|
||||
|
||||
fn verify_messages_proof(
|
||||
proof: Self::MessagesProof,
|
||||
_messages_count: u32,
|
||||
) -> Result<ProvedMessages<Message<TestMessageFee>>, Self::Error> {
|
||||
proof
|
||||
.result
|
||||
.map(|proof| proof.into_iter().collect())
|
||||
.map_err(|_| TEST_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
/// Source header chain that is used in tests.
|
||||
#[derive(Debug)]
|
||||
pub struct TestMessageDispatch;
|
||||
|
||||
impl MessageDispatch<TestMessageFee> for TestMessageDispatch {
|
||||
type DispatchPayload = TestPayload;
|
||||
|
||||
fn dispatch_weight(message: &DispatchMessage<TestPayload, TestMessageFee>) -> Weight {
|
||||
match message.data.payload.as_ref() {
|
||||
Ok(payload) => payload.1,
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(_message: DispatchMessage<TestPayload, TestMessageFee>) {}
|
||||
}
|
||||
|
||||
/// Return test lane message with given nonce and payload.
|
||||
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message<TestMessageFee> {
|
||||
Message {
|
||||
key: MessageKey {
|
||||
lane_id: TEST_LANE_ID,
|
||||
nonce,
|
||||
},
|
||||
data: message_data(payload),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return message data with valid fee for given payload.
|
||||
pub fn message_data(payload: TestPayload) -> MessageData<TestMessageFee> {
|
||||
MessageData {
|
||||
payload: payload.encode(),
|
||||
fee: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run message lane test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
let mut t = frame_system::GenesisConfig::default()
|
||||
.build_storage::<TestRuntime>()
|
||||
.unwrap();
|
||||
pallet_balances::GenesisConfig::<TestRuntime> {
|
||||
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(test)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2019-2020 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 bp_message_lane::{LaneId, MessageData, MessageNonce, OutboundLaneData};
|
||||
|
||||
/// Outbound lane storage.
|
||||
pub trait OutboundLaneStorage {
|
||||
/// Delivery and dispatch fee type on source chain.
|
||||
type MessageFee;
|
||||
|
||||
/// Lane id.
|
||||
fn id(&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<MessageData<Self::MessageFee>>;
|
||||
/// Save outbound message in the storage.
|
||||
fn save_message(&mut self, nonce: MessageNonce, message_data: MessageData<Self::MessageFee>);
|
||||
/// Remove outbound message from the storage.
|
||||
fn remove_message(&mut self, nonce: &MessageNonce);
|
||||
}
|
||||
|
||||
/// Outbound messages lane.
|
||||
pub struct OutboundLane<S> {
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S: OutboundLaneStorage> OutboundLane<S> {
|
||||
/// Create new inbound 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()
|
||||
}
|
||||
|
||||
/// Send message over lane.
|
||||
///
|
||||
/// Returns new message nonce.
|
||||
pub fn send_message(&mut self, message_data: MessageData<S::MessageFee>) -> MessageNonce {
|
||||
let mut data = self.storage.data();
|
||||
let nonce = data.latest_generated_nonce + 1;
|
||||
data.latest_generated_nonce = nonce;
|
||||
|
||||
self.storage.save_message(nonce, message_data);
|
||||
self.storage.set_data(data);
|
||||
|
||||
nonce
|
||||
}
|
||||
|
||||
/// Confirm messages delivery.
|
||||
///
|
||||
/// Returns `None` if confirmation is wrong/duplicate.
|
||||
/// Returns `Some` with inclusive ranges of message nonces that have been received.
|
||||
pub fn confirm_delivery(&mut self, latest_received_nonce: MessageNonce) -> Option<(MessageNonce, MessageNonce)> {
|
||||
let mut data = self.storage.data();
|
||||
if latest_received_nonce <= data.latest_received_nonce || latest_received_nonce > data.latest_generated_nonce {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prev_latest_received_nonce = data.latest_received_nonce;
|
||||
data.latest_received_nonce = latest_received_nonce;
|
||||
self.storage.set_data(data);
|
||||
|
||||
Some((prev_latest_received_nonce + 1, latest_received_nonce))
|
||||
}
|
||||
|
||||
/// Prune at most `max_messages_to_prune` already received messages.
|
||||
///
|
||||
/// Returns number of pruned messages.
|
||||
pub fn prune_messages(&mut self, max_messages_to_prune: MessageNonce) -> MessageNonce {
|
||||
let mut pruned_messages = 0;
|
||||
let mut anything_changed = false;
|
||||
let mut data = self.storage.data();
|
||||
while pruned_messages < max_messages_to_prune && data.oldest_unpruned_nonce <= data.latest_received_nonce {
|
||||
self.storage.remove_message(&data.oldest_unpruned_nonce);
|
||||
|
||||
anything_changed = true;
|
||||
pruned_messages += 1;
|
||||
data.oldest_unpruned_nonce += 1;
|
||||
}
|
||||
|
||||
if anything_changed {
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
pruned_messages
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
mock::{message_data, run_test, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID},
|
||||
outbound_lane,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn send_message_works() {
|
||||
run_test(|| {
|
||||
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 0);
|
||||
assert_eq!(lane.send_message(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 = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 2);
|
||||
assert_eq!(lane.send_message(message_data(REGULAR_PAYLOAD)), 3);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.confirm_delivery(3), Some((1, 3)));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_rejects_nonce_lesser_than_latest_received() {
|
||||
run_test(|| {
|
||||
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.confirm_delivery(3), Some((1, 3)));
|
||||
assert_eq!(lane.confirm_delivery(3), None);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
|
||||
assert_eq!(lane.confirm_delivery(2), None);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_rejects_nonce_larger_than_last_generated() {
|
||||
run_test(|| {
|
||||
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.confirm_delivery(10), None);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prune_messages_works() {
|
||||
run_test(|| {
|
||||
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
|
||||
// when lane is empty, nothing is pruned
|
||||
assert_eq!(lane.prune_messages(100), 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
// when nothing is confirmed, nothing is pruned
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.prune_messages(100), 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
// after confirmation, some messages are received
|
||||
assert_eq!(lane.confirm_delivery(2), Some((1, 2)));
|
||||
assert_eq!(lane.prune_messages(100), 2);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3);
|
||||
// after last message is confirmed, everything is pruned
|
||||
assert_eq!(lane.confirm_delivery(3), Some((3, 3)));
|
||||
assert_eq!(lane.prune_messages(100), 1);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
// Copyright 2019-2020 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_message_lane
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.1
|
||||
//! DATE: 2021-02-11, STEPS: [50, ], REPEAT: 20
|
||||
//! LOW RANGE: [], HIGH RANGE: []
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
|
||||
//! CHAIN: Some("local"), DB CACHE: 128
|
||||
|
||||
// Executed Command:
|
||||
// target/release/rialto-bridge-node
|
||||
// benchmark
|
||||
// --chain=local
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pallet=pallet_message_lane
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/message-lane/src/weights.rs
|
||||
// --template=./.maintain/rialto-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_message_lane.
|
||||
pub trait WeightInfo {
|
||||
fn send_minimal_message_worst_case() -> Weight;
|
||||
fn send_1_kb_message_worst_case() -> Weight;
|
||||
fn send_16_kb_message_worst_case() -> Weight;
|
||||
fn increase_message_fee() -> Weight;
|
||||
fn receive_single_message_proof() -> Weight;
|
||||
fn receive_two_messages_proof() -> Weight;
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight;
|
||||
fn receive_single_message_proof_1_kb() -> Weight;
|
||||
fn receive_single_message_proof_16_kb() -> 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 send_messages_of_various_lengths(i: u32) -> Weight;
|
||||
fn receive_multiple_messages_proof(i: u32) -> Weight;
|
||||
fn receive_message_proofs_with_extra_nodes(i: u32) -> Weight;
|
||||
fn receive_message_proofs_with_large_leaf(i: u32) -> Weight;
|
||||
fn receive_multiple_messages_proof_with_outbound_lane_state(i: u32) -> Weight;
|
||||
fn receive_delivery_proof_for_multiple_messages_by_single_relayer(i: u32) -> Weight;
|
||||
fn receive_delivery_proof_for_multiple_messages_by_multiple_relayers(i: u32) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_message_lane using the Rialto node and recommended hardware.
|
||||
pub struct RialtoWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for RialtoWeight<T> {
|
||||
fn send_minimal_message_worst_case() -> Weight {
|
||||
(140_645_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn send_1_kb_message_worst_case() -> Weight {
|
||||
(146_434_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn send_16_kb_message_worst_case() -> Weight {
|
||||
(214_721_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn increase_message_fee() -> Weight {
|
||||
(8_395_221_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
(156_390_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_two_messages_proof() -> Weight {
|
||||
(269_316_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
(174_342_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof_1_kb() -> Weight {
|
||||
(186_621_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof_16_kb() -> Weight {
|
||||
(487_028_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
(144_893_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(6 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
(151_134_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(7 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
(212_650_000 as Weight)
|
||||
.saturating_add(T::DbWeight::get().reads(8 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
fn send_messages_of_various_lengths(i: u32) -> Weight {
|
||||
(88_670_000 as Weight)
|
||||
.saturating_add((5_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn receive_multiple_messages_proof(i: u32) -> Weight {
|
||||
(0 as Weight)
|
||||
.saturating_add((125_956_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_message_proofs_with_extra_nodes(i: u32) -> Weight {
|
||||
(462_389_000 as Weight)
|
||||
.saturating_add((11_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_message_proofs_with_large_leaf(i: u32) -> Weight {
|
||||
(120_744_000 as Weight)
|
||||
.saturating_add((8_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_multiple_messages_proof_with_outbound_lane_state(i: u32) -> Weight {
|
||||
(0 as Weight)
|
||||
.saturating_add((130_087_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_multiple_messages_by_single_relayer(i: u32) -> Weight {
|
||||
(126_833_000 as Weight)
|
||||
.saturating_add((7_793_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(5 as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_multiple_messages_by_multiple_relayers(i: u32) -> Weight {
|
||||
(71_269_000 as Weight)
|
||||
.saturating_add((72_377_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(5 as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(i as Weight)))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight)))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
fn send_minimal_message_worst_case() -> Weight {
|
||||
(140_645_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn send_1_kb_message_worst_case() -> Weight {
|
||||
(146_434_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn send_16_kb_message_worst_case() -> Weight {
|
||||
(214_721_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn increase_message_fee() -> Weight {
|
||||
(8_395_221_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
(156_390_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_two_messages_proof() -> Weight {
|
||||
(269_316_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
(174_342_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof_1_kb() -> Weight {
|
||||
(186_621_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_single_message_proof_16_kb() -> Weight {
|
||||
(487_028_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
(144_893_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(6 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
(151_134_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(7 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
(212_650_000 as Weight)
|
||||
.saturating_add(RocksDbWeight::get().reads(8 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
|
||||
}
|
||||
fn send_messages_of_various_lengths(i: u32) -> Weight {
|
||||
(88_670_000 as Weight)
|
||||
.saturating_add((5_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(12 as Weight))
|
||||
}
|
||||
fn receive_multiple_messages_proof(i: u32) -> Weight {
|
||||
(0 as Weight)
|
||||
.saturating_add((125_956_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_message_proofs_with_extra_nodes(i: u32) -> Weight {
|
||||
(462_389_000 as Weight)
|
||||
.saturating_add((11_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_message_proofs_with_large_leaf(i: u32) -> Weight {
|
||||
(120_744_000 as Weight)
|
||||
.saturating_add((8_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_multiple_messages_proof_with_outbound_lane_state(i: u32) -> Weight {
|
||||
(0 as Weight)
|
||||
.saturating_add((130_087_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_multiple_messages_by_single_relayer(i: u32) -> Weight {
|
||||
(126_833_000 as Weight)
|
||||
.saturating_add((7_793_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
}
|
||||
fn receive_delivery_proof_for_multiple_messages_by_multiple_relayers(i: u32) -> Weight {
|
||||
(71_269_000 as Weight)
|
||||
.saturating_add((72_377_000 as Weight).saturating_mul(i as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(i as Weight)))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright 2019-2020 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_message_lane::{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;
|
||||
|
||||
/// Ensure that weights from `WeightInfoExt` implementation are looking correct.
|
||||
pub fn ensure_weights_are_correct<W: WeightInfoExt>(
|
||||
expected_default_message_delivery_tx_weight: Weight,
|
||||
expected_additional_byte_delivery_weight: Weight,
|
||||
expected_messages_delivery_confirmation_tx_weight: Weight,
|
||||
) {
|
||||
// verify `send_message` weight components
|
||||
assert_ne!(W::send_message_overhead(), 0);
|
||||
assert_ne!(W::send_message_size_overhead(0), 0);
|
||||
|
||||
// verify `receive_messages_proof` weight components
|
||||
assert_ne!(W::receive_messages_proof_overhead(), 0);
|
||||
assert_ne!(W::receive_messages_proof_messages_overhead(1), 0);
|
||||
assert_ne!(W::receive_messages_proof_outbound_lane_state_overhead(), 0);
|
||||
assert_ne!(W::storage_proof_size_overhead(1), 0);
|
||||
|
||||
// verify that the hardcoded value covers `receive_messages_proof` weight
|
||||
let actual_single_regular_message_delivery_tx_weight = W::receive_messages_proof_weight(
|
||||
&PreComputedSize((EXPECTED_DEFAULT_MESSAGE_LENGTH + W::expected_extra_storage_proof_size()) as usize),
|
||||
1,
|
||||
0,
|
||||
);
|
||||
assert!(
|
||||
actual_single_regular_message_delivery_tx_weight <= expected_default_message_delivery_tx_weight,
|
||||
"Default message delivery transaction weight {} is larger than expected weight {}",
|
||||
actual_single_regular_message_delivery_tx_weight,
|
||||
expected_default_message_delivery_tx_weight,
|
||||
);
|
||||
|
||||
// verify that hardcoded value covers additional byte length of `receive_messages_proof` weight
|
||||
let actual_additional_byte_delivery_weight = W::storage_proof_size_overhead(1);
|
||||
assert!(
|
||||
actual_additional_byte_delivery_weight <= expected_additional_byte_delivery_weight,
|
||||
"Single additional byte delivery weight {} is larger than expected weight {}",
|
||||
actual_additional_byte_delivery_weight,
|
||||
expected_additional_byte_delivery_weight,
|
||||
);
|
||||
|
||||
// verify `receive_messages_delivery_proof` weight components
|
||||
assert_ne!(W::receive_messages_delivery_proof_overhead(), 0);
|
||||
assert_ne!(W::receive_messages_delivery_proof_messages_overhead(1), 0);
|
||||
assert_ne!(W::receive_messages_delivery_proof_relayers_overhead(1), 0);
|
||||
assert_ne!(W::storage_proof_size_overhead(1), 0);
|
||||
|
||||
// verify that the hardcoded value covers `receive_messages_delivery_proof` weight
|
||||
let actual_messages_delivery_confirmation_tx_weight = W::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(W::expected_extra_storage_proof_size() as usize),
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
total_messages: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
actual_messages_delivery_confirmation_tx_weight <= expected_messages_delivery_confirmation_tx_weight,
|
||||
"Messages delivery confirmation transaction weight {} is larger than expected weight {}",
|
||||
actual_messages_delivery_confirmation_tx_weight,
|
||||
expected_messages_delivery_confirmation_tx_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,
|
||||
// This is a base weight (which includes cost of tx itself, per-byte cost, adjusted per-byte cost) of single
|
||||
// message delivery transaction that brings `max_incoming_message_proof_size` proof.
|
||||
max_incoming_message_proof_base_weight: Weight,
|
||||
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 {} + {} is larger than maximal possible transaction size {}",
|
||||
max_incoming_message_proof_size,
|
||||
SIGNED_EXTENSIONS_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,
|
||||
);
|
||||
let max_delivery_transaction_weight =
|
||||
max_incoming_message_proof_base_weight.saturating_add(max_delivery_transaction_dispatch_weight);
|
||||
assert!(
|
||||
max_delivery_transaction_weight <= max_extrinsic_weight,
|
||||
"Weight of maximal message delivery transaction {} + {} is larger than maximal possible transaction weight {}",
|
||||
max_delivery_transaction_weight,
|
||||
max_delivery_transaction_dispatch_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,
|
||||
// This is a base weight (which includes cost of tx itself, per-byte cost, adjusted per-byte cost) of single
|
||||
// confirmation transaction that brings `max_inbound_lane_data_proof_size_from_peer_chain` proof.
|
||||
max_incoming_delivery_proof_base_weight: Weight,
|
||||
) {
|
||||
// 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 {} + {} is larger than maximal possible transaction size {}",
|
||||
max_inbound_lane_data_proof_size_from_peer_chain,
|
||||
SIGNED_EXTENSIONS_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()
|
||||
},
|
||||
);
|
||||
let max_confirmation_transaction_weight =
|
||||
max_incoming_delivery_proof_base_weight.saturating_add(max_confirmation_transaction_dispatch_weight);
|
||||
assert!(
|
||||
max_confirmation_transaction_weight <= max_extrinsic_weight,
|
||||
"Weight of maximal confirmation transaction {} + {} is larger than maximal possible transaction weight {}",
|
||||
max_incoming_delivery_proof_base_weight,
|
||||
max_confirmation_transaction_dispatch_weight,
|
||||
max_extrinsic_weight,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
// Functions that are directly mapped to extrinsics weights.
|
||||
|
||||
/// Weight of message send extrinsic.
|
||||
fn send_message_weight(message: &impl Size) -> Weight {
|
||||
let transaction_overhead = Self::send_message_overhead();
|
||||
let message_size_overhead = Self::send_message_size_overhead(message.size_hint());
|
||||
|
||||
transaction_overhead.saturating_add(message_size_overhead)
|
||||
}
|
||||
|
||||
/// 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 transaction_overhead = Self::receive_messages_proof_overhead();
|
||||
let outbound_state_delivery_weight = Self::receive_messages_proof_outbound_lane_state_overhead();
|
||||
let messages_delivery_weight =
|
||||
Self::receive_messages_proof_messages_overhead(MessageNonce::from(messages_count));
|
||||
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_hint();
|
||||
let proof_size_overhead =
|
||||
Self::storage_proof_size_overhead(actual_proof_size.saturating_sub(expected_proof_size));
|
||||
|
||||
transaction_overhead
|
||||
.saturating_add(outbound_state_delivery_weight)
|
||||
.saturating_add(messages_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 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_hint();
|
||||
let proof_size_overhead =
|
||||
Self::storage_proof_size_overhead(actual_proof_size.saturating_sub(expected_proof_size));
|
||||
|
||||
transaction_overhead
|
||||
.saturating_add(messages_overhead)
|
||||
.saturating_add(relayers_overhead)
|
||||
.saturating_add(proof_size_overhead)
|
||||
}
|
||||
|
||||
// Functions that are used by extrinsics weights formulas.
|
||||
|
||||
/// Returns weight of message send transaction (`send_message`).
|
||||
fn send_message_overhead() -> Weight {
|
||||
Self::send_minimal_message_worst_case()
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when message of given size is sent (`send_message`).
|
||||
fn send_message_size_overhead(message_size: u32) -> Weight {
|
||||
let message_size_in_kb = (1024u64 + message_size as u64) / 1024;
|
||||
let single_kb_weight = (Self::send_16_kb_message_worst_case() - Self::send_1_kb_message_worst_case()) / 15;
|
||||
message_size_in_kb * single_kb_weight
|
||||
}
|
||||
|
||||
/// Returns weight overhead of message delivery transaction (`receive_messages_proof`).
|
||||
fn receive_messages_proof_overhead() -> Weight {
|
||||
let weight_of_two_messages_and_two_tx_overheads = Self::receive_single_message_proof().saturating_mul(2);
|
||||
let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof();
|
||||
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 given number of messages with message
|
||||
/// delivery transaction (`receive_messages_proof`).
|
||||
fn receive_messages_proof_messages_overhead(messages: MessageNonce) -> Weight {
|
||||
let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof();
|
||||
let weight_of_single_message_and_single_tx_overhead = Self::receive_single_message_proof();
|
||||
weight_of_two_messages_and_single_tx_overhead
|
||||
.saturating_sub(weight_of_single_message_and_single_tx_overhead)
|
||||
.saturating_mul(messages as Weight)
|
||||
}
|
||||
|
||||
/// 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 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 Weight)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when receiving confirmations for given 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 Weight)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when storage proof of given size is recieved (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 as Weight;
|
||||
let byte_weight =
|
||||
(Self::receive_single_message_proof_16_kb() - Self::receive_single_message_proof_1_kb()) / (15 * 1024);
|
||||
proof_size_in_bytes * byte_weight
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
bp_rialto::EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: frame_system::Config> WeightInfoExt for crate::weights::RialtoWeight<T> {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
bp_rialto::EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user