From 9deea5d251f126e65aa9a116f02d4de176183bdc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 18 Dec 2020 13:59:59 +0300 Subject: [PATCH] Benchmark for message delivery confirmation transaction (#570) * receive_delivery_proof benchmarks * fix compilation * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano * Update modules/message-lane/src/benchmarking.rs Co-authored-by: Hernando Castano Co-authored-by: Hernando Castano --- bridges/bin/rialto/runtime/src/lib.rs | 33 +++ .../bin/rialto/runtime/src/millau_messages.rs | 5 +- bridges/bin/runtime-common/src/messages.rs | 2 +- .../src/messages_benchmarking.rs | 51 ++++- .../modules/message-lane/src/benchmarking.rs | 213 +++++++++++++++++- bridges/modules/message-lane/src/lib.rs | 19 +- 6 files changed, 308 insertions(+), 15 deletions(-) diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index 52cd0896e9..31c08aba22 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -802,6 +802,7 @@ impl_runtime_apis! { use pallet_message_lane::benchmarking::{ Module as MessageLaneBench, Config as MessageLaneConfig, + MessageDeliveryProofParams as MessageLaneMessageDeliveryProofParams, MessageParams as MessageLaneMessageParams, MessageProofParams as MessageLaneMessageProofParams, }; @@ -811,6 +812,10 @@ impl_runtime_apis! { Default::default() } + fn account_balance(account: &Self::AccountId) -> Self::OutboundMessageFee { + pallet_balances::Module::::free_balance(account) + } + fn endow_account(account: &Self::AccountId) { pallet_balances::Module::::make_free_balance_be( account, @@ -913,6 +918,34 @@ impl_runtime_apis! { }.encode(), ) } + + fn prepare_message_delivery_proof( + params: MessageLaneMessageDeliveryProofParams, + ) -> millau_messages::FromMillauMessagesDeliveryProof { + use crate::millau_messages::{Millau, WithMillauMessageBridge}; + use bridge_runtime_common::{ + messages::ChainWithMessageLanes, + messages_benchmarking::prepare_message_delivery_proof, + }; + use sp_runtime::traits::Header; + + prepare_message_delivery_proof::( + params, + |lane_id| pallet_message_lane::storage_keys::inbound_lane_data_key::< + Runtime, + ::MessageLaneInstance, + >( + &lane_id, + ).0, + |state_root| bp_millau::Header::new( + 0, + Default::default(), + state_root, + Default::default(), + Default::default(), + ), + ) + } } 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 6fead2b451..33d254e9b5 100644 --- a/bridges/bin/rialto/runtime/src/millau_messages.rs +++ b/bridges/bin/rialto/runtime/src/millau_messages.rs @@ -74,7 +74,8 @@ pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDi pub type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof; /// Messages delivery proof for Rialto -> Millau messages. -type ToMillauMessagesDeliveryProof = messages::source::FromBridgedChainMessagesDeliveryProof; +pub type FromMillauMessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; /// Millau <-> Rialto message bridge. #[derive(RuntimeDebug, Clone, Copy)] @@ -168,7 +169,7 @@ impl TargetHeaderChain for Millau // - hash of the header this proof has been created with; // - the storage proof of one or several keys; // - id of the lane we prove state of. - type MessagesDeliveryProof = ToMillauMessagesDeliveryProof; + type MessagesDeliveryProof = FromMillauMessagesDeliveryProof; fn verify_message(payload: &ToMillauMessagePayload) -> Result<(), Self::Error> { messages::source::verify_chain_message::(payload) diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index 6715295dd6..30c6a1f7d1 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -90,7 +90,7 @@ pub trait ChainWithMessageLanes { /// Hash used in the chain. type Hash: Decode; /// Accound id on the chain. - type AccountId: Decode; + type AccountId: Encode + Decode; /// Public key of the chain account that may be used to verify signatures. type Signer: Decode; /// Signature type used on the chain. diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs index e3dd705f8f..70cbb76f43 100644 --- a/bridges/bin/runtime-common/src/messages_benchmarking.rs +++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs @@ -19,13 +19,16 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::messages::{target::FromBridgedChainMessagesProof, BalanceOf, BridgedChain, HashOf, MessageBridge}; +use crate::messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, AccountIdOf, BalanceOf, + BridgedChain, HashOf, MessageBridge, ThisChain, +}; 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 pallet_message_lane::benchmarking::{MessageDeliveryProofParams, MessageProofParams}; use sp_core::Hasher; use sp_runtime::traits::Header; use sp_std::prelude::*; @@ -142,3 +145,47 @@ where .expect("too many messages requested by benchmark"), ) } + +/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call. +pub fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams>>, + make_bridged_inbound_lane_data_key: ML, + make_bridged_header: MH, +) -> FromBridgedChainMessagesDeliveryProof +where + B: MessageBridge, + H: Hasher, + R: pallet_substrate_bridge::Config, + ::Hash: Into>>, + ML: Fn(LaneId) -> Vec, + MH: Fn(H::Out) -> ::Header, +{ + // prepare Bridged chain storage with inbound lane state + let storage_key = make_bridged_inbound_lane_data_key(params.lane); + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = TrieDBMut::::new(&mut mdb, &mut root); + trie.insert(&storage_key, ¶ms.inbound_lane_data.encode()) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + } + + // generate storage proof to be delivered to This chain + let mut proof_recorder = Recorder::::new(); + 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, + ) +} diff --git a/bridges/modules/message-lane/src/benchmarking.rs b/bridges/modules/message-lane/src/benchmarking.rs index e0d20a6558..f211f95c39 100644 --- a/bridges/modules/message-lane/src/benchmarking.rs +++ b/bridges/modules/message-lane/src/benchmarking.rs @@ -16,19 +16,23 @@ //! Message lane pallet benchmarking. -use crate::{inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane, Call, Instance}; +use crate::{ + inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane, relayer_fund_account_id, Call, Instance, +}; use bp_message_lane::{ - target_chain::SourceHeaderChain, InboundLaneData, LaneId, MessageData, MessageNonce, OutboundLaneData, + source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, InboundLaneData, LaneId, MessageData, + MessageNonce, OutboundLaneData, }; use frame_benchmarking::{account, benchmarks_instance}; use frame_support::{traits::Get, weights::Weight}; use frame_system::RawOrigin; -use num_traits::Zero; -use sp_std::{ops::RangeInclusive, prelude::*}; +use sp_std::{collections::btree_map::BTreeMap, 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; +/// Fee paid by submitter for single message delivery. +const MESSAGE_FEE: u32 = 1_000_000; const SEED: u32 = 0; @@ -55,10 +59,20 @@ pub struct MessageProofParams { pub outbound_lane_data: Option, } +/// Benchmark-specific message delivery proof parameters. +pub struct MessageDeliveryProofParams { + /// Id of the lane. + pub lane: LaneId, + /// The proof needs to include this inbound lane data. + pub inbound_lane_data: InboundLaneData, +} + /// 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; + /// 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. @@ -72,6 +86,10 @@ pub trait Config: crate::Config { >::MessagesProof, Weight, ); + /// Prepare messages delivery proof to receive by the module. + fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams, + ) -> >::MessagesDeliveryProof; } benchmarks_instance! { @@ -200,6 +218,111 @@ benchmarks_instance! { ); } + // 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 = 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::(); + + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![(1, 1, relayer_id.clone())].into_iter().collect(), + latest_received_nonce: 1, + latest_confirmed_nonce: 0, + } + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof) + 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 = 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::(); + send_regular_message::(); + + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![(1, 2, relayer_id.clone())].into_iter().collect(), + latest_received_nonce: 2, + latest_confirmed_nonce: 0, + } + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof) + verify { + assert_eq!( + T::account_balance(&relayer_id), + relayer_balance + (MESSAGE_FEE * 2).into(), + ); + } + + // 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 = 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::(); + send_regular_message::(); + + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![ + (1, 1, relayer1_id.clone()), + (2, 2, relayer2_id.clone()), + ].into_iter().collect(), + latest_received_nonce: 2, + latest_confirmed_nonce: 0, + } + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer1_id.clone()), proof) + verify { + assert_eq!( + T::account_balance(&relayer1_id), + relayer1_balance + MESSAGE_FEE.into(), + ); + assert_eq!( + T::account_balance(&relayer2_id), + relayer2_balance + MESSAGE_FEE.into(), + ); + } + // // Benchmarks for manual checks. // @@ -284,6 +407,86 @@ benchmarks_instance! { 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 = 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::(); + } + + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![(1, i as MessageNonce, relayer_id.clone())].into_iter().collect(), + latest_received_nonce: i as MessageNonce, + latest_confirmed_nonce: 0, + } + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof) + verify { + assert_eq!( + T::account_balance(&relayer_id), + relayer_balance + (MESSAGE_FEE * i).into(), + ); + } + + // 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 = relayer_fund_account_id::(); + let confirmation_relayer_id = account("relayer", 0, SEED); + let relayers: BTreeMap = (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::(); + } + + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: 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(), + latest_received_nonce: i as MessageNonce, + latest_confirmed_nonce: 0, + } + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(confirmation_relayer_id), proof) + verify { + for (relayer_id, prev_balance) in relayers { + assert_eq!( + T::account_balance(&relayer_id), + prev_balance + MESSAGE_FEE.into(), + ); + } + } } fn bench_lane_id() -> LaneId { @@ -294,7 +497,7 @@ fn send_regular_message, I: Instance>() { let mut outbound_lane = outbound_lane::(bench_lane_id()); outbound_lane.send_message(MessageData { payload: vec![], - fee: Zero::zero(), + fee: MESSAGE_FEE.into(), }); } diff --git a/bridges/modules/message-lane/src/lib.rs b/bridges/modules/message-lane/src/lib.rs index 2b6dcb6bda..468df6867d 100644 --- a/bridges/modules/message-lane/src/lib.rs +++ b/bridges/modules/message-lane/src/lib.rs @@ -45,7 +45,7 @@ use frame_support::{ Parameter, StorageMap, }; use frame_system::{ensure_signed, RawOrigin}; -use num_traits::Zero; +use num_traits::{SaturatingAdd, Zero}; use sp_runtime::{traits::BadOrigin, DispatchResult}; use sp_std::{cell::RefCell, marker::PhantomData, prelude::*}; @@ -92,12 +92,15 @@ pub trait Config: frame_system::Config { /// /// This constant limits difference between last message from last entry of the /// `InboundLaneData::relayers` and first message at the first entry. + /// + /// There is no point of making this parameter lesser than MaxUnrewardedRelayerEntriesAtInboundLane, + /// because then maximal number of relayer entries will be limited by maximal number of messages. type MaxUnconfirmedMessagesAtInboundLane: Get; /// Payload type of outbound messages. This payload is dispatched on the bridged chain. type OutboundPayload: Parameter; /// Message fee type of outbound messages. This fee is paid on this chain. - type OutboundMessageFee: Parameter + Zero; + type OutboundMessageFee: From + Parameter + SaturatingAdd + Zero; /// Payload type of inbound messages. This payload is dispatched on this chain. type InboundPayload: Decode; @@ -423,24 +426,30 @@ decl_module! { let received_range = lane.confirm_delivery(lane_data.latest_received_nonce); if let Some(received_range) = received_range { Self::deposit_event(RawEvent::MessagesDelivered(lane_id, received_range.0, received_range.1)); - let relayer_fund_account = relayer_fund_account_id::(); // reward relayers that have delivered messages - // this loop is bounded by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain + // this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain + let relayer_fund_account = relayer_fund_account_id::(); for (nonce_low, nonce_high, relayer) in lane_data.relayers { let nonce_begin = sp_std::cmp::max(nonce_low, received_range.0); let nonce_end = sp_std::cmp::min(nonce_high, received_range.1); + // loop won't proceed if current entry is ahead of received range (begin > end). + // this loop is bound by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain + let mut relayer_fee: T::OutboundMessageFee = Zero::zero(); for nonce in nonce_begin..nonce_end + 1 { let message_data = OutboundMessages::::get(MessageKey { lane_id, nonce, }).expect("message was just confirmed; we never prune unconfirmed messages; qed"); + relayer_fee = relayer_fee.saturating_add(&message_data.fee); + } + if !relayer_fee.is_zero() { >::MessageDeliveryAndDispatchPayment::pay_relayer_reward( &confirmation_relayer, &relayer, - &message_data.fee, + &relayer_fee, &relayer_fund_account, ); }