// Copyright 2019-2021 Parity Technologies (UK) Ltd. // This file is part of Parity Bridges Common. // Parity Bridges Common is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Bridges Common is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . //! Messages pallet benchmarking. use crate::{ inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane, outbound_lane::ReceivalConfirmationResult, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, Call, }; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, DeliveredMessages, InboundLaneData, LaneId, MessageData, MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, }; use bp_runtime::{messages::DispatchFeePayment, StorageProofSize}; use frame_benchmarking::{account, benchmarks_instance_pallet}; use frame_support::{traits::Get, weights::Weight}; use frame_system::RawOrigin; use sp_std::{collections::vec_deque::VecDeque, convert::TryInto, ops::RangeInclusive, prelude::*}; const SEED: u32 = 0; /// Pallet we're benchmarking here. pub struct Pallet, I: 'static>(crate::Pallet); /// Benchmark-specific message parameters. #[derive(Debug)] pub struct MessageParams { /// Size of the message payload. pub size: u32, /// Message sender account. pub sender_account: ThisAccountId, } /// Benchmark-specific message proof parameters. #[derive(Debug)] 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, /// Proof size requirements. pub size: StorageProofSize, /// Where the fee for dispatching message is paid? pub dispatch_fee_payment: DispatchFeePayment, } /// Benchmark-specific message delivery proof parameters. #[derive(Debug)] pub struct MessageDeliveryProofParams { /// Id of the lane. pub lane: LaneId, /// The proof needs to include this inbound lane data. pub inbound_lane_data: InboundLaneData, /// Proof size requirements. pub size: StorageProofSize, } /// Trait that must be implemented by runtime. pub trait Config: crate::Config { /// 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); /// Fee paid by submitter for single message delivery. fn message_fee() -> Self::OutboundMessageFee { 100_000_000_000_000.into() } /// Prepare message to send over lane. 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, ); /// Prepare messages delivery proof to receive by the module. fn prepare_message_delivery_proof( params: MessageDeliveryProofParams, ) -> >::MessagesDeliveryProof; /// Returns true if message has been dispatched (either successfully or not). fn is_message_dispatched(nonce: MessageNonce) -> bool; } benchmarks_instance_pallet! { // // 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 relayers_fund_id = crate::relayer_fund_account_id::(); let sender = account("sender", 0, SEED); T::endow_account(&sender); T::endow_account(&relayers_fund_id); // 'send' messages that are to be pruned when our message is sent for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() { send_regular_message::(); } confirm_message_delivery::(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::OutboundLanes::::get(&T::bench_lane_id()).latest_generated_nonce, 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 relayers_fund_id = crate::relayer_fund_account_id::(); let sender = account("sender", 0, SEED); T::endow_account(&sender); T::endow_account(&relayers_fund_id); // 'send' messages that are to be pruned when our message is sent for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() { send_regular_message::(); } confirm_message_delivery::(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::OutboundLanes::::get(&T::bench_lane_id()).latest_generated_nonce, 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 relayers_fund_id = crate::relayer_fund_account_id::(); let sender = account("sender", 0, SEED); T::endow_account(&sender); T::endow_account(&relayers_fund_id); // 'send' messages that are to be pruned when our message is sent for _nonce in 1..=T::MaxMessagesToPruneAtOnce::get() { send_regular_message::(); } confirm_message_delivery::(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::OutboundLanes::::get(&T::bench_lane_id()).latest_generated_nonce, 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. // // Result of this benchmark is directly used by weight formula of the call. maximal_increase_message_fee { let relayers_fund_id = crate::relayer_fund_account_id::(); let sender = account("sender", 42, SEED); T::endow_account(&sender); T::endow_account(&relayers_fund_id); let additional_fee = T::account_balance(&sender); let lane_id = T::bench_lane_id(); let nonce = 1; send_regular_message_with_payload::(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 `increase_message_fee` with following conditions: // * message size varies from minimal to maximal; // * submitter account is killed because its balance is less than ED after payment. increase_message_fee { let i in 0..T::maximal_message_size().try_into().unwrap_or_default(); let relayers_fund_id = crate::relayer_fund_account_id::(); let sender = account("sender", 42, SEED); T::endow_account(&sender); T::endow_account(&relayers_fund_id); let additional_fee = T::account_balance(&sender); let lane_id = T::bench_lane_id(); let nonce = 1; send_regular_message_with_payload::(vec![42u8; i 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; // * message dispatch fee is paid at target (this) chain. // // 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); T::endow_account(&relayer_id_on_target); // mark messages 1..=20 as delivered receive_messages::(20); let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { lane: T::bench_lane_id(), message_nonces: 21..=21, outbound_lane_data: None, size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), dispatch_fee_payment: DispatchFeePayment::AtTargetChain, }); }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) verify { assert_eq!( crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), 21, ); assert!(T::is_message_dispatched(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; // * message dispatch fee is paid at target (this) chain. // // 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); T::endow_account(&relayer_id_on_target); // mark messages 1..=20 as delivered receive_messages::(20); let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { lane: T::bench_lane_id(), message_nonces: 21..=22, outbound_lane_data: None, size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), dispatch_fee_payment: DispatchFeePayment::AtTargetChain, }); }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 2, dispatch_weight) verify { assert_eq!( crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), 22, ); assert!(T::is_message_dispatched(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; // * message dispatch fee is paid at target (this) chain. // // 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); T::endow_account(&relayer_id_on_target); // mark messages 1..=20 as delivered receive_messages::(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: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), dispatch_fee_payment: DispatchFeePayment::AtTargetChain, }); }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) verify { let lane_state = crate::InboundLanes::::get(&T::bench_lane_id()); assert_eq!(lane_state.last_delivered_nonce(), 21); assert_eq!(lane_state.last_confirmed_nonce, 20); assert!(T::is_message_dispatched(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 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); T::endow_account(&relayer_id_on_target); // mark messages 1..=20 as delivered receive_messages::(20); let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { lane: T::bench_lane_id(), message_nonces: 21..=21, outbound_lane_data: None, size: StorageProofSize::HasExtraNodes(1024), dispatch_fee_payment: DispatchFeePayment::AtTargetChain, }); }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) verify { assert_eq!( crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), 21, ); assert!(T::is_message_dispatched(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); T::endow_account(&relayer_id_on_target); // mark messages 1..=20 as delivered receive_messages::(20); let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { lane: T::bench_lane_id(), message_nonces: 21..=21, outbound_lane_data: None, size: StorageProofSize::HasExtraNodes(16 * 1024), dispatch_fee_payment: DispatchFeePayment::AtTargetChain, }); }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) verify { assert_eq!( crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), 21, ); assert!(T::is_message_dispatched(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; // * message dispatch fee is paid at source (bridged) chain. // // This benchmark is used to compute extra weight spent at target chain when fee is paid there. Then we use // this information in two places: (1) to reduce weight of delivery tx if sender pays fee at the source chain // and (2) to refund relayer with this weight if fee has been paid at the source chain. receive_single_prepaid_message_proof { let relayer_id_on_source = T::bridged_relayer_id(); let relayer_id_on_target = account("relayer", 0, SEED); T::endow_account(&relayer_id_on_target); // mark messages 1..=20 as delivered receive_messages::(20); let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { lane: T::bench_lane_id(), message_nonces: 21..=21, outbound_lane_data: None, size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), dispatch_fee_payment: DispatchFeePayment::AtSourceChain, }); }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) verify { assert_eq!( crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), 21, ); assert!(T::is_message_dispatched(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::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 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![UnrewardedRelayer { relayer: relayer_id.clone(), messages: DeliveredMessages::new(1, true), }].into_iter().collect(), last_confirmed_nonce: 0, }, size: StorageProofSize::Minimal(0), }); }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state) verify { assert_eq!( T::account_balance(&relayer_id), relayer_balance + T::message_fee(), ); } // 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::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 relayers_state = UnrewardedRelayersState { unrewarded_relayer_entries: 1, messages_in_oldest_entry: 2, total_messages: 2, }; let mut delivered_messages = DeliveredMessages::new(1, true); delivered_messages.note_dispatched_message(true); let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { lane: T::bench_lane_id(), inbound_lane_data: InboundLaneData { relayers: vec![UnrewardedRelayer { relayer: relayer_id.clone(), messages: delivered_messages, }].into_iter().collect(), last_confirmed_nonce: 0, }, size: StorageProofSize::Minimal(0), }); }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state) verify { ensure_relayer_rewarded::(&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::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 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![ UnrewardedRelayer { relayer: relayer1_id.clone(), messages: DeliveredMessages::new(1, true), }, UnrewardedRelayer { relayer: relayer2_id.clone(), messages: DeliveredMessages::new(2, true), }, ].into_iter().collect(), last_confirmed_nonce: 0, }, size: StorageProofSize::Minimal(0), }); }: receive_messages_delivery_proof(RawOrigin::Signed(relayer1_id.clone()), proof, relayers_state) verify { ensure_relayer_rewarded::(&relayer1_id, &relayer1_balance); ensure_relayer_rewarded::(&relayer2_id, &relayer2_balance); } } fn send_regular_message, I: 'static>() { let mut outbound_lane = outbound_lane::(T::bench_lane_id()); outbound_lane.send_message(MessageData { payload: vec![], fee: T::message_fee() }); } fn send_regular_message_with_payload, I: 'static>(payload: Vec) { let mut outbound_lane = outbound_lane::(T::bench_lane_id()); outbound_lane.send_message(MessageData { payload, fee: T::message_fee() }); } fn confirm_message_delivery, I: 'static>(nonce: MessageNonce) { let mut outbound_lane = outbound_lane::(T::bench_lane_id()); let latest_received_nonce = outbound_lane.data().latest_received_nonce; let mut relayers = VecDeque::with_capacity((nonce - latest_received_nonce) as usize); for nonce in latest_received_nonce + 1..=nonce { relayers.push_back(UnrewardedRelayer { relayer: (), messages: DeliveredMessages::new(nonce, true), }); } assert!(matches!( outbound_lane.confirm_delivery(nonce - latest_received_nonce, nonce, &relayers), ReceivalConfirmationResult::ConfirmedMessages(_), )); } fn receive_messages, I: 'static>(nonce: MessageNonce) { let mut inbound_lane_storage = inbound_lane_storage::(T::bench_lane_id()); inbound_lane_storage.set_data(InboundLaneData { relayers: vec![UnrewardedRelayer { relayer: T::bridged_relayer_id(), messages: DeliveredMessages::new(nonce, true), }] .into_iter() .collect(), last_confirmed_nonce: 0, }); } fn ensure_relayer_rewarded, I: 'static>( 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, ); }