From 5cc62597c7054cf649137ac325e0c506f9a5d81b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 16 Dec 2020 15:40:16 +0300 Subject: [PATCH] Benchmarks for message delivery transaction (#567) * benchmarks for pallet_message_lane::receive_messages_proof * use CallOrigin::TargetAccount (worst case of CallOrigin) * fmt * closures * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano * fix compilation Co-authored-by: Hernando Castano --- bridges/bin/rialto/runtime/Cargo.toml | 1 + bridges/bin/rialto/runtime/src/lib.rs | 76 +++++- .../bin/rialto/runtime/src/millau_messages.rs | 2 +- bridges/bin/runtime-common/Cargo.toml | 11 + bridges/bin/runtime-common/src/lib.rs | 1 + bridges/bin/runtime-common/src/messages.rs | 12 +- .../src/messages_benchmarking.rs | 144 +++++++++++ .../modules/message-lane/src/benchmarking.rs | 228 +++++++++++++++++- bridges/modules/message-lane/src/lib.rs | 9 +- bridges/modules/substrate/Cargo.toml | 1 + bridges/modules/substrate/src/lib.rs | 12 + 11 files changed, 479 insertions(+), 18 deletions(-) create mode 100644 bridges/bin/runtime-common/src/messages_benchmarking.rs diff --git a/bridges/bin/rialto/runtime/Cargo.toml b/bridges/bin/rialto/runtime/Cargo.toml index 29226e1cb114b..e1ab858a033e7 100644 --- a/bridges/bin/rialto/runtime/Cargo.toml +++ b/bridges/bin/rialto/runtime/Cargo.toml @@ -115,6 +115,7 @@ std = [ "sp-version/std", ] runtime-benchmarks = [ + "bridge-runtime-common/runtime-benchmarks", "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index 04534d48021bf..89d84f2e72bc6 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -806,9 +806,14 @@ impl_runtime_apis! { Module as MessageLaneBench, Config as MessageLaneConfig, MessageParams as MessageLaneMessageParams, + MessageProofParams as MessageLaneMessageProofParams, }; impl MessageLaneConfig for Runtime { + fn bridged_relayer_id() -> Self::InboundRelayer { + Default::default() + } + fn endow_account(account: &Self::AccountId) { pallet_balances::Module::::make_free_balance_be( account, @@ -816,7 +821,7 @@ impl_runtime_apis! { ); } - fn prepare_message( + fn prepare_outbound_message( params: MessageLaneMessageParams, ) -> (millau_messages::ToMillauMessagePayload, Balance) { use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge}; @@ -842,6 +847,75 @@ impl_runtime_apis! { }; (message, 1_000_000_000) } + + fn prepare_message_proof( + params: MessageLaneMessageProofParams, + ) -> (millau_messages::FromMillauMessagesProof, Weight) { + use crate::millau_messages::{Millau, WithMillauMessageBridge}; + use bp_message_lane::MessageKey; + use bridge_runtime_common::{ + messages::ChainWithMessageLanes, + messages_benchmarking::{ed25519_sign, prepare_message_proof}, + }; + use codec::Encode; + use frame_support::weights::GetDispatchInfo; + use pallet_message_lane::storage_keys; + use sp_runtime::traits::Header; + + let call = Call::System(SystemCall::remark(vec![])); + let call_weight = call.get_dispatch_info().weight; + + let millau_account_id: bp_millau::AccountId = Default::default(); + let (rialto_raw_public, rialto_raw_signature) = ed25519_sign( + &call, + &millau_account_id, + ); + let rialto_public = MultiSigner::Ed25519(sp_core::ed25519::Public::from_raw(rialto_raw_public)); + let rialto_signature = MultiSignature::Ed25519(sp_core::ed25519::Signature::from_raw( + rialto_raw_signature, + )); + + let make_millau_message_key = |message_key: MessageKey| storage_keys::message_key::< + Runtime, + ::MessageLaneInstance, + >( + &message_key.lane_id, message_key.nonce, + ).0; + let make_millau_outbound_lane_data_key = |lane_id| storage_keys::outbound_lane_data_key::< + ::MessageLaneInstance, + >( + &lane_id, + ).0; + let make_millau_header = |state_root| bp_millau::Header::new( + 0, + Default::default(), + state_root, + Default::default(), + Default::default(), + ); + + prepare_message_proof::( + params, + make_millau_message_key, + make_millau_outbound_lane_data_key, + make_millau_header, + call_weight, + pallet_bridge_call_dispatch::MessagePayload { + spec_version: VERSION.spec_version, + weight: call_weight, + origin: pallet_bridge_call_dispatch::CallOrigin::< + bp_millau::AccountId, + MultiSigner, + Signature, + >::TargetAccount( + millau_account_id, + rialto_public, + rialto_signature, + ), + call: call.encode(), + }.encode(), + ) + } } add_benchmark!(params, batches, pallet_bridge_eth_poa, BridgeKovan); diff --git a/bridges/bin/rialto/runtime/src/millau_messages.rs b/bridges/bin/rialto/runtime/src/millau_messages.rs index 77139b2bb6ab6..e1395d99b1e51 100644 --- a/bridges/bin/rialto/runtime/src/millau_messages.rs +++ b/bridges/bin/rialto/runtime/src/millau_messages.rs @@ -71,7 +71,7 @@ pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDi >; /// Messages proof for Millau -> Rialto messages. -type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof; +pub type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof; /// Messages delivery proof for Rialto -> Millau messages. type ToMillauMessagesDeliveryProof = messages::source::FromBridgedChainMessagesDeliveryProof; diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index da63005c41b3b..99cc02c20babd 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -9,6 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] } +ed25519-dalek = { version = "1.0", default-features = false, optional = true } hash-db = { version = "0.15.2", default-features = false } # Bridge dependencies @@ -23,7 +24,9 @@ pallet-substrate-bridge = { path = "../../modules/substrate", default-features = # Substrate dependencies frame-support = { 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-state-machine = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false, optional = true } sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false } sp-trie = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false } @@ -39,7 +42,15 @@ std = [ "pallet-bridge-call-dispatch/std", "pallet-message-lane/std", "pallet-substrate-bridge/std", + "sp-core/std", "sp-runtime/std", + "sp-state-machine/std", "sp-std/std", "sp-trie/std", ] +runtime-benchmarks = [ + "ed25519-dalek/u64_backend", + "pallet-message-lane/runtime-benchmarks", + "pallet-substrate-bridge/runtime-benchmarks", + "sp-state-machine", +] diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index 3168383f5eb8f..2842e3b6592a4 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -19,3 +19,4 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod messages; +pub mod messages_benchmarking; diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index 3ec1fb7284905..029707a017ebb 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -27,7 +27,7 @@ use bp_message_lane::{ InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData, }; use bp_runtime::InstanceId; -use codec::{Compact, Decode, Input}; +use codec::{Compact, Decode, Encode, Input}; use frame_support::{traits::Instance, RuntimeDebug}; use hash_db::Hasher; use pallet_substrate_bridge::StorageProofChecker; @@ -96,14 +96,14 @@ pub trait ChainWithMessageLanes { /// Signature type used on the chain. type Signature: Decode; /// Call type on the chain. - type Call: Decode; + type Call: Encode + Decode; /// Type of weight that is used on the chain. This would almost always be a regular /// `frame_support::weight::Weight`. But since the meaning of weight on different chains /// may be different, the `WeightOf<>` construct is used to avoid confusion between /// different weights. type Weight: From + PartialOrd; /// Type of balances that is used on the chain. - type Balance: Decode + CheckedAdd + CheckedDiv + CheckedMul + PartialOrd + From + Copy; + type Balance: Encode + Decode + CheckedAdd + CheckedDiv + CheckedMul + PartialOrd + From + Copy; /// Instance of the message-lane pallet. type MessageLaneInstance: Instance; @@ -327,6 +327,12 @@ pub mod target { /// Message payload for Bridged -> This messages. pub struct FromBridgedChainMessagePayload(pub(crate) FromBridgedChainDecodedMessagePayload); + impl From> for FromBridgedChainMessagePayload { + fn from(decoded_payload: FromBridgedChainDecodedMessagePayload) -> Self { + Self(decoded_payload) + } + } + impl Decode for FromBridgedChainMessagePayload { fn decode(input: &mut I) -> Result { // for bridged chain our Calls are opaque - they're encoded to Vec by submitter diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs new file mode 100644 index 0000000000000..e3dd705f8fc35 --- /dev/null +++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs @@ -0,0 +1,144 @@ +// 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 . + +//! Everything required to run benchmarks of message-lanes, based on +//! `bridge_runtime_common::messages` implementation. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::messages::{target::FromBridgedChainMessagesProof, BalanceOf, BridgedChain, HashOf, MessageBridge}; + +use bp_message_lane::{LaneId, MessageData, MessageKey, MessagePayload}; +use codec::Encode; +use ed25519_dalek::{PublicKey, SecretKey, Signer, KEYPAIR_LENGTH, SECRET_KEY_LENGTH}; +use frame_support::weights::Weight; +use pallet_message_lane::benchmarking::MessageProofParams; +use sp_core::Hasher; +use sp_runtime::traits::Header; +use sp_std::prelude::*; +use sp_trie::{read_trie_value_with, trie_types::TrieDBMut, Layout, MemoryDB, Recorder, StorageProof, TrieMut}; + +/// Generate ed25519 signature to be used in `pallet_brdige_call_dispatch::CallOrigin::TargetAccount`. +/// +/// Returns public key of the signer and the signature itself. +pub fn ed25519_sign(target_call: &impl Encode, source_account_id: &impl Encode) -> ([u8; 32], [u8; 64]) { + // key from the repo example (https://docs.rs/ed25519-dalek/1.0.1/ed25519_dalek/struct.SecretKey.html) + let target_secret = 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"); + 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 mut signature_message = Vec::new(); + target_call.encode_to(&mut signature_message); + source_account_id.encode_to(&mut signature_message); + 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()) +} + +/// Prepare proof of messages for the `receive_messages_proof` call. +pub fn prepare_message_proof( + params: MessageProofParams, + make_bridged_message_storage_key: MM, + make_bridged_outbound_lane_data_key: ML, + make_bridged_header: MH, + message_dispatch_weight: Weight, + message_payload: MessagePayload, +) -> (FromBridgedChainMessagesProof, Weight) +where + B: MessageBridge, + H: Hasher, + R: pallet_substrate_bridge::Config, + ::Hash: Into>>, + MM: Fn(MessageKey) -> Vec, + ML: Fn(LaneId) -> Vec, + MH: Fn(H::Out) -> ::Header, +{ + // prepare Bridged chain storage with messages and (optionally) outbound lane state + let message_count = params + .message_nonces + .end() + .saturating_sub(*params.message_nonces.start()) + + 1; + let mut storage_keys = Vec::with_capacity(message_count as usize + 1); + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = TrieDBMut::::new(&mut mdb, &mut root); + + // insert messages + for nonce in params.message_nonces.clone() { + let message_key = MessageKey { + lane_id: params.lane, + nonce, + }; + let message_data = MessageData { + fee: BalanceOf::>::from(0), + payload: message_payload.clone(), + }; + let storage_key = make_bridged_message_storage_key(message_key); + trie.insert(&storage_key, &message_data.encode()) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + storage_keys.push(storage_key); + } + + // insert outbound lane state + if let Some(outbound_lane_data) = params.outbound_lane_data { + let storage_key = make_bridged_outbound_lane_data_key(params.lane); + trie.insert(&storage_key, &outbound_lane_data.encode()) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + storage_keys.push(storage_key); + } + } + + // generate storage proof to be delivered to This chain + let mut proof_recorder = Recorder::::new(); + for storage_key in storage_keys { + read_trie_value_with::, _, _>(&mdb, &root, &storage_key, &mut proof_recorder) + .map_err(|_| "read_trie_value_with has failed") + .expect("read_trie_value_with should not fail in benchmarks"); + } + let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect(); + + // prepare Bridged chain header and insert it into the Substrate pallet + let bridged_header = make_bridged_header(root); + let bridged_header_hash = bridged_header.hash(); + pallet_substrate_bridge::initialize_for_benchmarks::(bridged_header); + + ( + ( + bridged_header_hash.into(), + StorageProof::new(storage_proof), + params.lane, + *params.message_nonces.start(), + *params.message_nonces.end(), + ), + message_dispatch_weight + .checked_mul(message_count) + .expect("too many messages requested by benchmark"), + ) +} diff --git a/bridges/modules/message-lane/src/benchmarking.rs b/bridges/modules/message-lane/src/benchmarking.rs index e56038a555970..67b69345ee966 100644 --- a/bridges/modules/message-lane/src/benchmarking.rs +++ b/bridges/modules/message-lane/src/benchmarking.rs @@ -16,14 +16,16 @@ //! Message lane pallet benchmarking. -use crate::{Call, Instance}; +use crate::{inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane, Call, Instance}; -use bp_message_lane::{LaneId, MessageData, MessageNonce}; +use bp_message_lane::{ + target_chain::SourceHeaderChain, InboundLaneData, LaneId, MessageData, MessageNonce, OutboundLaneData, +}; use frame_benchmarking::{account, benchmarks_instance}; -use frame_support::traits::Get; +use frame_support::{traits::Get, weights::Weight}; use frame_system::RawOrigin; use num_traits::Zero; -use sp_std::prelude::*; +use sp_std::{convert::TryInto, ops::RangeInclusive, prelude::*}; /// Message crafted with this size factor should be the largest possible message. pub const WORST_MESSAGE_SIZE_FACTOR: u32 = 1000; @@ -43,27 +45,52 @@ pub struct MessageParams { 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, + /// If `Some`, the proof needs to include this outbound lane data. + pub outbound_lane_data: Option, +} + /// Trait that must be implemented by runtime. pub trait Config: crate::Config { + /// Return id of relayer account at the bridged chain. + fn bridged_relayer_id() -> Self::InboundRelayer; /// 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_message(params: MessageParams) -> (Self::OutboundPayload, Self::OutboundMessageFee); + fn prepare_outbound_message( + params: MessageParams, + ) -> (Self::OutboundPayload, Self::OutboundMessageFee); + /// Prepare messages proof to receive by the module. + fn prepare_message_proof( + params: MessageProofParams, + ) -> ( + >::MessagesProof, + Weight, + ); } 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 maximal for the target chain. + // + // Results of this benchmark may be directly used in the `send_message`. send_message_worst_case { - let i in 1..100; - let lane_id = bench_lane_id(); - let sender = account("sender", i, SEED); + let sender = account("sender", 0, SEED); T::endow_account(&sender); // 'send' messages that are to be pruned when our message is sent @@ -72,11 +99,181 @@ benchmarks_instance! { } confirm_message_delivery::(T::MaxMessagesToPruneAtOnce::get()); - let (payload, fee) = T::prepare_message(MessageParams { + let (payload, fee) = T::prepare_outbound_message(MessageParams { size_factor: WORST_MESSAGE_SIZE_FACTOR, sender_account: sender.clone(), }); }: send_message(RawOrigin::Signed(sender), lane_id, payload, fee) + verify { + assert_eq!( + crate::Module::::outbound_latest_generated_nonce(bench_lane_id()), + T::MaxMessagesToPruneAtOnce::get() + 1, + ); + } + + // 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); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: bench_lane_id(), + message_nonces: 1..=1, + outbound_lane_data: None, + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, dispatch_weight) + verify { + assert_eq!( + crate::Module::::inbound_latest_received_nonce(bench_lane_id()), + 1, + ); + } + + // 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); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: bench_lane_id(), + message_nonces: 1..=2, + outbound_lane_data: None, + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, dispatch_weight) + verify { + assert_eq!( + crate::Module::::inbound_latest_received_nonce(bench_lane_id()), + 2, + ); + } + + // 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::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: bench_lane_id(), + message_nonces: 21..=21, + outbound_lane_data: Some(OutboundLaneData { + oldest_unpruned_nonce: 21, + latest_received_nonce: 20, + latest_generated_nonce: 21, + }), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, dispatch_weight) + verify { + assert_eq!( + crate::Module::::inbound_latest_received_nonce(bench_lane_id()), + 21, + ); + assert_eq!( + crate::Module::::inbound_latest_confirmed_nonce(bench_lane_id()), + 20, + ); + } + + // + // Benchmarks for manual checks. + // + + // 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..T::MaxMessagesInDeliveryTransaction::get() + .try_into() + .expect("Value of MaxMessagesInDeliveryTransaction is too large"); + + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: bench_lane_id(), + message_nonces: 1..=i as _, + outbound_lane_data: None, + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, dispatch_weight) + verify { + assert_eq!( + crate::Module::::inbound_latest_received_nonce(bench_lane_id()), + i as MessageNonce, + ); + } + + // 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..T::MaxMessagesInDeliveryTransaction::get() + .try_into() + .expect("Value of MaxMessagesInDeliveryTransaction is too large"); + + 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::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: 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, + }), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, dispatch_weight) + verify { + assert_eq!( + crate::Module::::inbound_latest_received_nonce(bench_lane_id()), + 20 + i as MessageNonce, + ); + assert_eq!( + crate::Module::::inbound_latest_confirmed_nonce(bench_lane_id()), + 20, + ); + } } fn bench_lane_id() -> LaneId { @@ -84,7 +281,7 @@ fn bench_lane_id() -> LaneId { } fn send_regular_message, I: Instance>() { - let mut outbound_lane = crate::outbound_lane::(bench_lane_id()); + let mut outbound_lane = outbound_lane::(bench_lane_id()); outbound_lane.send_message(MessageData { payload: vec![], fee: Zero::zero(), @@ -92,6 +289,15 @@ fn send_regular_message, I: Instance>() { } fn confirm_message_delivery, I: Instance>(nonce: MessageNonce) { - let mut outbound_lane = crate::outbound_lane::(bench_lane_id()); + let mut outbound_lane = outbound_lane::(bench_lane_id()); assert!(outbound_lane.confirm_delivery(nonce).is_some()); } + +fn receive_messages, I: Instance>(nonce: MessageNonce) { + let mut inbound_lane_storage = inbound_lane_storage::(bench_lane_id()); + inbound_lane_storage.set_data(InboundLaneData { + relayers: vec![(1, nonce, T::bridged_relayer_id())].into_iter().collect(), + latest_received_nonce: nonce, + latest_confirmed_nonce: 0, + }); +} diff --git a/bridges/modules/message-lane/src/lib.rs b/bridges/modules/message-lane/src/lib.rs index 77bfc72f2caaf..60cd2ce584a8d 100644 --- a/bridges/modules/message-lane/src/lib.rs +++ b/bridges/modules/message-lane/src/lib.rs @@ -558,11 +558,16 @@ fn ensure_operational, I: Instance>() -> Result<(), Error> { /// Creates new inbound lane object, backed by runtime storage. fn inbound_lane, I: Instance>(lane_id: LaneId) -> InboundLane> { - InboundLane::new(RuntimeInboundLaneStorage { + InboundLane::new(inbound_lane_storage::(lane_id)) +} + +/// Creates new runtime inbound lane storage. +fn inbound_lane_storage, I: Instance>(lane_id: LaneId) -> RuntimeInboundLaneStorage { + RuntimeInboundLaneStorage { lane_id, cached_data: RefCell::new(None), _phantom: Default::default(), - }) + } } /// Creates new outbound lane object, backed by runtime storage. diff --git a/bridges/modules/substrate/Cargo.toml b/bridges/modules/substrate/Cargo.toml index 03da6a16526f8..44022d6779b07 100644 --- a/bridges/modules/substrate/Cargo.toml +++ b/bridges/modules/substrate/Cargo.toml @@ -47,3 +47,4 @@ std = [ "sp-std/std", "sp-trie/std", ] +runtime-benchmarks = [] diff --git a/bridges/modules/substrate/src/lib.rs b/bridges/modules/substrate/src/lib.rs index aa01c32964759..fa957c384fb05 100644 --- a/bridges/modules/substrate/src/lib.rs +++ b/bridges/modules/substrate/src/lib.rs @@ -370,6 +370,18 @@ fn ensure_operational() -> Result<(), Error> { } } +/// (Re)initialize bridge with given header for using it in external benchmarks. +#[cfg(feature = "runtime-benchmarks")] +pub fn initialize_for_benchmarks(header: HeaderOf) { + initialize_bridge::(InitializationData { + header, + authority_list: Vec::new(), // we don't verify any proofs in external benchmarks + set_id: 0, + scheduled_change: None, + is_halted: false, + }); +} + /// Since this writes to storage with no real checks this should only be used in functions that were /// called by a trusted origin. fn initialize_bridge(init_params: InitializationData>) {