Remove support for encoded-call messaging from relay and runtime integration code (#1376)

* remove support for encoded-call messaging

* continue cleanup

* continue cleanup

* continue cleanup

* more cleanpup

* more cleanup

* fmt

* continue cleanup

* spellcheck

* rename

* fix benchmarks

* mention encoded-calls-messaging tag

* fixing deployments

* fix messages generation

* fmt
This commit is contained in:
Svyatoslav Nikolsky
2022-05-04 15:05:14 +03:00
committed by Bastian Köcher
parent dc96aeea35
commit d582061dff
58 changed files with 408 additions and 7062 deletions
+2 -6
View File
@@ -9,17 +9,15 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
ed25519-dalek = { version = "1.0", default-features = false, optional = true }
hash-db = { version = "0.15.2", default-features = false }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
static_assertions = { version = "1.1", optional = true }
# Bridge dependencies
bp-message-dispatch = { path = "../../primitives/message-dispatch", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
pallet-bridge-dispatch = { path = "../../modules/dispatch", default-features = false }
pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false }
pallet-bridge-messages = { path = "../../modules/messages", default-features = false }
@@ -40,14 +38,13 @@ sp-version = { git = "https://github.com/paritytech/substrate", branch = "master
[features]
default = ["std"]
std = [
"bp-message-dispatch/std",
"bp-messages/std",
"bp-runtime/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"hash-db/std",
"pallet-bridge-dispatch/std",
"log/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-transaction-payment/std",
@@ -60,7 +57,6 @@ std = [
"sp-trie/std",
]
runtime-benchmarks = [
"ed25519-dalek/u64_backend",
"pallet-balances",
"pallet-bridge-grandpa/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
-181
View File
@@ -1,181 +0,0 @@
# Helpers for Messages Module Integration
The [`messages`](./src/messages.rs) module of this crate contains a bunch of helpers for integrating
messages module into your runtime. Basic prerequisites of these helpers are:
- we're going to bridge Substrate-based chain with another Substrate-based chain;
- both chains have [messages module](../../modules/messages/README.md), Substrate bridge
module and the [call dispatch module](../../modules/dispatch/README.md);
- all message lanes are identical and may be used to transfer the same messages;
- the messages sent over the bridge are dispatched using
[call dispatch module](../../modules/dispatch/README.md);
- the messages are `bp_message_dispatch::MessagePayload` structures, where `call` field is
encoded `Call` of the target chain. This means that the `Call` is opaque to the
[messages module](../../modules/messages/README.md) instance at the source chain.
It is pre-encoded by the message submitter;
- all proofs in the [messages module](../../modules/messages/README.md) transactions are
based on the storage proofs from the bridged chain: storage proof of the outbound message (value
from the `pallet_bridge_messages::Store::MessagePayload` map), storage proof of the outbound lane
state (value from the `pallet_bridge_messages::Store::OutboundLanes` map) and storage proof of the
inbound lane state (value from the `pallet_bridge_messages::Store::InboundLanes` map);
- storage proofs are built at the finalized headers of the corresponding chain. So all message lane
transactions with proofs are verifying storage proofs against finalized chain headers from
Substrate bridge module.
**IMPORTANT NOTE**: after reading this document, you may refer to our test runtimes
([rialto_messages.rs](../millau/runtime/src/rialto_messages.rs) and/or
[millau_messages.rs](../rialto/runtime/src/millau_messages.rs)) to see how to use these helpers.
## Contents
- [`MessageBridge` Trait](#messagebridge-trait)
- [`ChainWithMessages` Trait ](#ChainWithMessages-trait)
- [Helpers for the Source Chain](#helpers-for-the-source-chain)
- [Helpers for the Target Chain](#helpers-for-the-target-chain)
## `MessageBridge` Trait
The essence of your integration will be a struct that implements a `MessageBridge` trait. It has
single method (`MessageBridge::bridged_balance_to_this_balance`), used to convert from bridged chain
tokens into this chain tokens. The bridge also requires two associated types to be specified -
`ThisChain` and `BridgedChain`.
Worth to say that if you're going to use hardcoded constant (conversion rate) in the
`MessageBridge::bridged_balance_to_this_balance` method (or in any other method of
`ThisChainWithMessages` or `BridgedChainWithMessages` traits), then you should take a
look at the
[messages parameters functionality](../../modules/messages/README.md#Non-Essential-Functionality).
They allow pallet owner to update constants more frequently than runtime upgrade happens.
## `ChainWithMessages` Trait
The trait is quite simple and can easily be implemented - you just need to specify types used at the
corresponding chain. There is single exception, though (it may be changed in the future):
- `ChainWithMessages::MessagesInstance`: this is used to compute runtime storage keys. There
may be several instances of messages pallet, included in the Runtime. Every instance stores
messages and these messages stored under different keys. When we are verifying storage proofs from
the bridged chain, we should know which instance we're talking to. This is fine, but there's
significant inconvenience with that - this chain runtime must have the same messages pallet
instance. This does not necessarily mean that we should use the same instance on both chains -
this instance may be used to bridge with another chain/instance, or may not be used at all.
## `ThisChainWithMessages` Trait
This trait represents this chain from bridge point of view. Let's review every method of this trait:
- `ThisChainWithMessages::is_message_accepted`: is used to check whether given lane accepts
messages. The send-message origin is passed to the function, so you may e.g. verify that only
given pallet is able to send messages over selected lane. **IMPORTANT**: if you assume that the
message must be paid by the sender, you must ensure that the sender origin has linked the account
for paying message delivery and dispatch fee.
- `ThisChainWithMessages::maximal_pending_messages_at_outbound_lane`: you should return maximal
number of pending (undelivered) messages from this function. Returning small values would require
relayers to operate faster and could make message sending logic more complicated. On the other
hand, returning large values could lead to chain state growth.
- `ThisChainWithMessages::estimate_delivery_confirmation_transaction`: you'll need to return
estimated size and dispatch weight of the delivery confirmation transaction (that happens on
this chain) from this function.
- `ThisChainWithMessages::transaction_payment`: you'll need to return fee that the submitter
must pay for given transaction on this chain. Normally, you would use transaction payment pallet
for this. However, if your chain has non-zero fee multiplier set, this would mean that the
payment will be computed using current value of this multiplier. But since this transaction
will be submitted in the future, you may want to choose other value instead. Otherwise,
non-altruistic relayer may choose not to submit this transaction until number of transactions
will decrease.
## `BridgedChainWithMessages` Trait
This trait represents this chain from bridge point of view. Let's review every method of this trait:
- `BridgedChainWithMessages::maximal_extrinsic_size`: you will need to return the maximal
extrinsic size of the target chain from this function.
- `MessageBridge::message_weight_limits`: you'll need to return a range of
dispatch weights that the outbound message may take at the target chain. Please keep in mind that
our helpers assume that the message is an encoded call of the target chain. But we never decode
this call at the source chain. So you can't simply get dispatch weight from pre-dispatch
information. Instead there are two options to prepare this range: if you know which calls are to
be sent over your bridge, then you may just return weight ranges for these particular calls.
Otherwise, if you're going to accept all kinds of calls, you may just return range `[0; maximal
incoming message dispatch weight]`. If you choose the latter, then you shall remember that the
delivery transaction itself has some weight, so you can't accept messages with weight equal to
maximal weight of extrinsic at the target chain. In our test chains, we reject all messages that
have declared dispatch weight larger than 50% of the maximal bridged extrinsic weight.
- `MessageBridge::estimate_delivery_transaction`: you will need to return estimated dispatch weight and
size of the delivery transaction that delivers a given message to the target chain. The transaction
weight must or must not include the weight of pay-dispatch-fee operation, depending on the value
of `include_pay_dispatch_fee_cost` argument.
- `MessageBridge::transaction_payment`: you'll need to return fee that the submitter
must pay for given transaction on bridged chain. The best case is when you have the same conversion
formula on both chains - then you may just reuse the `ThisChainWithMessages::transaction_payment`
implementation. Otherwise, you'll need to hardcode this formula into your runtime.
## Helpers for the Source Chain
The helpers for the Source Chain reside in the `source` submodule of the
[`messages`](./src/messages.rs) module. The structs are: `FromThisChainMessagePayload`,
`FromBridgedChainMessagesDeliveryProof`, `FromThisChainMessageVerifier`. And the helper functions
are: `maximal_message_size`, `verify_chain_message`, `verify_messages_delivery_proof` and
`estimate_message_dispatch_and_delivery_fee`.
`FromThisChainMessagePayload` is a message that the sender sends through our bridge. It is the
`bp_message_dispatch::MessagePayload`, where `call` field is encoded target chain call. So
at this chain we don't see internals of this call - we just know its size.
`FromThisChainMessageVerifier` is an implementation of `bp_messages::LaneMessageVerifier`. It
has following checks in its `verify_message` method:
1. it'll verify that the used outbound lane is enabled in our runtime;
1. it'll reject messages if there are too many undelivered outbound messages at this lane. The
sender need to wait while relayers will do their work before sending the message again;
1. it'll reject a message if it has the wrong dispatch origin declared. Like if the submitter is not
the root of this chain, but it tries to dispatch the message at the target chain using
`bp_message_dispatch::CallOrigin::SourceRoot` origin. Or he has provided wrong signature
in the `bp_message_dispatch::CallOrigin::TargetAccount` origin;
1. it'll reject a message if the delivery and dispatch fee that the submitter wants to pay is lesser
than the fee that is computed using the `estimate_message_dispatch_and_delivery_fee` function.
`estimate_message_dispatch_and_delivery_fee` returns a minimal fee that the submitter needs to pay
for sending a given message. The fee includes: payment for the delivery transaction at the target
chain, payment for delivery confirmation transaction on this chain, payment for `Call` dispatch at
the target chain and relayer interest.
`FromBridgedChainMessagesDeliveryProof` holds the lane identifier and the storage proof of this
inbound lane state at the bridged chain. This also holds the hash of the target chain header, that
was used to generate this storage proof. The proof is verified by the
`verify_messages_delivery_proof`, which simply checks that the target chain header is finalized
(using Substrate bridge module) and then reads the inbound lane state from the proof.
`verify_chain_message` function checks that the message may be delivered to the bridged chain. There
are two main checks:
1. that the message size is less than or equal to the `2/3` of maximal extrinsic size at the target
chain. We leave `1/3` for signed extras and for the storage proof overhead;
1. that the message dispatch weight is less than or equal to the `1/2` of maximal normal extrinsic
weight at the target chain. We leave `1/2` for the delivery transaction overhead.
## Helpers for the Target Chain
The helpers for the target chain reside in the `target` submodule of the
[`messages`](./src/messages.rs) module. The structs are: `FromBridgedChainMessagePayload`,
`FromBridgedChainMessagesProof`, `FromBridgedChainMessagesProof`. And the helper functions are:
`maximal_incoming_message_dispatch_weight`, `maximal_incoming_message_size` and
`verify_messages_proof`.
`FromBridgedChainMessagePayload` corresponds to the `FromThisChainMessagePayload` at the bridged
chain. We expect that messages with this payload are stored in the `OutboundMessages` storage map of
the [messages module](../../modules/messages/README.md). This map is used to build
`FromBridgedChainMessagesProof`. The proof holds the lane id, range of message nonces included in
the proof, storage proof of `OutboundMessages` entries and the hash of bridged chain header that has
been used to build the proof. Additionally, there's storage proof may contain the proof of outbound
lane state. It may be required to prune `relayers` entries at this chain (see
[messages module documentation](../../modules/messages/README.md#What-about-other-Constants-in-the-Messages-Module-Configuration-Trait)
for details). This proof is verified by the `verify_messages_proof` function.
+2 -2
View File
@@ -120,10 +120,10 @@ macro_rules! assert_bridge_messages_pallet_types(
use pallet_bridge_messages::Config as MessagesConfig;
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload<$bridge>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundMessageFee, BalanceOf<ThisChain<$bridge>>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundPayload, FromBridgedChainMessagePayload<$bridge>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundPayload, FromBridgedChainMessagePayload);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundMessageFee, BalanceOf<BridgedChain<$bridge>>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf<BridgedChain<$bridge>>);
+58 -324
View File
@@ -20,32 +20,21 @@
//! pallet is used to dispatch incoming messages. Message identified by a tuple
//! of to elements - message lane id and message nonce.
use bp_message_dispatch::MessageDispatch as _;
use bp_messages::{
source_chain::LaneMessageVerifier,
target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages},
InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData,
};
use bp_runtime::{
messages::{DispatchFeePayment, MessageDispatchResult},
ChainId, Size, StorageProofChecker,
};
use codec::{Decode, DecodeLimit, Encode};
use frame_support::{
traits::{Currency, ExistenceRequirement},
weights::{Weight, WeightToFeePolynomial},
RuntimeDebug,
};
use bp_runtime::{messages::MessageDispatchResult, ChainId, Size, StorageProofChecker};
use codec::{Decode, Encode};
use frame_support::{traits::Currency, weights::Weight, RuntimeDebug};
use hash_db::Hasher;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedDiv, CheckedMul, Saturating, Zero},
traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedDiv, CheckedMul, Saturating},
FixedPointNumber, FixedPointOperand, FixedU128,
};
use sp_std::{
cmp::PartialOrd, convert::TryFrom, fmt::Debug, marker::PhantomData, ops::RangeInclusive,
vec::Vec,
};
use sp_std::{cmp::PartialOrd, convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec};
use sp_trie::StorageProof;
/// Bidirectional message bridge.
@@ -136,16 +125,9 @@ pub trait BridgedChainWithMessages: ChainWithMessages {
/// Maximal extrinsic size at Bridged chain.
fn maximal_extrinsic_size() -> u32;
/// Returns feasible weights range for given message payload at the Bridged chain.
///
/// If message is being sent with the weight that is out of this range, then it
/// should be rejected.
///
/// Weights returned from this function shall not include transaction overhead
/// (like weight of signature and signed extensions verification), because they're
/// already accounted by the `weight_of_delivery_transaction`. So this function should
/// return pure call dispatch weights range.
fn message_weight_limits(message_payload: &[u8]) -> RangeInclusive<Self::Weight>;
/// Returns `true` if message dispatch weight is withing expected limits. `false` means
/// that the message is too heavy to be sent over the bridge and shall be rejected.
fn verify_dispatch_weight(message_payload: &[u8]) -> bool;
/// Estimate size and weight of single message delivery transaction at the Bridged chain.
fn estimate_delivery_transaction(
@@ -218,12 +200,7 @@ pub mod source {
pub type BridgedChainOpaqueCall = Vec<u8>;
/// Message payload for This -> Bridged chain messages.
pub type FromThisChainMessagePayload<B> = bp_message_dispatch::MessagePayload<
AccountIdOf<ThisChain<B>>,
SignerOf<BridgedChain<B>>,
SignatureOf<BridgedChain<B>>,
BridgedChainOpaqueCall,
>;
pub type FromThisChainMessagePayload = Vec<u8>;
/// Messages delivery proof from bridged chain:
///
@@ -260,7 +237,6 @@ pub mod source {
/// This verifier assumes following:
///
/// - all message lanes are equivalent, so all checks are the same;
/// - messages are being dispatched using `pallet-bridge-dispatch` pallet on the target chain.
///
/// Following checks are made:
///
@@ -288,7 +264,7 @@ pub mod source {
LaneMessageVerifier<
OriginOf<ThisChain<B>>,
AccountIdOf<ThisChain<B>>,
FromThisChainMessagePayload<B>,
FromThisChainMessagePayload,
BalanceOf<ThisChain<B>>,
> for FromThisChainMessageVerifier<B>
where
@@ -305,7 +281,7 @@ pub mod source {
delivery_and_dispatch_fee: &BalanceOf<ThisChain<B>>,
lane: &LaneId,
lane_outbound_data: &OutboundLaneData,
payload: &FromThisChainMessagePayload<B>,
payload: &FromThisChainMessagePayload,
) -> Result<(), Self::Error> {
// reject message if lane is blocked
if !ThisChain::<B>::is_message_accepted(submitter, lane) {
@@ -321,24 +297,6 @@ pub mod source {
return Err(TOO_MANY_PENDING_MESSAGES)
}
// Do the dispatch-specific check. We assume that the target chain uses
// `Dispatch`, so we verify the message accordingly.
let raw_origin_or_err: Result<
frame_system::RawOrigin<AccountIdOf<ThisChain<B>>>,
OriginOf<ThisChain<B>>,
> = submitter.clone().into();
if let Ok(raw_origin) = raw_origin_or_err {
pallet_bridge_dispatch::verify_message_origin(&raw_origin, payload)
.map(drop)
.map_err(|_| BAD_ORIGIN)?;
} else {
// so what it means that we've failed to convert origin to the
// `frame_system::RawOrigin`? now it means that the custom pallet origin has
// been used to send the message. Do we need to verify it? The answer is no,
// because pallet may craft any origin (e.g. root) && we can't verify whether it
// is valid, or not.
};
let minimal_fee_in_this_tokens = estimate_message_dispatch_and_delivery_fee::<B>(
payload,
B::RELAYER_FEE_PERCENT,
@@ -365,10 +323,9 @@ pub mod source {
/// may be 'mined' by the target chain. But the lane may have its own checks (e.g. fee
/// check) that would reject message (see `FromThisChainMessageVerifier`).
pub fn verify_chain_message<B: MessageBridge>(
payload: &FromThisChainMessagePayload<B>,
payload: &FromThisChainMessagePayload,
) -> Result<(), &'static str> {
let weight_limits = BridgedChain::<B>::message_weight_limits(&payload.call);
if !weight_limits.contains(&payload.weight.into()) {
if !BridgedChain::<B>::verify_dispatch_weight(payload) {
return Err("Incorrect message weight declared")
}
@@ -382,7 +339,7 @@ pub mod source {
// is enormously large, it should be several dozens/hundreds of bytes. The delivery
// transaction also contains signatures and signed extensions. Because of this, we reserve
// 1/3 of the the maximal extrinsic weight for this data.
if payload.call.len() > maximal_message_size::<B>() as usize {
if payload.len() > maximal_message_size::<B>() as usize {
return Err("The message is too large to be sent over the lane")
}
@@ -395,7 +352,7 @@ pub mod source {
/// The fee is paid in This chain Balance, but we use Bridged chain balance to avoid additional
/// conversions. Returns `None` if overflow has happened.
pub fn estimate_message_dispatch_and_delivery_fee<B: MessageBridge>(
payload: &FromThisChainMessagePayload<B>,
payload: &FromThisChainMessagePayload,
relayer_fee_percent: u32,
bridged_to_this_conversion_rate: Option<FixedU128>,
) -> Result<BalanceOf<ThisChain<B>>, &'static str> {
@@ -403,13 +360,8 @@ pub mod source {
//
// if we're going to pay dispatch fee at the target chain, then we don't include weight
// of the message dispatch in the delivery transaction cost
let pay_dispatch_fee_at_target_chain =
payload.dispatch_fee_payment == DispatchFeePayment::AtTargetChain;
let delivery_transaction = BridgedChain::<B>::estimate_delivery_transaction(
&payload.encode(),
pay_dispatch_fee_at_target_chain,
if pay_dispatch_fee_at_target_chain { 0.into() } else { payload.weight.into() },
);
let delivery_transaction =
BridgedChain::<B>::estimate_delivery_transaction(&payload.encode(), true, 0.into());
let delivery_transaction_fee = BridgedChain::<B>::transaction_payment(delivery_transaction);
// the fee (in This tokens) of all transactions that are made on This chain
@@ -477,20 +429,8 @@ pub mod source {
pub mod target {
use super::*;
/// Call origin for Bridged -> This chain messages.
pub type FromBridgedChainMessageCallOrigin<B> = bp_message_dispatch::CallOrigin<
AccountIdOf<BridgedChain<B>>,
SignerOf<ThisChain<B>>,
SignatureOf<ThisChain<B>>,
>;
/// Decoded Bridged -> This message payload.
pub type FromBridgedChainMessagePayload<B> = bp_message_dispatch::MessagePayload<
AccountIdOf<BridgedChain<B>>,
SignerOf<ThisChain<B>>,
SignatureOf<ThisChain<B>>,
FromBridgedChainEncodedMessageCall<CallOf<ThisChain<B>>>,
>;
pub type FromBridgedChainMessagePayload = Vec<u8>;
/// Messages proof from bridged chain:
///
@@ -522,35 +462,6 @@ pub mod target {
}
}
/// Encoded Call of This chain as it is transferred over bridge.
///
/// Our Call is opaque (`Vec<u8>`) for Bridged chain. So it is encoded, prefixed with
/// vector length. Custom decode implementation here is exactly to deal with this.
#[derive(Decode, Encode, RuntimeDebug, PartialEq)]
pub struct FromBridgedChainEncodedMessageCall<DecodedCall> {
encoded_call: Vec<u8>,
_marker: PhantomData<DecodedCall>,
}
impl<DecodedCall> FromBridgedChainEncodedMessageCall<DecodedCall> {
/// Create encoded call.
pub fn new(encoded_call: Vec<u8>) -> Self {
FromBridgedChainEncodedMessageCall { encoded_call, _marker: PhantomData::default() }
}
}
impl<DecodedCall: Decode> From<FromBridgedChainEncodedMessageCall<DecodedCall>>
for Result<DecodedCall, ()>
{
fn from(encoded_call: FromBridgedChainEncodedMessageCall<DecodedCall>) -> Self {
DecodedCall::decode_with_depth_limit(
sp_api::MAX_EXTRINSIC_DEPTH,
&mut &encoded_call.encoded_call[..],
)
.map_err(drop)
}
}
/// Dispatching Bridged -> This chain messages.
#[derive(RuntimeDebug, Clone, Copy)]
pub struct FromBridgedChainMessageDispatch<B, ThisRuntime, ThisCurrency, ThisDispatchInstance> {
@@ -563,60 +474,33 @@ pub mod target {
where
BalanceOf<ThisChain<B>>: Saturating + FixedPointOperand,
ThisDispatchInstance: 'static,
ThisRuntime: pallet_bridge_dispatch::Config<
ThisDispatchInstance,
BridgeMessageId = (LaneId, MessageNonce),
> + pallet_transaction_payment::Config,
ThisRuntime: pallet_transaction_payment::Config,
<ThisRuntime as pallet_transaction_payment::Config>::OnChargeTransaction:
pallet_transaction_payment::OnChargeTransaction<
ThisRuntime,
Balance = BalanceOf<ThisChain<B>>,
>,
ThisCurrency: Currency<AccountIdOf<ThisChain<B>>, Balance = BalanceOf<ThisChain<B>>>,
pallet_bridge_dispatch::Pallet<ThisRuntime, ThisDispatchInstance>:
bp_message_dispatch::MessageDispatch<
AccountIdOf<ThisChain<B>>,
(LaneId, MessageNonce),
Message = FromBridgedChainMessagePayload<B>,
>,
{
type DispatchPayload = FromBridgedChainMessagePayload<B>;
type DispatchPayload = FromBridgedChainMessagePayload;
fn dispatch_weight(
message: &DispatchMessage<Self::DispatchPayload, BalanceOf<BridgedChain<B>>>,
_message: &DispatchMessage<Self::DispatchPayload, BalanceOf<BridgedChain<B>>>,
) -> frame_support::weights::Weight {
message.data.payload.as_ref().map(|payload| payload.weight).unwrap_or(0)
0
}
fn dispatch(
relayer_account: &AccountIdOf<ThisChain<B>>,
_relayer_account: &AccountIdOf<ThisChain<B>>,
message: DispatchMessage<Self::DispatchPayload, BalanceOf<BridgedChain<B>>>,
) -> MessageDispatchResult {
let message_id = (message.key.lane_id, message.key.nonce);
pallet_bridge_dispatch::Pallet::<ThisRuntime, ThisDispatchInstance>::dispatch(
B::BRIDGED_CHAIN_ID,
B::THIS_CHAIN_ID,
message_id,
message.data.payload.map_err(drop),
|dispatch_origin, dispatch_weight| {
let unadjusted_weight_fee = ThisRuntime::WeightToFee::calc(&dispatch_weight);
let fee_multiplier =
pallet_transaction_payment::Pallet::<ThisRuntime>::next_fee_multiplier();
let adjusted_weight_fee =
fee_multiplier.saturating_mul_int(unadjusted_weight_fee);
if !adjusted_weight_fee.is_zero() {
ThisCurrency::transfer(
dispatch_origin,
relayer_account,
adjusted_weight_fee,
ExistenceRequirement::AllowDeath,
)
.map_err(drop)
} else {
Ok(())
}
},
)
log::trace!(target: "runtime::bridge-dispatch", "Incoming message {:?}: {:?}", message_id, message.data.payload);
MessageDispatchResult {
dispatch_result: true,
unspent_weight: 0,
dispatch_fee_paid_during_dispatch: false,
}
}
}
@@ -813,7 +697,8 @@ mod tests {
const THIS_CHAIN_WEIGHT_TO_BALANCE_RATE: Weight = 2;
const BRIDGED_CHAIN_WEIGHT_TO_BALANCE_RATE: Weight = 4;
const BRIDGED_CHAIN_TO_THIS_CHAIN_BALANCE_RATE: u32 = 6;
const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: Weight = 2048;
const BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT: usize = 5;
const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: usize = 2048;
const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
/// Bridge that is deployed on ThisChain and allows sending/receiving messages to/from
@@ -1016,7 +901,7 @@ mod tests {
unreachable!()
}
fn message_weight_limits(_message_payload: &[u8]) -> RangeInclusive<Self::Weight> {
fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
unreachable!()
}
@@ -1074,10 +959,9 @@ mod tests {
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
}
fn message_weight_limits(message_payload: &[u8]) -> RangeInclusive<Self::Weight> {
let begin =
std::cmp::min(BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT, message_payload.len() as Weight);
begin..=BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT
fn verify_dispatch_weight(message_payload: &[u8]) -> bool {
message_payload.len() >= BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT &&
message_payload.len() <= BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT
}
fn estimate_delivery_transaction(
@@ -1102,57 +986,16 @@ mod tests {
OutboundLaneData::default()
}
#[test]
fn message_from_bridged_chain_is_decoded() {
// the message is encoded on the bridged chain
let message_on_bridged_chain =
source::FromThisChainMessagePayload::<OnBridgedChainBridge> {
spec_version: 1,
weight: 100,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtTargetChain,
call: ThisChainCall::Transfer.encode(),
}
.encode();
// and sent to this chain where it is decoded
let message_on_this_chain =
target::FromBridgedChainMessagePayload::<OnThisChainBridge>::decode(
&mut &message_on_bridged_chain[..],
)
.unwrap();
assert_eq!(
message_on_this_chain,
target::FromBridgedChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 100,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtTargetChain,
call: target::FromBridgedChainEncodedMessageCall::<ThisChainCall>::new(
ThisChainCall::Transfer.encode(),
),
}
);
assert_eq!(Ok(ThisChainCall::Transfer), message_on_this_chain.call.into());
}
const TEST_LANE_ID: &LaneId = b"test";
const MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE: MessageNonce = 32;
fn regular_outbound_message_payload() -> source::FromThisChainMessagePayload<OnThisChainBridge>
{
source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 100,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![42],
}
fn regular_outbound_message_payload() -> source::FromThisChainMessagePayload {
vec![42]
}
#[test]
fn message_fee_is_checked_by_verifier() {
const EXPECTED_MINIMAL_FEE: u32 = 5500;
const EXPECTED_MINIMAL_FEE: u32 = 2860;
// payload of the This -> Bridged chain message
let payload = regular_outbound_message_payload();
@@ -1167,25 +1010,6 @@ mod tests {
Ok(ThisChainBalance(EXPECTED_MINIMAL_FEE)),
);
// let's check if estimation is less than hardcoded, if dispatch is paid at target chain
let mut payload_with_pay_on_target = regular_outbound_message_payload();
payload_with_pay_on_target.dispatch_fee_payment = DispatchFeePayment::AtTargetChain;
let fee_at_source =
source::estimate_message_dispatch_and_delivery_fee::<OnThisChainBridge>(
&payload_with_pay_on_target,
OnThisChainBridge::RELAYER_FEE_PERCENT,
None,
)
.expect(
"estimate_message_dispatch_and_delivery_fee failed for pay-at-target-chain message",
);
assert!(
fee_at_source < EXPECTED_MINIMAL_FEE.into(),
"Computed fee {:?} without prepaid dispatch must be less than the fee with prepaid dispatch {}",
fee_at_source,
EXPECTED_MINIMAL_FEE,
);
// and now check that the verifier checks the fee
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
@@ -1207,80 +1031,6 @@ mod tests {
.is_ok(),);
}
#[test]
fn should_disallow_root_calls_from_regular_accounts() {
// payload of the This -> Bridged chain message
let payload = source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 100,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![42],
};
// and now check that the verifier checks the fee
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainOrigin(Ok(frame_system::RawOrigin::Signed(ThisChainAccountId(0)))),
&ThisChainBalance(1_000_000),
TEST_LANE_ID,
&test_lane_outbound_data(),
&payload,
),
Err(source::BAD_ORIGIN)
);
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainOrigin(Ok(frame_system::RawOrigin::None)),
&ThisChainBalance(1_000_000),
TEST_LANE_ID,
&test_lane_outbound_data(),
&payload,
),
Err(source::BAD_ORIGIN)
);
assert!(source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainOrigin(Ok(frame_system::RawOrigin::Root)),
&ThisChainBalance(1_000_000),
TEST_LANE_ID,
&test_lane_outbound_data(),
&payload,
)
.is_ok(),);
}
#[test]
fn should_verify_source_and_target_origin_matching() {
// payload of the This -> Bridged chain message
let payload = source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 100,
origin: bp_message_dispatch::CallOrigin::SourceAccount(ThisChainAccountId(1)),
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![42],
};
// and now check that the verifier checks the fee
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainOrigin(Ok(frame_system::RawOrigin::Signed(ThisChainAccountId(0)))),
&ThisChainBalance(1_000_000),
TEST_LANE_ID,
&test_lane_outbound_data(),
&payload,
),
Err(source::BAD_ORIGIN)
);
assert!(source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainOrigin(Ok(frame_system::RawOrigin::Signed(ThisChainAccountId(1)))),
&ThisChainBalance(1_000_000),
TEST_LANE_ID,
&test_lane_outbound_data(),
&payload,
)
.is_ok(),);
}
#[test]
fn message_is_rejected_when_sent_using_disabled_lane() {
assert_eq!(
@@ -1315,58 +1065,42 @@ mod tests {
#[test]
fn verify_chain_message_rejects_message_with_too_small_declared_weight() {
assert!(source::verify_chain_message::<OnThisChainBridge>(
&source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 5,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![1, 2, 3, 4, 5, 6],
},
)
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
42;
BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT -
1
])
.is_err());
}
#[test]
fn verify_chain_message_rejects_message_with_too_large_declared_weight() {
assert!(source::verify_chain_message::<OnThisChainBridge>(
&source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT + 1,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![1, 2, 3, 4, 5, 6],
},
)
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
42;
BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT -
1
])
.is_err());
}
#[test]
fn verify_chain_message_rejects_message_too_large_message() {
assert!(source::verify_chain_message::<OnThisChainBridge>(
&source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![0; source::maximal_message_size::<OnThisChainBridge>() as usize + 1],
},
)
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as usize + 1
],)
.is_err());
}
#[test]
fn verify_chain_message_accepts_maximal_message() {
assert_eq!(
source::verify_chain_message::<OnThisChainBridge>(
&source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT,
origin: bp_message_dispatch::CallOrigin::SourceRoot,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: vec![0; source::maximal_message_size::<OnThisChainBridge>() as _],
},
),
source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as _
],),
Ok(()),
);
}
@@ -16,10 +16,9 @@
//! Helpers for implementing various message-related runtime API mthods.
use crate::messages::{source::FromThisChainMessagePayload, MessageBridge};
use crate::messages::MessageBridge;
use bp_messages::{LaneId, MessageDetails, MessageNonce};
use codec::Decode;
use sp_std::vec::Vec;
/// Implementation of the `To*OutboundLaneApi::message_details`.
@@ -37,14 +36,12 @@ where
.filter_map(|nonce| {
let message_data =
pallet_bridge_messages::Pallet::<Runtime, MessagesPalletInstance>::outbound_message_data(lane, nonce)?;
let decoded_payload =
FromThisChainMessagePayload::<BridgeConfig>::decode(&mut &message_data.payload[..]).ok()?;
Some(MessageDetails {
nonce,
dispatch_weight: decoded_payload.weight,
dispatch_weight: 0,
size: message_data.payload.len() as _,
delivery_and_dispatch_fee: message_data.fee,
dispatch_fee_payment: decoded_payload.dispatch_fee_payment,
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtTargetChain,
})
})
.collect()
@@ -27,13 +27,8 @@ use crate::messages::{
};
use bp_messages::{storage_keys, MessageData, MessageKey, MessagePayload};
use bp_runtime::{messages::DispatchFeePayment, ChainId};
use codec::Encode;
use ed25519_dalek::{PublicKey, SecretKey, Signer, KEYPAIR_LENGTH, SECRET_KEY_LENGTH};
use frame_support::{
traits::Currency,
weights::{GetDispatchInfo, Weight},
};
use frame_support::weights::{GetDispatchInfo, Weight};
use pallet_bridge_messages::benchmarking::{
MessageDeliveryProofParams, MessageParams, MessageProofParams, ProofSize,
};
@@ -41,49 +36,16 @@ use sp_core::Hasher;
use sp_runtime::traits::{Header, IdentifyAccount, MaybeSerializeDeserialize, Zero};
use sp_std::{fmt::Debug, prelude::*};
use sp_trie::{record_all_keys, trie_types::TrieDBMutV1, LayoutV1, MemoryDB, Recorder, TrieMut};
use sp_version::RuntimeVersion;
/// Return this chain account, used to dispatch message.
pub fn dispatch_account<B>() -> AccountIdOf<ThisChain<B>>
where
B: MessageBridge,
SignerOf<ThisChain<B>>:
From<sp_core::ed25519::Public> + IdentifyAccount<AccountId = AccountIdOf<ThisChain<B>>>,
{
let this_raw_public = PublicKey::from(&dispatch_account_secret());
let this_public: SignerOf<ThisChain<B>> =
sp_core::ed25519::Public::from_raw(this_raw_public.to_bytes()).into();
this_public.into_account()
}
/// Return public key of this chain account, used to dispatch message.
pub fn dispatch_account_secret() -> SecretKey {
// key from the repo example (https://docs.rs/ed25519-dalek/1.0.1/ed25519_dalek/struct.SecretKey.html)
SecretKey::from_bytes(&[
157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, 073,
197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096,
])
.expect("harcoded key is valid")
}
/// Prepare outbound message for the `send_message` call.
pub fn prepare_outbound_message<B>(
params: MessageParams<AccountIdOf<ThisChain<B>>>,
) -> FromThisChainMessagePayload<B>
) -> FromThisChainMessagePayload
where
B: MessageBridge,
BalanceOf<ThisChain<B>>: From<u64>,
{
let message_payload = vec![0; params.size as usize];
let dispatch_origin = bp_message_dispatch::CallOrigin::SourceAccount(params.sender_account);
FromThisChainMessagePayload::<B> {
spec_version: 0,
weight: params.size as _,
origin: dispatch_origin,
call: message_payload,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
}
vec![0; params.size as usize]
}
/// Prepare proof of messages for the `receive_messages_proof` call.
@@ -92,8 +54,6 @@ where
/// proof.
pub fn prepare_message_proof<R, BI, FI, B, BH, BHH>(
params: MessageProofParams,
version: &RuntimeVersion,
endow_amount: BalanceOf<ThisChain<B>>,
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
where
R: frame_system::Config<AccountId = AccountIdOf<ThisChain<B>>>
@@ -115,51 +75,10 @@ where
+ From<sp_core::ed25519::Public>
+ IdentifyAccount<AccountId = AccountIdOf<ThisChain<B>>>,
{
// we'll be dispatching the same call at This chain
let remark = match params.size {
let message_payload = match params.size {
ProofSize::Minimal(ref size) => vec![0u8; *size as _],
_ => vec![],
};
let call: CallOf<ThisChain<B>> = frame_system::Call::remark { remark }.into();
let call_weight = call.get_dispatch_info().weight;
// message payload needs to be signed, because we use `TargetAccount` call origin
// (which is 'heaviest' to verify)
let bridged_account_id: AccountIdOf<BridgedChain<B>> = [0u8; 32].into();
let (this_raw_public, this_raw_signature) = ed25519_sign(
&call,
&bridged_account_id,
version.spec_version,
B::BRIDGED_CHAIN_ID,
B::THIS_CHAIN_ID,
);
let this_public: SignerOf<ThisChain<B>> =
sp_core::ed25519::Public::from_raw(this_raw_public).into();
let this_signature: SignatureOf<ThisChain<B>> =
sp_core::ed25519::Signature::from_raw(this_raw_signature).into();
// if dispatch fee is paid at this chain, endow relayer account
if params.dispatch_fee_payment == DispatchFeePayment::AtTargetChain {
assert_eq!(this_public.clone().into_account(), dispatch_account::<B>());
pallet_balances::Pallet::<R, BI>::make_free_balance_be(
&this_public.clone().into_account(),
endow_amount,
);
}
// prepare message payload that is stored in the Bridged chain storage
let message_payload = bp_message_dispatch::MessagePayload {
spec_version: version.spec_version,
weight: call_weight,
origin: bp_message_dispatch::CallOrigin::<
AccountIdOf<BridgedChain<B>>,
SignerOf<ThisChain<B>>,
SignatureOf<ThisChain<B>>,
>::TargetAccount(bridged_account_id, this_public, this_signature),
dispatch_fee_payment: params.dispatch_fee_payment.clone(),
call: call.encode(),
}
.encode();
// finally - prepare storage proof and update environment
let (state_root, storage_proof) =
@@ -174,11 +93,7 @@ where
nonces_start: *params.message_nonces.start(),
nonces_end: *params.message_nonces.end(),
},
call_weight
.checked_mul(
params.message_nonces.end().saturating_sub(*params.message_nonces.start()) + 1,
)
.expect("too many messages requested by benchmark"),
0,
)
}
@@ -312,40 +227,6 @@ where
bridged_header_hash
}
/// Generate ed25519 signature to be used in
/// `pallet_brdige_call_dispatch::CallOrigin::TargetAccount`.
///
/// Returns public key of the signer and the signature itself.
fn ed25519_sign(
target_call: &impl Encode,
source_account_id: &impl Encode,
target_spec_version: u32,
source_chain_id: ChainId,
target_chain_id: ChainId,
) -> ([u8; 32], [u8; 64]) {
let target_secret = dispatch_account_secret();
let target_public: PublicKey = (&target_secret).into();
let mut target_pair_bytes = [0u8; KEYPAIR_LENGTH];
target_pair_bytes[..SECRET_KEY_LENGTH].copy_from_slice(&target_secret.to_bytes());
target_pair_bytes[SECRET_KEY_LENGTH..].copy_from_slice(&target_public.to_bytes());
let target_pair =
ed25519_dalek::Keypair::from_bytes(&target_pair_bytes).expect("hardcoded pair is valid");
let signature_message = pallet_bridge_dispatch::account_ownership_digest(
target_call,
source_account_id,
target_spec_version,
source_chain_id,
target_chain_id,
);
let target_origin_signature = target_pair
.try_sign(&signature_message)
.expect("Ed25519 try_sign should not fail in benchmarks");
(target_public.to_bytes(), target_origin_signature.to_bytes())
}
/// Populate trie with dummy keys+values until trie has at least given size.
fn grow_trie<H: Hasher>(mut root: H::Out, mdb: &mut MemoryDB<H>, trie_size: ProofSize) -> H::Out {
let (iterations, leaf_size, minimal_trie_size) = match trie_size {