From 663c4c38b585038c4418bc58a4ef338726db9cfc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 2 Oct 2023 12:10:56 +0300 Subject: [PATCH 01/60] per-lane priority boost: prototype --- Cargo.lock | 1 + .../relayers/src/extension/grandpa_adapter.rs | 6 +- .../src/extension/parachain_adapter.rs | 6 +- modules/relayers/src/extension/priority.rs | 52 +- modules/relayers/src/lib.rs | 452 ++++++++++++++---- modules/relayers/src/mock.rs | 5 + primitives/relayers/Cargo.toml | 2 + primitives/relayers/src/extension.rs | 15 +- primitives/relayers/src/registration.rs | 68 ++- 9 files changed, 513 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e4b2ef802..e8afdfb94e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,6 +1363,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-runtime", "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] diff --git a/modules/relayers/src/extension/grandpa_adapter.rs b/modules/relayers/src/extension/grandpa_adapter.rs index 317bc965fe..a857deef5b 100644 --- a/modules/relayers/src/extension/grandpa_adapter.rs +++ b/modules/relayers/src/extension/grandpa_adapter.rs @@ -86,10 +86,14 @@ where type Runtime = R; type BatchCallUnpacker = BCU; type BridgeMessagesPalletInstance = MI; - type PriorityBoostPerMessage = P; type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; + type SlotLength = sp_runtime::traits::ConstU32<32>; // TODO + + type PriorityBoostForLaneRelayer = sp_runtime::traits::ConstU64<1>; // TODO + type PriorityBoostPerMessage = P; + fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< diff --git a/modules/relayers/src/extension/parachain_adapter.rs b/modules/relayers/src/extension/parachain_adapter.rs index ad76683083..c455c6a8c9 100644 --- a/modules/relayers/src/extension/parachain_adapter.rs +++ b/modules/relayers/src/extension/parachain_adapter.rs @@ -92,11 +92,15 @@ where type Runtime = R; type BatchCallUnpacker = BCU; type BridgeMessagesPalletInstance = MI; - type PriorityBoostPerMessage = P; type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; + type SlotLength = sp_runtime::traits::ConstU32<32>; // TODO + + type PriorityBoostForLaneRelayer = sp_runtime::traits::ConstU64<1>; // TODO + type PriorityBoostPerMessage = P; + fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 14511ad254..5858126207 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -22,13 +22,61 @@ //! single message with nonce `N`, then the transaction with nonces `N..=N+100` will //! be rejected. This can lower bridge throughput down to one message per block. -use bp_messages::MessageNonce; +use crate::{Config as RelayersConfig, Pallet as RelayersPallet}; + +use bp_messages::{LaneId, MessageNonce}; +use bp_relayers::ExtensionConfig; use frame_support::traits::Get; -use sp_runtime::transaction_validity::TransactionPriority; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as SystemPallet}; +use sp_runtime::{traits::{One, Zero}, transaction_validity::TransactionPriority}; // reexport everything from `integrity_tests` module pub use integrity_tests::*; +/// Compute priority boost for message delivery transaction that delivers +/// given number of messages. +pub fn maybe_lane_relayer_boost( + lane_id: LaneId, + relayer: &Runtime::AccountId, +) -> TransactionPriority +where + Runtime: RelayersConfig, + Config: ExtensionConfig, + usize: TryFrom>, +{ + // if there are no relayers, explicitly registered at this lane, noone gets additional + // priority boost + let lane_relayers = RelayersPallet::::lane_relayers(&lane_id); + let lane_relayers_len: BlockNumberFor = (lane_relayers.len() as u32).into(); + if lane_relayers_len.is_zero() { + return 0; + } + + // we can't deal with slots shorter than 1 block + let slot_length: BlockNumberFor = Config::SlotLength::get().into(); + if slot_length < One::one() { + return 0; + } + + // let's compute current slot number + let current_block_number = SystemPallet::::block_number(); + let slot = current_block_number / slot_length; + + // and then get the relayer for that slot + let slot_relayer = match usize::try_from(slot % lane_relayers_len) { + Ok(slot_relayer_index) => &lane_relayers[slot_relayer_index], + Err(_) => return 0, + }; + + // if message delivery transaction is submitted by the relayer, assigned to the current + // slot, let's boost the transaction priority + if relayer != slot_relayer { + return 0; + } + + Config::PriorityBoostForLaneRelayer::get() +} + /// Compute priority boost for message delivery transaction that delivers /// given number of messages. pub fn compute_priority_boost( diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 3e97d28a8e..360c38c5c7 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -14,20 +14,41 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -//! Runtime module that is used to store relayer rewards and (in the future) to -//! coordinate relations between relayers. +//! Runtime module that is used to store relayer rewards and to coordinate relations +//! between relayers. + +// TODO: allow bridge owners to add "protected" relayers that have a guaranteed slot in the lane relayers +// TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? + +// TODO: lane registration must be time-limited to force relayers to renew it and drop relayers that have stopped +// working. Otherwise one relayer may fill up all lane slots for a fixed returnable sum. +// +// Or we may introduce some fine for relayer for not delivering messages. This is near to impossible though. +// +// Or we may allow to buy lane registration using relayer rewards only - i.e. has has delivered 100 messages +// and reward is 100 DOTs. He could reserve his 100 DOTs to buy lane registration slots. Every slot costs 1 DOT. +// So after 100 slots, the registration becomes inactive. And he must renew it. + +// TODO: additionally we could add a reward market - i.e. now we have boosts: +// `messages_count * per_message + per_lane`. We could add another boost if relayer wants to receive lower reward. +// E.g. if normal reward is 1 DOT per message but relayers claims that he could deliver 10 messages in exchange of +// 1 DOT, we will prefer such transaction over transaction with 10 DOTs reward. #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] +use bp_messages::LaneId; use bp_relayers::{ PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; -use frame_support::fail; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{DefaultNoBound, fail}; +use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; -use sp_runtime::{traits::CheckedSub, Saturating}; -use sp_std::marker::PhantomData; +use scale_info::TypeInfo; +use sp_runtime::{traits::CheckedSub, BoundedVec, RuntimeDebug, Saturating}; +use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData}; pub use pallet::*; pub use payment_adapter::DeliveryConfirmationPaymentsAdapter; @@ -53,6 +74,39 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// An action that needs to be performed for relayer at given lane. + #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(AccountId))] + pub enum LaneRelayerAction { + /// Add relayer to the set. + Add(AccountId), + /// Remove relayer from the set. + Remove(AccountId), + } + + /// A scheduled change of relayers set at given lane. + #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(BlockNumber, AccountId))] + pub struct LaneRelayersChange { + /// Change at given block. + pub at: BlockNumber, + /// Change relayers set at given lane. + pub lane_id: LaneId, + /// Relayer action. + pub action: LaneRelayerAction, + } + + /// + #[derive(Clone, Decode, DefaultNoBound, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] + #[scale_info(skip_type_params(T))] + pub struct BoundedLaneRelayersChanges(pub VecDeque, T::AccountId>>); + + impl MaxEncodedLen for BoundedLaneRelayersChanges { + fn max_encoded_len() -> usize { + BoundedVec::::max_encoded_len() + } + } + /// `RelayerRewardsKeyProvider` for given configuration. type RelayerRewardsKeyProviderOf = RelayerRewardsKeyProvider<::AccountId, ::Reward>; @@ -67,6 +121,17 @@ pub mod pallet { type PaymentProcedure: PaymentProcedure; /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; + + /// TODO + #[pallet::constant] + type MaxLanesPerRelayer: Get; + /// Maximal number of relayers that can register themselves on a single lane. + #[pallet::constant] + type MaxRelayersPerLane: Get; + /// TODO + #[pallet::constant] + type MaxLaneRelayersChanges: Get; + /// Pallet call weights. type WeightInfo: WeightInfoExt; } @@ -74,6 +139,35 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(PhantomData); + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + LaneRelayersChanges::::mutate(|changes| { + while let Some(change) = changes.0.pop_front() { + if change.at > n { + changes.0.push_front(change); + return; + } + + LaneRelayers::::mutate(change.lane_id, |lane_relayers| { + match change.action { + LaneRelayerAction::Add(relayer) => { + if let Err(_) = lane_relayers.try_push(relayer) { + // TODO + } + }, + LaneRelayerAction::Remove(relayer) => { + lane_relayers.retain(|r| *r != relayer); + }, + } + }); + } + }); + + Weight::zero() // TODO + } + } + #[pallet::call] impl Pallet { /// Claim accumulated rewards. @@ -115,6 +209,13 @@ pub mod pallet { /// Register relayer or update its registration. /// /// Registration allows relayer to get priority boost for its message delivery transactions. + /// Honest block authors will choose prioritized transactions when there are transactions + /// from registered and unregistered relayers. However, registered relayers take additional + /// responsibility to submit only valid transactions. If they submit an invalid transaction, + /// their stake will be slashed and registration will be lost. + /// + /// Relayers may get additional priority boost by registering their intention to relay + /// messages at given lanes, using `register_at_lane` method. #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::register())] pub fn register(origin: OriginFor, valid_till: BlockNumberFor) -> DispatchResult { @@ -130,7 +231,8 @@ pub mod pallet { RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { let mut registration = maybe_registration - .unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() }); + .take() + .unwrap_or_else(|| Registration::new(valid_till)); // new `valid_till` must be larger (or equal) than the old one ensure!( @@ -139,32 +241,20 @@ pub mod pallet { ); registration.valid_till = valid_till; - // regarding stake, there are three options: - // - if relayer stake is larger than required stake, we may do unreserve - // - if relayer stake equals to required stake, we do nothing - // - if relayer stake is smaller than required stake, we do additional reserve - let required_stake = Pallet::::required_stake(); - if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) { - Self::do_unreserve(&relayer, to_unreserve)?; - } else if let Some(to_reserve) = required_stake.checked_sub(®istration.stake) { - T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| { - log::trace!( - target: LOG_TARGET, - "Failed to reserve {:?} on relayer {:?} account: {:?}", - to_reserve, - relayer, - e, - ); - - Error::::FailedToReserve - })?; - } - registration.stake = required_stake; + // reserve stake on relayer account + registration.stake = Self::update_relayer_stake( + &relayer, + registration.stake, + registration.required_stake( + Self::base_stake(), + Self::stake_per_lane(), + ), + )?; log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); Self::deposit_event(Event::::RegistrationUpdated { relayer: relayer.clone(), - registration, + registration: registration.clone(), }); *maybe_registration = Some(registration); @@ -176,7 +266,9 @@ pub mod pallet { /// `Deregister` relayer. /// /// After this call, message delivery transactions of the relayer won't get any priority - /// boost. + /// boost. Keep in mind that the relayer can't deregister until `valid_till` block, which + /// he has specified in the registration call. The relayer is also unregistered from all + /// lanes, where he has explicitly registered using `register_at_lane`. #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::deregister())] pub fn deregister(origin: OriginFor) -> DispatchResult { @@ -194,10 +286,18 @@ pub mod pallet { Error::::RegistrationIsStillActive, ); + // we can't deregister relayer that has registered itself for some lanes + ensure!( + registration.lanes.is_empty(), + Error::::HasLaneRegistrations, + ); + // if stake is non-zero, we should do unreserve - if !registration.stake.is_zero() { - Self::do_unreserve(&relayer, registration.stake)?; - } + Self::update_relayer_stake( + &relayer, + registration.stake, + Zero::zero(), + )?; log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); @@ -207,6 +307,134 @@ pub mod pallet { Ok(()) }) } + + /// Register relayer intention to serve given messages lane. + /// + /// Relayer that registers itself at given message lane + #[pallet::call_index(3)] + #[pallet::weight(Weight::zero())] // TODO + pub fn register_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { + let mut registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; + + // cannot add another lane registration if registration is inactive + let current_block_number = SystemPallet::::block_number(); + ensure!( + registration.is_active( + Self::base_stake(), + Self::stake_per_lane(), + SystemPallet::::block_number(), + Self::required_registration_lease(), + ), + Error::::RegistrationIsInactive, + ); + + // cannot add duplicate lane registration + ensure!(!registration.lanes.contains(&lane), Error::::DuplicateLaneRegistration); + + // cannot have more than `MaxRelayersPerLane` relayers at lane + LaneRelayers::::try_mutate(lane, |lane_relayers| { + ensure!(lane_relayers.try_push(relayer.clone()).is_ok(), Error::::TooManyLaneRelayersAtLane); + Ok::<_, Error>(()) + })?; + + // cannot add another lane registration if relayer has already max allowed + // lane registrations + ensure!(registration.lanes.try_push(lane).is_ok(), Error::::TooManyLaneRegistrations); + + // the relayer need to stake additional amount for every additional lane + registration.stake = Self::update_relayer_stake( + &relayer, + registration.stake, + registration.required_stake( + Self::base_stake(), + Self::stake_per_lane(), + ), + )?; + + // schedule lane relayers set change + LaneRelayersChanges::::try_mutate(|changes| { + ensure!( + changes.0.len() <= T::MaxLaneRelayersChanges::get() as usize, + Error::::TooManyScheduledChanges, + ); + changes.0.push_back(LaneRelayersChange { + at: current_block_number.saturating_add(100u32.into()), // TODO + lane_id: lane, + action: LaneRelayerAction::Add(relayer), + }); + Ok::<_, Error>(()) + })?; + + *maybe_registration = Some(registration); + + Ok(()) + })?; + + Ok(()) + } + + /// TODO + #[pallet::call_index(4)] + #[pallet::weight(Weight::zero())] // TODO + pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { + let mut registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; + + // ensure that the relayer has lane registration + ensure!(registration.lanes.contains(&lane), Error::::UnregisteredAtLane); + + // cannot have more than `MaxRelayersPerLane` relayers at lane + LaneRelayers::::try_mutate(lane, |lane_relayers| { + ensure!(lane_relayers.try_push(relayer.clone()).is_ok(), Error::::TooManyLaneRelayersAtLane); + Ok::<_, Error>(()) + })?; + + // cannot add another lane registration if relayer has already max allowed + // lane registrations + ensure!(registration.lanes.try_push(lane).is_ok(), Error::::TooManyLaneRegistrations); + + // the relayer need to stake additional amount for every additional lane + registration.stake = Self::update_relayer_stake( + &relayer, + registration.stake, + registration.required_stake( + Self::base_stake(), + Self::stake_per_lane(), + ), + )?; + + // schedule lane relayers set change + LaneRelayersChanges::::try_mutate(|changes| { + ensure!( + changes.0.len() <= T::MaxLaneRelayersChanges::get() as usize, + Error::::TooManyScheduledChanges, + ); + changes.0.push_back(LaneRelayersChange { + at: current_block_number.saturating_add(100u32.into()), // TODO + lane_id: lane, + action: LaneRelayerAction::Add(relayer), + }); + Ok::<_, Error>(()) + })?; + + *maybe_registration = Some(registration); + + Ok(()) + })?; + + Ok(()) + } } impl Pallet { @@ -216,25 +444,15 @@ pub mod pallet { /// it'll return false if registered stake is lower than required or if remaining lease /// is less than `RequiredRegistrationLease`. pub fn is_registration_active(relayer: &T::AccountId) -> bool { - let registration = match Self::registered_relayer(relayer) { - Some(registration) => registration, - None => return false, - }; - - // registration is inactive if relayer stake is less than required - if registration.stake < Self::required_stake() { - return false - } - - // registration is inactive if it ends soon - let remaining_lease = registration - .valid_till - .saturating_sub(frame_system::Pallet::::block_number()); - if remaining_lease <= Self::required_registration_lease() { - return false + match Self::registered_relayer(relayer) { + Some(registration) => registration.is_active( + Self::base_stake(), + Self::stake_per_lane(), + SystemPallet::::block_number(), + Self::required_registration_lease(), + ), + None => false, } - - true } /// Slash and `deregister` relayer. This function slashes all staked balance. @@ -338,8 +556,8 @@ pub mod pallet { >>::RequiredRegistrationLease::get() } - /// Return required stake. - pub(crate) fn required_stake() -> T::Reward { + /// Return required base stake. + pub(crate) fn base_stake() -> T::Reward { , @@ -347,22 +565,54 @@ pub mod pallet { >>::RequiredStake::get() } - /// `Unreserve` given amount on relayer account. - fn do_unreserve(relayer: &T::AccountId, amount: T::Reward) -> DispatchResult { - let failed_to_unreserve = T::StakeAndSlash::unreserve(relayer, amount); - if !failed_to_unreserve.is_zero() { - log::trace!( - target: LOG_TARGET, - "Failed to unreserve {:?}/{:?} on relayer {:?} account", - failed_to_unreserve, - amount, - relayer, - ); + /// Return required stake per lane. + pub(crate) fn stake_per_lane() -> T::Reward { + , + T::Reward, + >>::RequiredStake::get() // TODO + } - fail!(Error::::FailedToUnreserve) - } + /// Update relayer stake. + fn update_relayer_stake( + relayer: &T::AccountId, + current_stake: T::Reward, + required_stake: T::Reward, + ) -> Result { + // regarding stake, there are three options: + // - if relayer stake is larger than required stake, we may do unreserve + // - if relayer stake equals to required stake, we do nothing + // - if relayer stake is smaller than required stake, we do additional reserve + if let Some(to_unreserve) = current_stake.checked_sub(&required_stake) { + let failed_to_unreserve = T::StakeAndSlash::unreserve(relayer, to_unreserve); + if !failed_to_unreserve.is_zero() { + log::trace!( + target: LOG_TARGET, + "Failed to unreserve {:?}/{:?} on relayer {:?} account", + failed_to_unreserve, + to_unreserve, + relayer, + ); + + fail!(Error::::FailedToUnreserve) + } + } else if let Some(to_reserve) = required_stake.checked_sub(¤t_stake) { + let reserve_result = T::StakeAndSlash::reserve(&relayer, to_reserve); + if let Err(e) = reserve_result { + log::trace!( + target: LOG_TARGET, + "Failed to reserve {:?} on relayer {:?} account: {:?}", + to_reserve, + relayer, + e, + ); - Ok(()) + fail!(Error::::FailedToReserve) + } + } + + Ok(required_stake) } } @@ -383,7 +633,7 @@ pub mod pallet { /// Relayer account that has been registered. relayer: T::AccountId, /// Relayer registration. - registration: Registration, T::Reward>, + registration: Registration, T::Reward, T::MaxLanesPerRelayer>, }, /// Relayer has been `deregistered`. Deregistered { @@ -395,7 +645,7 @@ pub mod pallet { /// Relayer account that has been `deregistered`. relayer: T::AccountId, /// Registration that was removed. - registration: Registration, T::Reward>, + registration: Registration, T::Reward, T::MaxLanesPerRelayer>, }, } @@ -418,6 +668,20 @@ pub mod pallet { NotRegistered, /// Failed to `deregister` relayer, because lease is still active. RegistrationIsStillActive, + /// Failed to `deregister` relayer, because he has registered himself as a + /// relayer at some lanes using `register_at_lane`. Lane registrations must + /// be explicitly removed using `deregister_at_lane` + HasLaneRegistrations, + /// Failed to perform required action, because relayer registration is inactive. + RegistrationIsInactive, + /// Relayer is trying to register twice on the same lane. + DuplicateLaneRegistration, + /// Relayer has too many lane registrations. + TooManyLaneRegistrations, + /// + TooManyScheduledChanges, + /// + TooManyLaneRelayersAtLane, } /// Map of the relayer => accumulated reward. @@ -445,9 +709,33 @@ pub mod pallet { _, Blake2_128Concat, T::AccountId, - Registration, T::Reward>, + Registration, T::Reward, T::MaxLanesPerRelayer>, OptionQuery, >; + + /// A set of relayers that have explicitly registered themselves at a given lane. + /// + /// Every relayer inside this set receives additional priority boost when it submits + /// message delivers messages at given lane. The boost only happens inside the slot, + /// assigned to relayer. + #[pallet::storage] + #[pallet::getter(fn lane_relayers)] + pub type LaneRelayers = StorageMap< + _, + Identity, + LaneId, + BoundedVec, + ValueQuery, + >; + + /// Scheduled changes of the `LaneRelayers` map. + #[pallet::storage] + #[pallet::getter(fn lane_relayers_changes)] + pub type LaneRelayersChanges = StorageValue< + _, + BoundedLaneRelayersChanges, + ValueQuery, + >; } #[cfg(test)] @@ -627,7 +915,7 @@ mod tests { assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get()); assert_eq!( Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get() }), + Some(Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }), ); assert_eq!( @@ -636,7 +924,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get() }, + registration: Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }, }), topics: vec![], }), @@ -664,7 +952,7 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() + 1 }, + Registration { valid_till: 150, stake: Stake::get() + 1, lanes: BoundedVec::new() }, ); assert_noop!( @@ -681,7 +969,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() + 1 }, + Registration { valid_till: 150, stake: Stake::get() + 1, lanes: BoundedVec::new() }, ); TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() + 1).unwrap(); assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 1); @@ -695,7 +983,7 @@ mod tests { assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + 1); assert_eq!( Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get() }), + Some(Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }), ); assert_eq!( @@ -704,7 +992,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get() } + registration: Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() } }), topics: vec![], }), @@ -728,7 +1016,7 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1 }, + Registration { valid_till: 150, stake: Stake::get() - 1, lanes: BoundedVec::new() }, ); Balances::set_balance(®ISTER_RELAYER, 0); @@ -746,7 +1034,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1 }, + Registration { valid_till: 150, stake: Stake::get() - 1, lanes: BoundedVec::new() }, ); TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() - 1).unwrap(); @@ -759,7 +1047,7 @@ mod tests { assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance - 1); assert_eq!( Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get() }), + Some(Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }), ); assert_eq!( @@ -768,7 +1056,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get() } + registration: Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() } }), topics: vec![], }), @@ -849,7 +1137,7 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1 }, + Registration { valid_till: 150, stake: Stake::get() - 1, lanes: BoundedVec::new() }, ); assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); }); @@ -862,7 +1150,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() }, + Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }, ); assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); }); @@ -875,7 +1163,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 151, stake: Stake::get() }, + Registration { valid_till: 151, stake: Stake::get(), lanes: BoundedVec::new() }, ); assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); }); diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 1d7c326021..06400c157c 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -301,6 +301,11 @@ impl pallet_bridge_relayers::Config for TestRuntime { type Reward = ThisChainBalance; type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; + + type MaxLaneRelayersChanges = ConstU32<4>; + type MaxLanesPerRelayer = ConstU32<4>; + type MaxRelayersPerLane = ConstU32<4>; + type WeightInfo = (); } diff --git a/primitives/relayers/Cargo.toml b/primitives/relayers/Cargo.toml index 1c24b2f793..4a39b48d5c 100644 --- a/primitives/relayers/Cargo.toml +++ b/primitives/relayers/Cargo.toml @@ -22,6 +22,7 @@ bp-runtime = { path = "../runtime", default-features = false } frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } @@ -41,6 +42,7 @@ std = [ "frame-system/std", "pallet-utility/std", "scale-info/std", + "sp-arithmetic/std", "sp-runtime/std", "sp-std/std", ] diff --git a/primitives/relayers/src/extension.rs b/primitives/relayers/src/extension.rs index 91d0a92b0d..ccebc52552 100644 --- a/primitives/relayers/src/extension.rs +++ b/primitives/relayers/src/extension.rs @@ -123,9 +123,6 @@ pub trait ExtensionConfig { type BatchCallUnpacker: BatchCallUnpacker; /// Messages pallet instance. type BridgeMessagesPalletInstance: 'static; - /// Additional priority that is added to base message delivery transaction priority - /// for every additional bundled message. - type PriorityBoostPerMessage: Get; /// Type of reward, that the `pallet-bridge-relayers` is using. type Reward; /// Block number for the remote **GRANDPA chain**. Mind that this chain is not @@ -134,6 +131,18 @@ pub trait ExtensionConfig { /// GRANDPA chain, it must be it. type RemoteGrandpaChainBlockNumber: Clone + Copy + Debug; + // TODO: all constants below must be a part of the pallet configuration + + /// TODO + type SlotLength: Get; + + /// Additional priority boost that is added to base message delivery transaction + /// priority, submitted by the relayer, assigned to given lane at given slot. + type PriorityBoostForLaneRelayer: Get; + /// Additional priority that is added to base message delivery transaction priority + /// for every additional bundled message. + type PriorityBoostPerMessage: Get; + /// Given runtime call, check if it is supported by the signed extension. Additionally, /// check if call (or any of batched calls) are obsolete. fn parse_and_check_for_obsolete_call( diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index bc2d0d127a..71b4063c74 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -39,16 +39,21 @@ use crate::RewardsAccountParams; +use bp_messages::LaneId; use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use scale_info::TypeInfo; +use sp_arithmetic::traits::BaseArithmetic; use sp_runtime::{ traits::{Get, Zero}, - DispatchError, DispatchResult, + BoundedVec, DispatchError, DispatchResult, Saturating, }; +use sp_std::fmt::Debug; /// Relayer registration. -#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] -pub struct Registration { +#[derive(CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(BlockNumber, Balance, MaxLanesPerRelayer))] +pub struct Registration> { /// The last block number, where this registration is considered active. /// /// Relayer has an option to renew his registration (this may be done before it @@ -60,9 +65,62 @@ pub struct Registration { pub valid_till: BlockNumber, /// Active relayer stake, which is mapped to the relayer reserved balance. /// - /// If `stake` is less than the [`StakeAndSlash::RequiredStake`], the registration - /// is considered inactive even if `valid_till + 1` is not yet reached. + /// If `stake` is less than the [`StakeAndSlash::RequiredStake`] plus additional + /// [`StakeAndSlash::RequiredStake`] for every entry in the `lanes` vector, the + /// registration is considered inactive even if `valid_till + 1` is not yet reached. pub stake: Balance, + /// All lanes, where relayer has explicitly registered itself for additional + /// priority boost. + /// + /// Relayer pays additional [`StakeAndSlash::RequiredStake`] for every lane. + pub lanes: BoundedVec, +} + +impl> Registration { + /// Creates new empty registration that ends at given block. + pub fn new(valid_till: BlockNumber) -> Self { + Registration { valid_till, stake: Zero::zero(), lanes: BoundedVec::new() } + } + + /// Returns minimal stake that the relayer need to have in reserve to be + /// considered active. + pub fn required_stake( + &self, + base_stake: Balance, + stake_per_lane: Balance, + ) -> Balance { + stake_per_lane + .saturating_mul(Balance::try_from(self.lanes.len()).unwrap_or(Balance::max_value())) + .saturating_add(base_stake) + } + + /// Returns `true` if registration is active. In other words, if registration + /// + /// - has stake larger or equal to required; + /// + /// - is valid for another `required_registration_lease` blocks. + pub fn is_active( + &self, + base_stake: Balance, + stake_per_lane: Balance, + current_block_number: BlockNumber, + required_registration_lease: BlockNumber, + ) -> bool { + // registration is inactive if relayer stake is less than required + if self.stake < self.required_stake(base_stake, stake_per_lane) { + return false + } + + // registration is inactive if it ends soon + let remaining_lease = self + .valid_till + .saturating_sub(current_block_number); + if remaining_lease <= required_registration_lease { + return false + } + + true + } } /// Relayer stake-and-slash mechanism. From cb2e0457e593e3367312bc0074775ddd91f959d0 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 2 Oct 2023 13:35:16 +0300 Subject: [PATCH 02/60] compute_per_lane_priority_boost to discuss new approach --- bin/millau/runtime/src/lib.rs | 5 +- bin/rialto-parachain/runtime/src/lib.rs | 8 +- bin/rialto/runtime/src/lib.rs | 4 + .../relayers/src/extension/grandpa_adapter.rs | 12 +- modules/relayers/src/extension/mod.rs | 40 +++--- .../src/extension/parachain_adapter.rs | 12 +- modules/relayers/src/extension/priority.rs | 134 ++++++++++++++++-- modules/relayers/src/lib.rs | 49 +++++++ modules/relayers/src/mock.rs | 7 + primitives/relayers/src/extension.rs | 9 +- 10 files changed, 222 insertions(+), 58 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index fe1f8aaa2b..50c241fb23 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -399,6 +399,10 @@ impl pallet_bridge_relayers::Config for Runtime { ConstU64<1_000>, ConstU64<8>, >; + type MaxRelayersPerLane = ConstU32<16>; + type SlotLength = ConstU64<16>; + type PriorityBoostPerMessage = PriorityBoostPerMessage; + type PriorityBoostForActiveLaneRelayer = ConstU64<0>; type WeightInfo = (); } @@ -660,7 +664,6 @@ pub type BridgeRefundRialtoParachainMessages = bp_relayers::RuntimeWithUtilityPallet, WithRialtoParachainsInstance, WithRialtoParachainMessagesInstance, - PriorityBoostPerMessage, >, >; diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 8b70fc5757..7009747c7a 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -34,7 +34,9 @@ use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, ConstBool, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, Block as BlockT, ConstU128, DispatchInfoOf, SignedExtension}, + traits::{ + AccountIdLookup, Block as BlockT, ConstU128, ConstU64, DispatchInfoOf, SignedExtension, + }, transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, ApplyExtrinsicResult, }; @@ -539,6 +541,10 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); + type MaxRelayersPerLane = ConstU32<16>; + type SlotLength = ConstU32<16>; + type PriorityBoostPerMessage = ConstU64<0>; + type PriorityBoostForActiveLaneRelayer = ConstU64<0>; type WeightInfo = (); } diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index 78c756dd83..263c056eba 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -393,6 +393,10 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); + type MaxRelayersPerLane = ConstU32<16>; + type SlotLength = ConstU32<16>; + type PriorityBoostPerMessage = ConstU64<0>; + type PriorityBoostForActiveLaneRelayer = ConstU64<0>; type WeightInfo = (); } diff --git a/modules/relayers/src/extension/grandpa_adapter.rs b/modules/relayers/src/extension/grandpa_adapter.rs index 317bc965fe..5fb0f9dda9 100644 --- a/modules/relayers/src/extension/grandpa_adapter.rs +++ b/modules/relayers/src/extension/grandpa_adapter.rs @@ -33,9 +33,7 @@ use pallet_bridge_messages::{ CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, }; use sp_runtime::{ - traits::{Dispatchable, Get}, - transaction_validity::{TransactionPriority, TransactionValidityError}, - Saturating, + traits::Dispatchable, transaction_validity::TransactionValidityError, Saturating, }; use sp_std::marker::PhantomData; @@ -54,8 +52,6 @@ pub struct WithGrandpaChainExtensionConfig< BridgeGrandpaPalletInstance, // instance of BridgedChainthe `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, - // message delivery transaction priority boost for every additional message - PriorityBoostPerMessage, >( PhantomData<( IdProvider, @@ -63,12 +59,10 @@ pub struct WithGrandpaChainExtensionConfig< BatchCallUnpacker, BridgeGrandpaPalletInstance, BridgeMessagesPalletInstance, - PriorityBoostPerMessage, )>, ); -impl ExtensionConfig - for WithGrandpaChainExtensionConfig +impl ExtensionConfig for WithGrandpaChainExtensionConfig where ID: StaticStrProvider, R: BridgeRelayersConfig @@ -77,7 +71,6 @@ where BCU: BatchCallUnpacker, GI: 'static, MI: 'static, - P: Get, R::RuntimeCall: Dispatchable + BridgeGrandpaCallSubtype + BridgeMessagesCallSubType, @@ -86,7 +79,6 @@ where type Runtime = R; type BatchCallUnpacker = BCU; type BridgeMessagesPalletInstance = MI; - type PriorityBoostPerMessage = P; type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; diff --git a/modules/relayers/src/extension/mod.rs b/modules/relayers/src/extension/mod.rs index 0e5603c793..b80a53a670 100644 --- a/modules/relayers/src/extension/mod.rs +++ b/modules/relayers/src/extension/mod.rs @@ -35,7 +35,7 @@ use frame_support::{ dispatch::{DispatchInfo, PostDispatchInfo}, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; -use frame_system::Config as SystemConfig; +use frame_system::{pallet_prelude::BlockNumberFor, Config as SystemConfig}; use pallet_bridge_messages::{CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig}; use pallet_transaction_payment::{ Config as TransactionPaymentConfig, OnChargeTransaction, Pallet as TransactionPaymentPallet, @@ -106,6 +106,7 @@ where R::RuntimeCall: Dispatchable, ::OnChargeTransaction: OnChargeTransaction, + usize: TryFrom>, { /// Returns number of bundled messages `Some(_)`, if the given call info is a: /// @@ -117,16 +118,15 @@ where /// virtually boosted. The relayer registration (we only boost priority for registered /// relayer transactions) must be checked outside. fn bundled_messages_for_priority_boost( - call_info: Option<&ExtensionCallInfo>, + call_info: &ExtensionCallInfo, ) -> Option { // we only boost priority of message delivery transactions - let parsed_call = match call_info { - Some(parsed_call) if parsed_call.is_receive_messages_proof_call() => parsed_call, - _ => return None, - }; + if !call_info.is_receive_messages_proof_call() { + return None + } // compute total number of messages in transaction - let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len(); + let bundled_messages = call_info.messages_call_info().bundled_messages().saturating_len(); // a quick check to avoid invalid high-priority transactions let max_unconfirmed_messages_in_confirmation_tx = >::BridgedChain @@ -178,8 +178,7 @@ where // // - when relayer is registered after `validate` is called and priority is not boosted: // relayer should be ready for slashing after registration. - let may_slash_relayer = - Self::bundled_messages_for_priority_boost(Some(&call_info)).is_some(); + let may_slash_relayer = Self::bundled_messages_for_priority_boost(&call_info).is_some(); let slash_relayer_if_delivery_result = may_slash_relayer .then(|| RelayerAccountAction::Slash(relayer.clone(), reward_account_params)) .unwrap_or(RelayerAccountAction::None); @@ -254,6 +253,8 @@ where R::RuntimeCall: Dispatchable, ::OnChargeTransaction: OnChargeTransaction, + usize: TryFrom>, + usize: TryFrom>, { const IDENTIFIER: &'static str = C::IdProvider::STR; type AccountId = R::AccountId; @@ -277,13 +278,15 @@ where // we're not calling `validate` from `pre_dispatch` directly because of performance // reasons, so if you're adding some code that may fail here, please check if it needs // to be added to the `pre_dispatch` as well - let parsed_call = C::parse_and_check_for_obsolete_call(call)?; + let parsed_call = match C::parse_and_check_for_obsolete_call(call)? { + Some(parsed_call) => parsed_call, + None => return Ok(Default::default()), + }; // the following code just plays with transaction priority and never returns an error // we only boost priority of presumably correct message delivery transactions - let bundled_messages = match Self::bundled_messages_for_priority_boost(parsed_call.as_ref()) - { + let bundled_messages = match Self::bundled_messages_for_priority_boost(&parsed_call) { Some(bundled_messages) => bundled_messages, None => return Ok(Default::default()), }; @@ -294,8 +297,11 @@ where } // compute priority boost - let priority_boost = - priority::compute_priority_boost::(bundled_messages); + let priority_boost = priority::compute_priority_boost::( + parsed_call.messages_call_info().lane_id(), + bundled_messages, + &who, + ); let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost); log::trace!( @@ -303,7 +309,7 @@ where "{}.{:?}: has boosted priority of message delivery transaction \ of relayer {:?}: {} messages -> {} priority", Self::IDENTIFIER, - parsed_call.as_ref().map(|p| p.messages_call_info().lane_id()), + parsed_call.messages_call_info().lane_id(), who, bundled_messages, priority_boost, @@ -424,7 +430,7 @@ mod tests { use pallet_bridge_parachains::Call as ParachainsCall; use pallet_utility::Call as UtilityCall; use sp_runtime::{ - traits::{ConstU64, Header as HeaderT}, + traits::Header as HeaderT, transaction_validity::{InvalidTransaction, ValidTransaction}, DispatchError, }; @@ -452,7 +458,6 @@ mod tests { RuntimeWithUtilityPallet, (), (), - ConstU64<1>, >; type TestGrandpaExtension = BridgeRelayersSignedExtension; @@ -462,7 +467,6 @@ mod tests { RuntimeWithUtilityPallet, (), (), - ConstU64<1>, >; type TestExtension = BridgeRelayersSignedExtension; diff --git a/modules/relayers/src/extension/parachain_adapter.rs b/modules/relayers/src/extension/parachain_adapter.rs index ad76683083..37c7c8793a 100644 --- a/modules/relayers/src/extension/parachain_adapter.rs +++ b/modules/relayers/src/extension/parachain_adapter.rs @@ -38,10 +38,7 @@ use pallet_bridge_parachains::{ CallSubType as BridgeParachainsCallSubtype, Config as BridgeParachainsConfig, SubmitParachainHeadsHelper, }; -use sp_runtime::{ - traits::{Dispatchable, Get}, - transaction_validity::{TransactionPriority, TransactionValidityError}, -}; +use sp_runtime::{traits::Dispatchable, transaction_validity::TransactionValidityError}; use sp_std::marker::PhantomData; /// Adapter to be used in signed extension configuration, when bridging with remote parachains. @@ -58,8 +55,6 @@ pub struct WithParachainExtensionConfig< BridgeParachainsPalletInstance, // instance of BridgedChainthe `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, - // message delivery transaction priority boost for every additional message - PriorityBoostPerMessage, >( PhantomData<( IdProvider, @@ -67,11 +62,10 @@ pub struct WithParachainExtensionConfig< BatchCallUnpacker, BridgeParachainsPalletInstance, BridgeMessagesPalletInstance, - PriorityBoostPerMessage, )>, ); -impl ExtensionConfig for WithParachainExtensionConfig +impl ExtensionConfig for WithParachainExtensionConfig where ID: StaticStrProvider, R: BridgeRelayersConfig @@ -81,7 +75,6 @@ where BCU: BatchCallUnpacker, PI: 'static, MI: 'static, - P: Get, R::RuntimeCall: Dispatchable + BridgeGrandpaCallSubtype + BridgeParachainsCallSubtype @@ -92,7 +85,6 @@ where type Runtime = R; type BatchCallUnpacker = BCU; type BridgeMessagesPalletInstance = MI; - type PriorityBoostPerMessage = P; type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 14511ad254..637220fe16 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -22,31 +22,89 @@ //! single message with nonce `N`, then the transaction with nonces `N..=N+100` will //! be rejected. This can lower bridge throughput down to one message per block. -use bp_messages::MessageNonce; +use crate::{Config as RelayersConfig, Pallet as RelayersPallet}; + +use bp_messages::{LaneId, MessageNonce}; use frame_support::traits::Get; -use sp_runtime::transaction_validity::TransactionPriority; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as SystemPallet}; +use sp_runtime::{ + traits::{One, Zero}, + transaction_validity::TransactionPriority, +}; // reexport everything from `integrity_tests` module pub use integrity_tests::*; -/// Compute priority boost for message delivery transaction that delivers -/// given number of messages. -pub fn compute_priority_boost( +/// Compute total priority boost for message delivery transaction. +pub fn compute_priority_boost( + lane_id: LaneId, messages: MessageNonce, + relayer: &R::AccountId, ) -> TransactionPriority where - PriorityBoostPerMessage: Get, + usize: TryFrom>, { + compute_per_message_priority_boost::(messages) + .saturating_add(compute_per_lane_priority_boost::(lane_id, relayer)) +} + +/// Compute priority boost for message delivery transaction, that depends on number +/// of bundled messages. +fn compute_per_message_priority_boost>( + messages: MessageNonce, +) -> TransactionPriority { // we don't want any boost for transaction with single message => minus one PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1)) } +/// Compute priority boost for message delivery transaction, that depends on +/// the set of lane relayers and current slot. +fn compute_per_lane_priority_boost( + lane_id: LaneId, + relayer: &R::AccountId, +) -> TransactionPriority +where + usize: TryFrom>, +{ + // if there are no relayers, explicitly registered at this lane, noone gets additional + // priority boost + let lane_relayers = RelayersPallet::::lane_relayers(&lane_id); + let lane_relayers_len: BlockNumberFor = (lane_relayers.len() as u32).into(); + if lane_relayers_len.is_zero() { + return 0 + } + + // we can't deal with slots shorter than 1 block + let slot_length: BlockNumberFor = R::SlotLength::get().into(); + if slot_length < One::one() { + return 0 + } + + // let's compute current slot number + let current_block_number = SystemPallet::::block_number(); + let slot = current_block_number / slot_length; + + // and then get the relayer for that slot + let slot_relayer = match usize::try_from(slot % lane_relayers_len) { + Ok(slot_relayer_index) => &lane_relayers[slot_relayer_index], + Err(_) => return 0, + }; + + // if message delivery transaction is submitted by the relayer, assigned to the current + // slot, let's boost the transaction priority + if relayer != slot_relayer { + return 0 + } + + R::PriorityBoostForActiveLaneRelayer::get() +} + #[cfg(not(feature = "integrity-test"))] mod integrity_tests {} #[cfg(feature = "integrity-test")] mod integrity_tests { - use super::compute_priority_boost; + use super::compute_per_message_priority_boost; use bp_messages::ChainWithMessages; use bp_messages::MessageNonce; @@ -93,7 +151,8 @@ mod integrity_tests { Runtime, MessagesInstance, >(messages, Zero::zero()); - let priority_boost = compute_priority_boost::(messages); + let priority_boost = + compute_per_message_priority_boost::(messages); let priority_with_boost = base_priority + priority_boost; let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into()); @@ -108,7 +167,7 @@ mod integrity_tests { panic!( "The PriorityBoostPerMessage value ({}) must be fixed to: {}", priority_boost_per_message, - compute_priority_boost_per_message::( + compute_per_message_priority_boost_per_message::( tip_boost_per_message ), ); @@ -118,7 +177,7 @@ mod integrity_tests { /// Compute priority boost that we give to message delivery transaction for additional message. #[cfg(feature = "integrity-test")] - fn compute_priority_boost_per_message( + fn compute_per_message_priority_boost_per_message( tip_boost_per_message: BalanceOf, ) -> TransactionPriority where @@ -200,3 +259,58 @@ mod integrity_tests { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{mock::*, LaneRelayers}; + + #[test] + fn compute_per_lane_priority_boost_works() { + run_test(|| { + // insert 3 relayers to the queue + let lane_id = LaneId::new(1, 2); + let relayer1 = 1_000; + let relayer2 = 2_000; + let relayer3 = 3_000; + LaneRelayers::::insert( + lane_id, + sp_runtime::BoundedVec::try_from(vec![relayer1, relayer2, relayer3]).unwrap(), + ); + + // at blocks 1..=SlotLength relayer1 gets the boost + System::set_block_number(0); + for _ in 1..SlotLength::get() { + System::set_block_number(System::block_number() + 1); + assert_eq!( + compute_per_lane_priority_boost::(lane_id, &relayer1), + PriorityBoostForActiveLaneRelayer::get(), + ); + assert_eq!(compute_per_lane_priority_boost::(lane_id, &relayer2), 0,); + assert_eq!(compute_per_lane_priority_boost::(lane_id, &relayer3), 0,); + } + + // at next slot, relayer2 gets the boost + for _ in 1..=SlotLength::get() { + System::set_block_number(System::block_number() + 1); + assert_eq!(compute_per_lane_priority_boost::(lane_id, &relayer1), 0,); + assert_eq!( + compute_per_lane_priority_boost::(lane_id, &relayer2), + PriorityBoostForActiveLaneRelayer::get(), + ); + assert_eq!(compute_per_lane_priority_boost::(lane_id, &relayer3), 0,); + } + + // at next slot, relayer3 gets the boost + for _ in 1..=SlotLength::get() { + System::set_block_number(System::block_number() + 1); + assert_eq!(compute_per_lane_priority_boost::(lane_id, &relayer1), 0,); + assert_eq!(compute_per_lane_priority_boost::(lane_id, &relayer2), 0,); + assert_eq!( + compute_per_lane_priority_boost::(lane_id, &relayer3), + PriorityBoostForActiveLaneRelayer::get(), + ); + } + }); + } +} diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 3e97d28a8e..c733c592e9 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -20,6 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] +use bp_messages::LaneId; use bp_relayers::{ PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, }; @@ -65,8 +66,41 @@ pub mod pallet { type Reward: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen; /// Pay rewards scheme. type PaymentProcedure: PaymentProcedure; + /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; + + /// Maximal number of relayers that can register themselves on a single lane. + #[pallet::constant] + type MaxRelayersPerLane: Get; + /// Length of slots in chain blocks. + /// + /// Registered relayer may explicitly register himself at some lane to get priority boost + /// for message delivery transactions on that lane (that is done using `register_at_lane` + /// pallet call). All relayers, registered at the lane form an ordered queue and only + /// relayer at the head of that queue receives a boost at his slot (which has a length of + /// `SlotLength` blocks). Then the "best" relayer is removed and pushed to the tail of the + /// queue and next relayer gets the boost during next `SlotLength` blocks. And so on... + /// + /// Shall not be too low to have an effect, because there's some (at least one block) lag + /// between moments when priority is computed and when active slot changes. + type SlotLength: Get>; + /// Priority boost that the registered relayer gets for every additional message in the + /// message delivery transaction. + type PriorityBoostPerMessage: Get; + /// Additional priority boost, that is added to regular `PriorityBoostPerMessage` boost for + /// message delivery transactions, submitted by relayer at the head of the lane relayers + /// queue. + /// + /// In other words, if relayer has registered at some `lane`, using `register_at_lane` call + /// AND he is currently at the head of the lane relayers queue, his message delivery + /// transaction will get the following additional priority boost: + /// + /// ```nocompile + /// T::PriorityBoostForActiveLaneRelayer::get() + T::PriorityBoostPerMessage::get() * (msgs - 1) + /// ``` + type PriorityBoostForActiveLaneRelayer: Get; + /// Pallet call weights. type WeightInfo: WeightInfoExt; } @@ -448,6 +482,21 @@ pub mod pallet { Registration, T::Reward>, OptionQuery, >; + + /// A set of relayers that have explicitly registered themselves at a given lane. + /// + /// Every relayer inside this set receives additional priority boost when it submits + /// message delivers messages at given lane. The boost only happens inside the slot, + /// assigned to relayer. + #[pallet::storage] + #[pallet::getter(fn lane_relayers)] + pub type LaneRelayers = StorageMap< + _, + Identity, + LaneId, + BoundedVec, + ValueQuery, + >; } #[cfg(test)] diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 1d7c326021..ddee5ba789 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -38,6 +38,7 @@ use pallet_transaction_payment::Multiplier; use sp_core::{ConstU64, ConstU8, H256}; use sp_runtime::{ traits::{BlakeTwo256, ConstU32, IdentityLookup}, + transaction_validity::TransactionPriority, BuildStorage, FixedPointNumber, Perquintill, StateVersion, }; @@ -190,6 +191,8 @@ parameter_types! { pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); + pub SlotLength: u32 = 16; + pub PriorityBoostForActiveLaneRelayer: TransactionPriority = 1; } impl frame_system::Config for TestRuntime { @@ -301,6 +304,10 @@ impl pallet_bridge_relayers::Config for TestRuntime { type Reward = ThisChainBalance; type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; + type MaxRelayersPerLane = ConstU32<16>; + type SlotLength = SlotLength; + type PriorityBoostPerMessage = ConstU64<1>; + type PriorityBoostForActiveLaneRelayer = PriorityBoostForActiveLaneRelayer; type WeightInfo = (); } diff --git a/primitives/relayers/src/extension.rs b/primitives/relayers/src/extension.rs index 91d0a92b0d..9ef49d7be9 100644 --- a/primitives/relayers/src/extension.rs +++ b/primitives/relayers/src/extension.rs @@ -26,11 +26,7 @@ use frame_support::{ }; use frame_system::Config as SystemConfig; use pallet_utility::{Call as UtilityCall, Pallet as UtilityPallet}; -use sp_runtime::{ - traits::Get, - transaction_validity::{TransactionPriority, TransactionValidityError}, - RuntimeDebug, -}; +use sp_runtime::{transaction_validity::TransactionValidityError, RuntimeDebug}; use sp_std::{fmt::Debug, marker::PhantomData, vec, vec::Vec}; /// Type of the call that the signed extension recognizes. @@ -123,9 +119,6 @@ pub trait ExtensionConfig { type BatchCallUnpacker: BatchCallUnpacker; /// Messages pallet instance. type BridgeMessagesPalletInstance: 'static; - /// Additional priority that is added to base message delivery transaction priority - /// for every additional bundled message. - type PriorityBoostPerMessage: Get; /// Type of reward, that the `pallet-bridge-relayers` is using. type Reward; /// Block number for the remote **GRANDPA chain**. Mind that this chain is not From d310fe07e2b2c4f818310d7303970160191da3a6 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 2 Oct 2023 14:40:30 +0300 Subject: [PATCH 03/60] clippy --- modules/relayers/src/extension/mod.rs | 2 +- modules/relayers/src/extension/priority.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/relayers/src/extension/mod.rs b/modules/relayers/src/extension/mod.rs index b80a53a670..cb569bb762 100644 --- a/modules/relayers/src/extension/mod.rs +++ b/modules/relayers/src/extension/mod.rs @@ -300,7 +300,7 @@ where let priority_boost = priority::compute_priority_boost::( parsed_call.messages_call_info().lane_id(), bundled_messages, - &who, + who, ); let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost); diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 637220fe16..03e4849637 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -68,14 +68,14 @@ where { // if there are no relayers, explicitly registered at this lane, noone gets additional // priority boost - let lane_relayers = RelayersPallet::::lane_relayers(&lane_id); + let lane_relayers = RelayersPallet::::lane_relayers(lane_id); let lane_relayers_len: BlockNumberFor = (lane_relayers.len() as u32).into(); if lane_relayers_len.is_zero() { return 0 } // we can't deal with slots shorter than 1 block - let slot_length: BlockNumberFor = R::SlotLength::get().into(); + let slot_length: BlockNumberFor = R::SlotLength::get(); if slot_length < One::one() { return 0 } From 57cd105a5eb0a16a1d83e3f964f40e29c1d8bf7f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 4 Oct 2023 15:53:52 +0300 Subject: [PATCH 04/60] change approach a bit --- modules/relayers/src/lib.rs | 146 +++++++---------------- modules/relayers/src/payment_adapter.rs | 4 +- primitives/relayers/src/lane_relayers.rs | 67 +++++++++++ primitives/relayers/src/registration.rs | 4 + 4 files changed, 113 insertions(+), 108 deletions(-) create mode 100644 primitives/relayers/src/lane_relayers.rs diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 360c38c5c7..6bd99dc6e1 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -34,6 +34,8 @@ // E.g. if normal reward is 1 DOT per message but relayers claims that he could deliver 10 messages in exchange of // 1 DOT, we will prefer such transaction over transaction with 10 DOTs reward. +// TODO: better (easier code) handling of reserved funds. Separate calls? + #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] @@ -74,39 +76,6 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - /// An action that needs to be performed for relayer at given lane. - #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(AccountId))] - pub enum LaneRelayerAction { - /// Add relayer to the set. - Add(AccountId), - /// Remove relayer from the set. - Remove(AccountId), - } - - /// A scheduled change of relayers set at given lane. - #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(BlockNumber, AccountId))] - pub struct LaneRelayersChange { - /// Change at given block. - pub at: BlockNumber, - /// Change relayers set at given lane. - pub lane_id: LaneId, - /// Relayer action. - pub action: LaneRelayerAction, - } - - /// - #[derive(Clone, Decode, DefaultNoBound, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] - #[scale_info(skip_type_params(T))] - pub struct BoundedLaneRelayersChanges(pub VecDeque, T::AccountId>>); - - impl MaxEncodedLen for BoundedLaneRelayersChanges { - fn max_encoded_len() -> usize { - BoundedVec::::max_encoded_len() - } - } - /// `RelayerRewardsKeyProvider` for given configuration. type RelayerRewardsKeyProviderOf = RelayerRewardsKeyProvider<::AccountId, ::Reward>; @@ -119,6 +88,7 @@ pub mod pallet { type Reward: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen; /// Pay rewards scheme. type PaymentProcedure: PaymentProcedure; + /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; @@ -128,9 +98,6 @@ pub mod pallet { /// Maximal number of relayers that can register themselves on a single lane. #[pallet::constant] type MaxRelayersPerLane: Get; - /// TODO - #[pallet::constant] - type MaxLaneRelayersChanges: Get; /// Pallet call weights. type WeightInfo: WeightInfoExt; @@ -139,35 +106,6 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(PhantomData); - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: BlockNumberFor) -> Weight { - LaneRelayersChanges::::mutate(|changes| { - while let Some(change) = changes.0.pop_front() { - if change.at > n { - changes.0.push_front(change); - return; - } - - LaneRelayers::::mutate(change.lane_id, |lane_relayers| { - match change.action { - LaneRelayerAction::Add(relayer) => { - if let Err(_) = lane_relayers.try_push(relayer) { - // TODO - } - }, - LaneRelayerAction::Remove(relayer) => { - lane_relayers.retain(|r| *r != relayer); - }, - } - }); - } - }); - - Weight::zero() // TODO - } - } - #[pallet::call] impl Pallet { /// Claim accumulated rewards. @@ -310,19 +248,28 @@ pub mod pallet { /// Register relayer intention to serve given messages lane. /// - /// Relayer that registers itself at given message lane + /// Relayer that registers itself at given message lane gets a priority boost for his message + /// delivery transactions, **verified** at his slots (consecutive range of blocks). #[pallet::call_index(3)] #[pallet::weight(Weight::zero())] // TODO - pub fn register_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + pub fn register_at_lane( + origin: OriginFor, + lane: LaneId, + ) -> DispatchResult { let relayer = ensure_signed(origin)?; + // TODO: we probably need a way for bridge owners (sibling/parent chains) to at least set a maximal + // possible reward for their lane over XCM? + maybe change relayers set? This way they could implement + // their own incentivization mechanisms by setting reward to zero and changing relayers set on their own. + RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { + // we only allow registered relayers to have priority boosts let mut registration = match maybe_registration.take() { Some(registration) => registration, None => fail!(Error::::NotRegistered), }; - // cannot add another lane registration if registration is inactive + // cannot add another lane registration if "base" registration is inactive let current_block_number = SystemPallet::::block_number(); ensure!( registration.is_active( @@ -334,19 +281,26 @@ pub mod pallet { Error::::RegistrationIsInactive, ); - // cannot add duplicate lane registration - ensure!(!registration.lanes.contains(&lane), Error::::DuplicateLaneRegistration); + // cannot add another lane registration if relayer has already max allowed + // lane registrations + if !registration.lanes.contains(&lane) { + ensure!(registration.lanes.try_push(lane).is_ok(), Error::::TooManyLaneRegistrations); + } - // cannot have more than `MaxRelayersPerLane` relayers at lane + // TODO: ideally we shall use the candle auction here (similar to parachain slot auctions) + // let's try to claim a slot in the next set LaneRelayers::::try_mutate(lane, |lane_relayers| { - ensure!(lane_relayers.try_push(relayer.clone()).is_ok(), Error::::TooManyLaneRelayersAtLane); + ensure!( + !lane_relayers.next_set_contains(relayer.clone()), + Error::::AlreadyRegisteredAtLane, + ); + ensure!( + lane_relayers.next_set_try_push(relayer.clone(), expected_reward), + Error::::TooLargeRewardToOccupyAnEntry, + ); Ok::<_, Error>(()) })?; - // cannot add another lane registration if relayer has already max allowed - // lane registrations - ensure!(registration.lanes.try_push(lane).is_ok(), Error::::TooManyLaneRegistrations); - // the relayer need to stake additional amount for every additional lane registration.stake = Self::update_relayer_stake( &relayer, @@ -357,19 +311,8 @@ pub mod pallet { ), )?; - // schedule lane relayers set change - LaneRelayersChanges::::try_mutate(|changes| { - ensure!( - changes.0.len() <= T::MaxLaneRelayersChanges::get() as usize, - Error::::TooManyScheduledChanges, - ); - changes.0.push_back(LaneRelayersChange { - at: current_block_number.saturating_add(100u32.into()), // TODO - lane_id: lane, - action: LaneRelayerAction::Add(relayer), - }); - Ok::<_, Error>(()) - })?; + // cannot add duplicate lane registration + // ensure!(!registration.lanes.contains(&lane), Error::::DuplicateLaneRegistration); *maybe_registration = Some(registration); @@ -386,6 +329,8 @@ pub mod pallet { let relayer = ensure_signed(origin)?; RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { + // if relayer doesn't have a basic registration, we know that he is not registered + // at the lane as well let mut registration = match maybe_registration.take() { Some(registration) => registration, None => fail!(Error::::NotRegistered), @@ -394,9 +339,12 @@ pub mod pallet { // ensure that the relayer has lane registration ensure!(registration.lanes.contains(&lane), Error::::UnregisteredAtLane); - // cannot have more than `MaxRelayersPerLane` relayers at lane + // remove relayer from the `next_set` of lane relayers. So relayer is still LaneRelayers::::try_mutate(lane, |lane_relayers| { - ensure!(lane_relayers.try_push(relayer.clone()).is_ok(), Error::::TooManyLaneRelayersAtLane); + ensure!( + lane_relayers.next_set_ctry_remove(relayer.clone()), + Error::::NotRegisteredAtLane, + ); Ok::<_, Error>(()) })?; @@ -414,26 +362,12 @@ pub mod pallet { ), )?; - // schedule lane relayers set change - LaneRelayersChanges::::try_mutate(|changes| { - ensure!( - changes.0.len() <= T::MaxLaneRelayersChanges::get() as usize, - Error::::TooManyScheduledChanges, - ); - changes.0.push_back(LaneRelayersChange { - at: current_block_number.saturating_add(100u32.into()), // TODO - lane_id: lane, - action: LaneRelayerAction::Add(relayer), - }); - Ok::<_, Error>(()) - })?; - *maybe_registration = Some(registration); Ok(()) })?; - Ok(()) + Ok(())*/ } } diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 3693793a3e..05586a7ede 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -73,14 +73,14 @@ fn register_relayers_rewards( confirmation_relayer: &T::AccountId, relayers_rewards: RelayersRewards, lane_id: RewardsAccountParams, - delivery_fee: T::Reward, + message_delivery_reward: T::Reward, ) { // reward every relayer except `confirmation_relayer` let mut confirmation_relayer_reward = T::Reward::zero(); for (relayer, messages) in relayers_rewards { // sane runtime configurations guarantee that the number of messages will be below // `u32::MAX` - let relayer_reward = T::Reward::saturated_from(messages).saturating_mul(delivery_fee); + let relayer_reward = T::Reward::saturated_from(messages).saturating_mul(message_delivery_reward); if relayer != *confirmation_relayer { Pallet::::register_relayer_reward(lane_id, &relayer, relayer_reward); diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs new file mode 100644 index 0000000000..1eccb2581b --- /dev/null +++ b/primitives/relayers/src/lane_relayers.rs @@ -0,0 +1,67 @@ +// Copyright (C) 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 . + +//! Bridge lane relayers registration and slashing scheme. + +/// A relayer and the reward that it wants to receive for delivering a single message. +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(AccountId, Reward))] +pub struct RelayerAndReward { + /// A relayer account identifier. + pub relayer: AccountId, + /// A reward that is paid to relayer for delivering a single message. + pub reward: Reward, +} + +/// A set of relayers that have explicitly registered themselves at a given lane. +/// +/// Every relayer inside this set receives additional priority boost when it submits +/// message delivers messages at given lane. The boost only happens inside the slot, +/// assigned to relayer. +/// +/// The set is required to change periodically (at `next_set_enacts_at`). An interval, when +/// the same relayers set is active is called epoch. Every relayer in the epoch is guaranteed +/// to have at least one slot, but epochs may have differrent lengths. +/// +/// We change the set to guarantee that inactive relayers are removed from the set eventually +/// and are replaced by active relayers. The relayer will be scheduled for autoremoval if it +/// has not delivered any messages during previous epoch. +/// +/// Relayers are bargaining for the place in the set by offering lower reward for delivering +/// messages. Relayer, which agress to get a lower reward will likely to replace a "more greedy" +/// relayer in the [`Self::next_set`]. +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(AccountId, BlockNumber, Reward))] +pub struct LaneRelayers { + /// Number of block, where the active set has been enacted. + pub enacted_at: BlockNumber, + /// Number of block, where the active set may be replaced with the [`Self::next_set`]. + /// + /// We do not allow immediate changes of the [`Self::next_set`], because relayers + /// may change it so that they are always assigned the current slot. + pub next_set_enacts_at: BlockNumber, + /// An active set of lane relayers. + /// + /// It is a circular queue. Every relayer in the queue is assigned the slot (fixed number + /// of blocks), starting from [`Self::enacted_at`]. Once the slot of last relayer ends, + /// next slot will be assigned to the first relayer and so on. + pub active_set: BoundedVec, MaxLaneRelayers>, + /// Next set of lane relayers. + /// + /// It is a bounded priority queue. Relayers that are working for larger reward are replaced + /// with relayers, that are working for smaller reward. + pub next_set: BoundedVec, MaxLaneRelayers>, +} diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 71b4063c74..016ca3811d 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -73,6 +73,10 @@ pub struct Registration, } From c1f9d8adb36d639e655340105ebefa5a4f7b97e3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 4 Oct 2023 19:21:16 +0300 Subject: [PATCH 05/60] continue --- modules/relayers/src/lib.rs | 24 +------- primitives/relayers/src/lane_relayers.rs | 74 +++++++++++++++++++++--- primitives/relayers/src/lib.rs | 2 + 3 files changed, 70 insertions(+), 30 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 6bd99dc6e1..bb0ac32350 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -290,10 +290,6 @@ pub mod pallet { // TODO: ideally we shall use the candle auction here (similar to parachain slot auctions) // let's try to claim a slot in the next set LaneRelayers::::try_mutate(lane, |lane_relayers| { - ensure!( - !lane_relayers.next_set_contains(relayer.clone()), - Error::::AlreadyRegisteredAtLane, - ); ensure!( lane_relayers.next_set_try_push(relayer.clone(), expected_reward), Error::::TooLargeRewardToOccupyAnEntry, @@ -337,37 +333,23 @@ pub mod pallet { }; // ensure that the relayer has lane registration - ensure!(registration.lanes.contains(&lane), Error::::UnregisteredAtLane); + // ensure!(registration.lanes.remove(&lane), Error::::UnregisteredAtLane); // remove relayer from the `next_set` of lane relayers. So relayer is still LaneRelayers::::try_mutate(lane, |lane_relayers| { ensure!( - lane_relayers.next_set_ctry_remove(relayer.clone()), + lane_relayers.next_set_try_remove(&relayer), Error::::NotRegisteredAtLane, ); Ok::<_, Error>(()) })?; - // cannot add another lane registration if relayer has already max allowed - // lane registrations - ensure!(registration.lanes.try_push(lane).is_ok(), Error::::TooManyLaneRegistrations); - - // the relayer need to stake additional amount for every additional lane - registration.stake = Self::update_relayer_stake( - &relayer, - registration.stake, - registration.required_stake( - Self::base_stake(), - Self::stake_per_lane(), - ), - )?; - *maybe_registration = Some(registration); Ok(()) })?; - Ok(())*/ + Ok(()) } } diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 1eccb2581b..08837115ef 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -14,16 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -//! Bridge lane relayers registration and slashing scheme. +//! Bridge lane relayers. + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Get, BoundedVec, RuntimeDebug}; /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(AccountId, Reward))] pub struct RelayerAndReward { /// A relayer account identifier. - pub relayer: AccountId, + relayer: AccountId, /// A reward that is paid to relayer for delivering a single message. - pub reward: Reward, + reward: Reward, } /// A set of relayers that have explicitly registered themselves at a given lane. @@ -32,7 +36,7 @@ pub struct RelayerAndReward { /// message delivers messages at given lane. The boost only happens inside the slot, /// assigned to relayer. /// -/// The set is required to change periodically (at `next_set_enacts_at`). An interval, when +/// The set is required to change periodically (at `next_set_may_enact_at`). An interval, when /// the same relayers set is active is called epoch. Every relayer in the epoch is guaranteed /// to have at least one slot, but epochs may have differrent lengths. /// @@ -45,23 +49,75 @@ pub struct RelayerAndReward { /// relayer in the [`Self::next_set`]. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(AccountId, BlockNumber, Reward))] -pub struct LaneRelayers { +pub struct LaneRelayers> { /// Number of block, where the active set has been enacted. - pub enacted_at: BlockNumber, + enacted_at: BlockNumber, /// Number of block, where the active set may be replaced with the [`Self::next_set`]. /// /// We do not allow immediate changes of the [`Self::next_set`], because relayers /// may change it so that they are always assigned the current slot. - pub next_set_enacts_at: BlockNumber, + next_set_may_enact_at: BlockNumber, /// An active set of lane relayers. /// /// It is a circular queue. Every relayer in the queue is assigned the slot (fixed number /// of blocks), starting from [`Self::enacted_at`]. Once the slot of last relayer ends, /// next slot will be assigned to the first relayer and so on. - pub active_set: BoundedVec, MaxLaneRelayers>, + active_set: BoundedVec, MaxLaneRelayers>, /// Next set of lane relayers. /// /// It is a bounded priority queue. Relayers that are working for larger reward are replaced /// with relayers, that are working for smaller reward. - pub next_set: BoundedVec, MaxLaneRelayers>, + next_set: BoundedVec, MaxLaneRelayers>, +} + +impl LaneRelayers where + AccountId: PartialOrd, + Reward: Clone + Copy + Ord, + MaxLaneRelayers: Get, +{ + /// Try insert relayer into next set. + /// + /// Returns `true` if relayer has been added to the set and false otherwise. + pub fn next_set_try_push(&mut self, relayer: AccountId, reward: Reward) -> bool { + // first, remove existing entry for the same relayer from the set + self.next_set.retain(|entry| entry.relayer != relayer); + // now try to insert new entry into the queue + self.next_set.force_insert_keep_left( + self.select_position_in_next_set(reward), + RelayerAndReward { relayer, reward }, + ).is_ok() + } + + fn select_position_in_next_set(&self, reward: Reward) -> usize { + // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it may be used + // to push relayers our of the queue + let mut initial_position = self + .next_set + .binary_search_by_key(&reward, |entry| entry.reward) + .unwrap_or_else(|position| position); + while self.next_set.get(initial_position + 1).map(|entry| entry.reward == reward).unwrap_or(false) { + initial_position += 1; + } + initial_position + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const MAX_LANE_RELAYERS: u32 = 4; + type TestLaneRelayers = LaneRelayers>; + + #[test] + fn next_set_try_push_works() { + let mut relayers: TestLaneRelayers = LaneRelayers { + enacted_at: 0, + next_set_may_enact_at: 100, + active_set: vec![].try_into().unwrap(), + next_set: vec![].try_into().unwrap(), + }; + + // first `MAX_LANE_RELAYERS` are added + } } diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index 6ca2d9d10a..4186d8ae90 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -23,6 +23,7 @@ pub use extension::{ BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig, RuntimeWithUtilityPallet, }; +pub use lane_relayers::LaneRelayers; pub use registration::{Registration, StakeAndSlash}; use bp_messages::LaneId; @@ -37,6 +38,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, marker::PhantomData}; mod extension; +mod lane_relayers; mod registration; /// The owner of the sovereign account that should pay the rewards. From ebd6c61c811f945e374224f8a341f30c285c0f70 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 11:28:45 +0300 Subject: [PATCH 06/60] continue --- modules/relayers/src/extension/priority.rs | 12 +- modules/relayers/src/lib.rs | 55 +++++-- modules/relayers/src/mock.rs | 1 - primitives/relayers/src/lane_relayers.rs | 170 +++++++++++++++++++-- primitives/relayers/src/lib.rs | 2 +- 5 files changed, 205 insertions(+), 35 deletions(-) diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 5858126207..ee97ef3823 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -46,8 +46,12 @@ where { // if there are no relayers, explicitly registered at this lane, noone gets additional // priority boost - let lane_relayers = RelayersPallet::::lane_relayers(&lane_id); - let lane_relayers_len: BlockNumberFor = (lane_relayers.len() as u32).into(); + let lane_relayers = match RelayersPallet::::lane_relayers(&lane_id) { + Some(lane_relayers) => lane_relayers, + None => return 0, + }; + let active_lane_relayers = lane_relayers.active_relayers(); + let lane_relayers_len: BlockNumberFor = (active_lane_relayers.len() as u32).into(); if lane_relayers_len.is_zero() { return 0; } @@ -64,13 +68,13 @@ where // and then get the relayer for that slot let slot_relayer = match usize::try_from(slot % lane_relayers_len) { - Ok(slot_relayer_index) => &lane_relayers[slot_relayer_index], + Ok(slot_relayer_index) => &active_lane_relayers[slot_relayer_index], Err(_) => return 0, }; // if message delivery transaction is submitted by the relayer, assigned to the current // slot, let's boost the transaction priority - if relayer != slot_relayer { + if relayer != slot_relayer.relayer() { return 0; } diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index bb0ac32350..7feaf7db0b 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -27,7 +27,7 @@ // // Or we may allow to buy lane registration using relayer rewards only - i.e. has has delivered 100 messages // and reward is 100 DOTs. He could reserve his 100 DOTs to buy lane registration slots. Every slot costs 1 DOT. -// So after 100 slots, the registration becomes inactive. And he must renew it. +// So after 100 slots, the registration becomes inactive. And he must renew it. // TODO: additionally we could add a reward market - i.e. now we have boosts: // `messages_count * per_message + per_lane`. We could add another boost if relayer wants to receive lower reward. @@ -41,7 +41,7 @@ use bp_messages::LaneId; use bp_relayers::{ - PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use codec::{Decode, Encode, MaxEncodedLen}; @@ -49,7 +49,7 @@ use frame_support::{DefaultNoBound, fail}; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use scale_info::TypeInfo; -use sp_runtime::{traits::CheckedSub, BoundedVec, RuntimeDebug, Saturating}; +use sp_runtime::{traits::{CheckedSub, One}, BoundedVec, RuntimeDebug, Saturating}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData}; pub use pallet::*; @@ -255,6 +255,7 @@ pub mod pallet { pub fn register_at_lane( origin: OriginFor, lane: LaneId, + expected_reward: T::Reward, ) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -262,6 +263,8 @@ pub mod pallet { // possible reward for their lane over XCM? + maybe change relayers set? This way they could implement // their own incentivization mechanisms by setting reward to zero and changing relayers set on their own. + // TODO: check that `expected_reward` makes sense + RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { // we only allow registered relayers to have priority boosts let mut registration = match maybe_registration.take() { @@ -289,11 +292,28 @@ pub mod pallet { // TODO: ideally we shall use the candle auction here (similar to parachain slot auctions) // let's try to claim a slot in the next set - LaneRelayers::::try_mutate(lane, |lane_relayers| { + LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { + let mut lane_relayers = match lane_relayers_ref.take() { + Some(lane_relayers) => lane_relayers, + None => { + // TODO: give some time for initial elections + // TODO: what if all relayers that have registered for the next set then call `deregister_at_lane` + // before `next_set` activates? This could be used by malicious relayers - they could fill + // the whole `next_set` and then clear it right before it is enacted. Think we shall allow more + // entries in the `mnext_set` so that it'll be harder for the attacker to fill the full queue. + LaneRelayersSet::empty( + SystemPallet::::block_number().saturating_add(One::one()).saturating_add(4u32.into()), // TODO + ) + } + }; + ensure!( lane_relayers.next_set_try_push(relayer.clone(), expected_reward), Error::::TooLargeRewardToOccupyAnEntry, ); + + *lane_relayers_ref = Some(lane_relayers); + Ok::<_, Error>(()) })?; @@ -336,11 +356,19 @@ pub mod pallet { // ensure!(registration.lanes.remove(&lane), Error::::UnregisteredAtLane); // remove relayer from the `next_set` of lane relayers. So relayer is still - LaneRelayers::::try_mutate(lane, |lane_relayers| { + LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { + let mut lane_relayers = match lane_relayers_ref.take() { + Some(lane_relayers) => lane_relayers, + None => fail!(Error::::NotRegisteredAtLane), + }; + ensure!( lane_relayers.next_set_try_remove(&relayer), Error::::NotRegisteredAtLane, ); + + *lane_relayers_ref = Some(lane_relayers); + Ok::<_, Error>(()) })?; @@ -598,6 +626,10 @@ pub mod pallet { TooManyScheduledChanges, /// TooManyLaneRelayersAtLane, + /// + TooLargeRewardToOccupyAnEntry, + /// + NotRegisteredAtLane, } /// Map of the relayer => accumulated reward. @@ -640,17 +672,8 @@ pub mod pallet { _, Identity, LaneId, - BoundedVec, - ValueQuery, - >; - - /// Scheduled changes of the `LaneRelayers` map. - #[pallet::storage] - #[pallet::getter(fn lane_relayers_changes)] - pub type LaneRelayersChanges = StorageValue< - _, - BoundedLaneRelayersChanges, - ValueQuery, + LaneRelayersSet, T::Reward, T::MaxRelayersPerLane>, + OptionQuery, >; } diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 06400c157c..15ef75732b 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -302,7 +302,6 @@ impl pallet_bridge_relayers::Config for TestRuntime { type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; - type MaxLaneRelayersChanges = ConstU32<4>; type MaxLanesPerRelayer = ConstU32<4>; type MaxRelayersPerLane = ConstU32<4>; diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 08837115ef..cebd7e9f3a 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -18,7 +18,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::{traits::Get, BoundedVec, RuntimeDebug}; +use sp_runtime::{traits::{Get, Zero}, BoundedVec, RuntimeDebug}; /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -30,6 +30,13 @@ pub struct RelayerAndReward { reward: Reward, } +impl RelayerAndReward { + /// Return relayer account identifier. + pub fn relayer(&self) -> &AccountId { + &self.relayer + } +} + /// A set of relayers that have explicitly registered themselves at a given lane. /// /// Every relayer inside this set receives additional priority boost when it submits @@ -48,8 +55,8 @@ pub struct RelayerAndReward { /// messages. Relayer, which agress to get a lower reward will likely to replace a "more greedy" /// relayer in the [`Self::next_set`]. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[scale_info(skip_type_params(AccountId, BlockNumber, Reward))] -pub struct LaneRelayers> { +#[scale_info(skip_type_params(MaxRelayersPerLane))] +pub struct LaneRelayersSet> { /// Number of block, where the active set has been enacted. enacted_at: BlockNumber, /// Number of block, where the active set may be replaced with the [`Self::next_set`]. @@ -62,25 +69,41 @@ pub struct LaneRelayers, MaxLaneRelayers>, + active_set: BoundedVec, MaxRelayersPerLane>, /// Next set of lane relayers. /// /// It is a bounded priority queue. Relayers that are working for larger reward are replaced /// with relayers, that are working for smaller reward. - next_set: BoundedVec, MaxLaneRelayers>, + next_set: BoundedVec, MaxRelayersPerLane>, } -impl LaneRelayers where +impl LaneRelayersSet where AccountId: PartialOrd, + BlockNumber: Zero, Reward: Clone + Copy + Ord, - MaxLaneRelayers: Get, + MaxRelayersPerLane: Get, { - /// Try insert relayer into next set. + /// Creates new empty relayers set, where next sets enacts at given block. + pub fn empty(next_set_may_enact_at: BlockNumber) -> Self { + LaneRelayersSet { + enacted_at: Zero::zero(), + next_set_may_enact_at, + active_set: BoundedVec::new(), + next_set: BoundedVec::new(), + } + } + + /// Returns count of relayers in the active set. + pub fn active_relayers(&self) -> &[RelayerAndReward] { + self.active_set.as_slice() + } + + /// Try insert relayer to the next set. /// /// Returns `true` if relayer has been added to the set and false otherwise. pub fn next_set_try_push(&mut self, relayer: AccountId, reward: Reward) -> bool { // first, remove existing entry for the same relayer from the set - self.next_set.retain(|entry| entry.relayer != relayer); + self.next_set_try_remove(&relayer); // now try to insert new entry into the queue self.next_set.force_insert_keep_left( self.select_position_in_next_set(reward), @@ -88,6 +111,15 @@ impl LaneRelayers bool { + let len_before = self.next_set.len(); + self.next_set.retain(|entry| entry.relayer != *relayer); + self.next_set.len() != len_before + } + fn select_position_in_next_set(&self, reward: Reward) -> usize { // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it may be used // to push relayers our of the queue @@ -95,7 +127,7 @@ impl LaneRelayers LaneRelayers>; + type TestLaneRelayersSet = LaneRelayersSet>; #[test] fn next_set_try_push_works() { - let mut relayers: TestLaneRelayers = LaneRelayers { + let mut relayers: TestLaneRelayersSet = LaneRelayersSet { enacted_at: 0, next_set_may_enact_at: 100, active_set: vec![].try_into().unwrap(), next_set: vec![].try_into().unwrap(), }; - // first `MAX_LANE_RELAYERS` are added + // first `MAX_LANE_RELAYERS` are simply filling the set + for i in 0..MAX_LANE_RELAYERS { + assert!(relayers.next_set_try_push(i, (MAX_LANE_RELAYERS - i) * 10)); + } + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 2, reward: 20 }, + RelayerAndReward { relayer: 1, reward: 30 }, + RelayerAndReward { relayer: 0, reward: 40 }, + ], + ); + + // try to insert relayer who wants reward, that is larger than anyone in the set + // => the set is not changed + assert!(!relayers.next_set_try_push(4, 50)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 2, reward: 20 }, + RelayerAndReward { relayer: 1, reward: 30 }, + RelayerAndReward { relayer: 0, reward: 40 }, + ], + ); + + // replace worst relayer in the set + assert!(relayers.next_set_try_push(5, 35)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 2, reward: 20 }, + RelayerAndReward { relayer: 1, reward: 30 }, + RelayerAndReward { relayer: 5, reward: 35 }, + ], + ); + + // insert best relayer to the set, pushing worst relayer out of set + assert!(relayers.next_set_try_push(6, 5)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, reward: 5 }, + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 2, reward: 20 }, + RelayerAndReward { relayer: 1, reward: 30 }, + ], + ); + + // insert best relayer to the set, pushing worst relayer out of set + assert!(relayers.next_set_try_push(6, 5)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, reward: 5 }, + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 2, reward: 20 }, + RelayerAndReward { relayer: 1, reward: 30 }, + ], + ); + + // insert relayer to the middle of the set, pushing worst relayer out of set + assert!(relayers.next_set_try_push(7, 15)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, reward: 5 }, + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 7, reward: 15 }, + RelayerAndReward { relayer: 2, reward: 20 }, + ], + ); + + // insert couple of relayer that want the same reward as some relayer in the middle of the queue + // => they are inserted **after** existing relayers + assert!(relayers.next_set_try_push(8, 10)); + assert!(relayers.next_set_try_push(9, 10)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, reward: 5 }, + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 8, reward: 10 }, + RelayerAndReward { relayer: 9, reward: 10 }, + ], + ); + + // insert next relayer, similar to previous => it isn't inserted + assert!(!relayers.next_set_try_push(10, 10)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, reward: 5 }, + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 8, reward: 10 }, + RelayerAndReward { relayer: 9, reward: 10 }, + ], + ); + + // update expected reward of existing relayer => the set order is changed + assert!(!relayers.next_set_try_push(8, 2)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 8, reward: 2 }, + RelayerAndReward { relayer: 6, reward: 5 }, + RelayerAndReward { relayer: 3, reward: 10 }, + RelayerAndReward { relayer: 9, reward: 10 }, + ], + ); } } diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index 4186d8ae90..b0ceef5604 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -23,7 +23,7 @@ pub use extension::{ BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig, RuntimeWithUtilityPallet, }; -pub use lane_relayers::LaneRelayers; +pub use lane_relayers::{LaneRelayersSet, RelayerAndReward}; pub use registration::{Registration, StakeAndSlash}; use bp_messages::LaneId; From facf6a525b04122ed5a889d45cfd2733d3ffb336 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 12:56:31 +0300 Subject: [PATCH 07/60] flush --- modules/relayers/src/lib.rs | 38 ++++++++++++++++++++++++ primitives/relayers/src/lane_relayers.rs | 7 +++++ 2 files changed, 45 insertions(+) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 7feaf7db0b..ef5db6cb9a 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -379,6 +379,44 @@ pub mod pallet { Ok(()) } + + // TODO: add another `obsolete` extension for this call of the relayers pallet? + /// Enact next set of relayers at a given lane. + /// + /// This will replace the set of active relayers with the next scheduled set, for given lane. Anyone could + /// call this method at any point. If the set will be changed, the cost of transaction will be refunded to + /// the submitter. We do not provide any on-chain means to sync between relayers on who will submit this + /// transaction, so first transaction from anyone will be accepted and it will have the zero cost. All + /// subsequent transactions will be paid. We suggest the first relayer from the `next_set` to submit this + /// transaction. + #[pallet::call_index(5)] + #[pallet::weight(Weight::zero())] // TODO + pub fn enact_next_relayers_set_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + // remove relayer from the `next_set` of lane relayers. So relayer is still + LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { + let mut lane_relayers = match lane_relayers_ref.take() { + Some(lane_relayers) => lane_relayers, + None => fail!(Error::::NoRelayersAtLane), + }; + + // ensure that the current block number allows us to enact next set + let current_block_number = SystemPallet::::block_number(); + ensure!( + lane_relayers.next_set_may_enact_at() >= current_block_number, + Error::::TooEarlyToActivateNextRelayersSet, + ); + + let next_next_set_may_enact_at = current_block_number.saturating_add(4); // TODO + lane_relayers.activate_next_set( + next_next_set_may_enact_at, + |relayer| Self::is_registration_active(relayer.relayer()) + ); + + *lane_relayers_ref = Some(lane_relayers); + + Ok::<_, Error>(()) + })?; + } } impl Pallet { diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index cebd7e9f3a..a13d2ed99f 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -120,6 +120,13 @@ impl LaneRelayersSet bool) { + unimplemented!("TODO") + } + fn select_position_in_next_set(&self, reward: Reward) -> usize { // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it may be used // to push relayers our of the queue From e208524160c7323a40bbfed342ff27315814e584 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 13:02:33 +0300 Subject: [PATCH 08/60] continue prototyping --- modules/relayers/src/lib.rs | 12 +++++++++--- primitives/relayers/src/lane_relayers.rs | 12 ++++++++---- primitives/relayers/src/registration.rs | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index ef5db6cb9a..0c295ba673 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -406,16 +406,18 @@ pub mod pallet { Error::::TooEarlyToActivateNextRelayersSet, ); - let next_next_set_may_enact_at = current_block_number.saturating_add(4); // TODO + let new_next_set_may_enact_at = current_block_number.saturating_add(4u32.into()); // TODO lane_relayers.activate_next_set( - next_next_set_may_enact_at, - |relayer| Self::is_registration_active(relayer.relayer()) + new_next_set_may_enact_at, + |relayer| Self::is_registration_active(relayer) ); *lane_relayers_ref = Some(lane_relayers); Ok::<_, Error>(()) })?; + + Ok(()) } } @@ -668,6 +670,10 @@ pub mod pallet { TooLargeRewardToOccupyAnEntry, /// NotRegisteredAtLane, + /// + NoRelayersAtLane, + /// + TooEarlyToActivateNextRelayersSet, } /// Map of the relayer => accumulated reward. diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index a13d2ed99f..a1251320e3 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -22,7 +22,6 @@ use sp_runtime::{traits::{Get, Zero}, BoundedVec, RuntimeDebug}; /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[scale_info(skip_type_params(AccountId, Reward))] pub struct RelayerAndReward { /// A relayer account identifier. relayer: AccountId, @@ -79,8 +78,8 @@ pub struct LaneRelayersSet LaneRelayersSet where AccountId: PartialOrd, - BlockNumber: Zero, - Reward: Clone + Copy + Ord, + BlockNumber: Copy + Zero, + Reward: Copy + Ord, MaxRelayersPerLane: Get, { /// Creates new empty relayers set, where next sets enacts at given block. @@ -93,6 +92,11 @@ impl LaneRelayersSet BlockNumber { + self.next_set_may_enact_at + } + /// Returns count of relayers in the active set. pub fn active_relayers(&self) -> &[RelayerAndReward] { self.active_set.as_slice() @@ -123,7 +127,7 @@ impl LaneRelayersSet bool) { + pub fn activate_next_set(&mut self, new_next_set_may_enact_at: BlockNumber, is_relayer_active: impl Fn(&AccountId) -> bool) { unimplemented!("TODO") } diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 016ca3811d..7fe6dd88bd 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -52,7 +52,7 @@ use sp_std::fmt::Debug; /// Relayer registration. #[derive(CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] -#[scale_info(skip_type_params(BlockNumber, Balance, MaxLanesPerRelayer))] +#[scale_info(skip_type_params(MaxLanesPerRelayer))] pub struct Registration> { /// The last block number, where this registration is considered active. /// From 16aba6c75c5f1b4e71fdc6f56f9ba9c0644d9204 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 14:13:18 +0300 Subject: [PATCH 09/60] lanes now affect valid_till --- modules/relayers/src/lib.rs | 121 ++++++++++++------------ modules/relayers/src/mock.rs | 3 +- primitives/relayers/src/registration.rs | 84 ++++++++++++---- 3 files changed, 132 insertions(+), 76 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 7e3566915a..62a27a7b89 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -44,13 +44,11 @@ use bp_relayers::{ LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{DefaultNoBound, fail}; +use frame_support::fail; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; -use scale_info::TypeInfo; -use sp_runtime::{traits::{CheckedSub, One}, BoundedVec, RuntimeDebug, Saturating}; -use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData}; +use sp_runtime::{traits::{CheckedSub, One}, Saturating}; +use sp_std::marker::PhantomData; pub use pallet::*; pub use payment_adapter::DeliveryConfirmationPaymentsAdapter; @@ -202,20 +200,20 @@ pub mod pallet { // new `valid_till` must be larger (or equal) than the old one ensure!( - valid_till >= registration.valid_till, + valid_till >= registration.valid_till_ignore_lanes(), Error::::CannotReduceRegistrationLease, ); - registration.valid_till = valid_till; + registration.set_valid_till(valid_till); // reserve stake on relayer account - registration.stake = Self::update_relayer_stake( + registration.set_stake(Self::update_relayer_stake( &relayer, - registration.stake, + registration.current_stake(), registration.required_stake( Self::base_stake(), Self::stake_per_lane(), ), - )?; + )?); log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); Self::deposit_event(Event::::RegistrationUpdated { @@ -246,22 +244,17 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; - // we can't deregister until `valid_till + 1` + // we can't deregister until `valid_till + 1` block and while relayer has active + // lane registerations ensure!( - registration.valid_till < frame_system::Pallet::::block_number(), + registration.valid_till().map(|valid_till| valid_till < frame_system::Pallet::::block_number()).unwrap_or(false), Error::::RegistrationIsStillActive, ); - // we can't deregister relayer that has registered itself for some lanes - ensure!( - registration.lanes.is_empty(), - Error::::HasLaneRegistrations, - ); - // if stake is non-zero, we should do unreserve Self::update_relayer_stake( &relayer, - registration.stake, + registration.current_stake(), Zero::zero(), )?; @@ -301,11 +294,8 @@ pub mod pallet { }; // cannot add another lane registration if "base" registration is inactive - let current_block_number = SystemPallet::::block_number(); ensure!( registration.is_active( - Self::base_stake(), - Self::stake_per_lane(), SystemPallet::::block_number(), Self::required_registration_lease(), ), @@ -314,9 +304,7 @@ pub mod pallet { // cannot add another lane registration if relayer has already max allowed // lane registrations - if !registration.lanes.contains(&lane) { - ensure!(registration.lanes.try_push(lane).is_ok(), Error::::TooManyLaneRegistrations); - } + ensure!(registration.register_at_lane(lane), Error::::FailedToRegisterAtLane); // TODO: ideally we shall use the candle auction here (similar to parachain slot auctions) // let's try to claim a slot in the next set @@ -346,14 +334,14 @@ pub mod pallet { })?; // the relayer need to stake additional amount for every additional lane - registration.stake = Self::update_relayer_stake( + registration.set_stake(Self::update_relayer_stake( &relayer, - registration.stake, + registration.current_stake(), registration.required_stake( Self::base_stake(), Self::stake_per_lane(), ), - )?; + )?); // cannot add duplicate lane registration // ensure!(!registration.lanes.contains(&lane), Error::::DuplicateLaneRegistration); @@ -380,9 +368,6 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; - // ensure that the relayer has lane registration - // ensure!(registration.lanes.remove(&lane), Error::::UnregisteredAtLane); - // remove relayer from the `next_set` of lane relayers. So relayer is still LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { let mut lane_relayers = match lane_relayers_ref.take() { @@ -400,6 +385,9 @@ pub mod pallet { Ok::<_, Error>(()) })?; + // ensure that the relayer has lane registration + registration.deregister_at_lane(lane); + *maybe_registration = Some(registration); Ok(()) @@ -420,6 +408,8 @@ pub mod pallet { #[pallet::call_index(5)] #[pallet::weight(Weight::zero())] // TODO pub fn enact_next_relayers_set_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + let _ = ensure_signed(origin)?; + // remove relayer from the `next_set` of lane relayers. So relayer is still LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { let mut lane_relayers = match lane_relayers_ref.take() { @@ -458,8 +448,6 @@ pub mod pallet { pub fn is_registration_active(relayer: &T::AccountId) -> bool { match Self::registered_relayer(relayer) { Some(registration) => registration.is_active( - Self::base_stake(), - Self::stake_per_lane(), SystemPallet::::block_number(), Self::required_registration_lease(), ), @@ -490,14 +478,14 @@ pub mod pallet { match T::StakeAndSlash::repatriate_reserved( relayer, slash_destination, - registration.stake, + registration.current_stake(), ) { Ok(failed_to_slash) if failed_to_slash.is_zero() => { log::trace!( target: crate::LOG_TARGET, "Relayer account {:?} has been slashed for {:?}. Funds were deposited to {:?}", relayer, - registration.stake, + registration.current_stake(), slash_destination, ); }, @@ -507,7 +495,7 @@ pub mod pallet { "Relayer account {:?} has been partially slashed for {:?}. Funds were deposited to {:?}. \ Failed to slash: {:?}", relayer, - registration.stake, + registration.current_stake(), slash_destination, failed_to_slash, ); @@ -524,8 +512,8 @@ pub mod pallet { relayer, e, slash_destination, - registration.stake, - registration.stake, + registration.current_stake(), + registration.current_stake(), ); }, } @@ -680,16 +668,12 @@ pub mod pallet { NotRegistered, /// Failed to `deregister` relayer, because lease is still active. RegistrationIsStillActive, - /// Failed to `deregister` relayer, because he has registered himself as a - /// relayer at some lanes using `register_at_lane`. Lane registrations must - /// be explicitly removed using `deregister_at_lane` - HasLaneRegistrations, /// Failed to perform required action, because relayer registration is inactive. RegistrationIsInactive, /// Relayer is trying to register twice on the same lane. DuplicateLaneRegistration, /// Relayer has too many lane registrations. - TooManyLaneRegistrations, + FailedToRegisterAtLane, /// TooManyScheduledChanges, /// @@ -769,6 +753,12 @@ mod tests { System::::reset_events(); } + fn registration(valid_till: ThisChainBlockNumber, stake: ThisChainBalance) -> Registration { + let mut registration = Registration::new(valid_till); + registration.set_stake(stake); + registration + } + #[test] fn root_cant_claim_anything() { run_test(|| { @@ -926,7 +916,7 @@ mod tests { assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get()); assert_eq!( Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }), + Some(registration(150, Stake::get())), ); assert_eq!( @@ -935,7 +925,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }, + registration: registration(150, Stake::get()), }), topics: vec![], }), @@ -963,7 +953,7 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() + 1, lanes: BoundedVec::new() }, + registration(150, Stake::get() + 1), ); assert_noop!( @@ -980,7 +970,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() + 1, lanes: BoundedVec::new() }, + registration(150, Stake::get() + 1) ); TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() + 1).unwrap(); assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 1); @@ -994,7 +984,7 @@ mod tests { assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + 1); assert_eq!( Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }), + Some(registration(150, Stake::get())), ); assert_eq!( @@ -1003,7 +993,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() } + registration: registration(150, Stake::get()), }), topics: vec![], }), @@ -1027,7 +1017,7 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1, lanes: BoundedVec::new() }, + registration(150, Stake::get() - 1), ); Balances::set_balance(®ISTER_RELAYER, 0); @@ -1045,7 +1035,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1, lanes: BoundedVec::new() }, + registration(150, Stake::get() - 1) ); TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() - 1).unwrap(); @@ -1058,7 +1048,7 @@ mod tests { assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance - 1); assert_eq!( Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }), + Some(registration(150, Stake::get())), ); assert_eq!( @@ -1067,7 +1057,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() } + registration: registration(150, Stake::get()), }), topics: vec![], }), @@ -1144,13 +1134,13 @@ mod tests { } #[test] - fn is_registration_active_is_false_when_stake_is_too_low() { + fn is_registration_active_is_true_when_stake_is_too_low() { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1, lanes: BoundedVec::new() }, + registration(150, Stake::get() - 1) ); - assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); }); } @@ -1161,20 +1151,33 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get(), lanes: BoundedVec::new() }, + registration(150, Stake::get()), ); assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); }); } #[test] - fn is_registration_active_is_true_when_relayer_is_properly_registeered() { + fn is_registration_active_is_true_when_relayer_is_registered_at_lanes() { + run_test(|| { + System::::set_block_number(150 - Lease::get()); + + let mut registration = registration(150, Stake::get()); + assert!(registration.register_at_lane(LaneId::new(1, 2))); + + RegisteredRelayers::::insert(REGISTER_RELAYER, registration); + assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); + }); + } + + #[test] + fn is_registration_active_is_true_when_relayer_is_properly_registered() { run_test(|| { System::::set_block_number(150 - Lease::get()); RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 151, stake: Stake::get(), lanes: BoundedVec::new() }, + registration(151, Stake::get()), ); assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); }); diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 52a8902e0f..779fd414fd 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -192,6 +192,7 @@ parameter_types! { pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); pub SlotLength: u32 = 16; + pub MaxLanesPerRelayer: u32 = 4; pub PriorityBoostForLaneRelayer: TransactionPriority = 4; } @@ -304,7 +305,7 @@ impl pallet_bridge_relayers::Config for TestRuntime { type Reward = ThisChainBalance; type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; - type MaxLanesPerRelayer = ConstU32<4>; + type MaxLanesPerRelayer = MaxLanesPerRelayer; type MaxRelayersPerLane = ConstU32<4>; type SlotLength = ConstU32<16>; type PriorityBoostPerMessage = ConstU64<1>; diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 7fe6dd88bd..bc917c58e7 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -21,7 +21,7 @@ //! required finality proofs). This extension boosts priority of message delivery //! transactions, based on the number of bundled messages. So transaction with more //! messages has larger priority than the transaction with less messages. -//! See `bridge_runtime_common::priority_calculator` for details; +//! See [`crate::extension.rs`] for details. //! //! This encourages relayers to include more messages to their delivery transactions. //! At the same time, we are not verifying storage proofs before boosting @@ -62,13 +62,17 @@ pub struct Registration, + lanes: BoundedVec, } impl> Registration { @@ -86,6 +90,33 @@ impl Option { + // TODO: could be used by attacker to bump their current transaction priority while lease ends. We need to prolong valid_till + // at least by [`StakeAndSlash::RequiredRegistrationLease`] if lane is removed + if self.lanes.is_empty() { + Some(self.valid_till) + } else { + None + } + } + + /// Returns the **actual** last block number, where this registration is considered active. + /// + /// It returns the actual block number that was set by the relayer during registration. Normally, this + /// method shall not be used anywhere outside of the `pallet-bridge-relayers` calls. + pub fn valid_till_ignore_lanes(&self) -> BlockNumber { + self.valid_till + } + + /// Returns active relayer stake, which is mapped to the relayer reserved balance. + pub fn current_stake(&self) -> Balance { + self.stake.clone() + } + /// Returns minimal stake that the relayer need to have in reserve to be /// considered active. pub fn required_stake( @@ -105,26 +136,47 @@ impl bool { - // registration is inactive if relayer stake is less than required - if self.stake < self.required_stake(base_stake, stake_per_lane) { - return false - } + // if there are lane registrations, the registration is active + let valid_till = match self.valid_till() { + Some(valid_till) => valid_till, + None => return true, + }; // registration is inactive if it ends soon - let remaining_lease = self - .valid_till - .saturating_sub(current_block_number); + let remaining_lease = valid_till.saturating_sub(current_block_number); if remaining_lease <= required_registration_lease { return false } true } + + /// Set last block number, where this registration is considered active. + pub fn set_valid_till(&mut self, valid_till: BlockNumber) { + self.valid_till = valid_till; + } + + /// Set stake amount. This amount is reserved on the relayer account. + pub fn set_stake(&mut self, stake: Balance) { + self.stake = stake; + } + + /// Add another lane to the relayer registration. + pub fn register_at_lane(&mut self, lane: LaneId) -> bool { + if !self.lanes.contains(&lane) { + self.lanes.try_push(lane).is_ok() + } else { + true + } + } + + /// Remove lane registration. + pub fn deregister_at_lane(&mut self, lane: LaneId) { + self.lanes.retain(|l| *l != lane); + } } /// Relayer stake-and-slash mechanism. From f473051f36faf52ef5f0e9181e3e2550b814e7e1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 14:13:27 +0300 Subject: [PATCH 10/60] fmt --- modules/relayers/src/extension/priority.rs | 6 +- modules/relayers/src/lib.rs | 277 +++++++++++---------- modules/relayers/src/payment_adapter.rs | 3 +- primitives/relayers/src/lane_relayers.rs | 42 +++- primitives/relayers/src/registration.rs | 33 ++- 5 files changed, 204 insertions(+), 157 deletions(-) diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index d1c4a14504..7ea13ea861 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -76,13 +76,13 @@ where let active_lane_relayers = lane_relayers.active_relayers(); let lane_relayers_len: BlockNumberFor = (active_lane_relayers.len() as u32).into(); if lane_relayers_len.is_zero() { - return 0; + return 0 } // we can't deal with slots shorter than 1 block let slot_length: BlockNumberFor = R::SlotLength::get().into(); if slot_length < One::one() { - return 0; + return 0 } // let's compute current slot number @@ -98,7 +98,7 @@ where // if message delivery transaction is submitted by the relayer, assigned to the current // slot, let's boost the transaction priority if relayer != slot_relayer.relayer() { - return 0; + return 0 } R::PriorityBoostForLaneRelayer::get() diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 62a27a7b89..bfc1f1efac 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -17,22 +17,26 @@ //! Runtime module that is used to store relayer rewards and to coordinate relations //! between relayers. -// TODO: allow bridge owners to add "protected" relayers that have a guaranteed slot in the lane relayers -// TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? +// TODO: allow bridge owners to add "protected" relayers that have a guaranteed slot in the lane +// relayers TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? -// TODO: lane registration must be time-limited to force relayers to renew it and drop relayers that have stopped -// working. Otherwise one relayer may fill up all lane slots for a fixed returnable sum. +// TODO: lane registration must be time-limited to force relayers to renew it and drop relayers that +// have stopped working. Otherwise one relayer may fill up all lane slots for a fixed returnable +// sum. // -// Or we may introduce some fine for relayer for not delivering messages. This is near to impossible though. +// Or we may introduce some fine for relayer for not delivering messages. This is near to impossible +// though. // -// Or we may allow to buy lane registration using relayer rewards only - i.e. has has delivered 100 messages -// and reward is 100 DOTs. He could reserve his 100 DOTs to buy lane registration slots. Every slot costs 1 DOT. -// So after 100 slots, the registration becomes inactive. And he must renew it. +// Or we may allow to buy lane registration using relayer rewards only - i.e. has has delivered 100 +// messages and reward is 100 DOTs. He could reserve his 100 DOTs to buy lane registration slots. +// Every slot costs 1 DOT. So after 100 slots, the registration becomes inactive. And he must renew +// it. // TODO: additionally we could add a reward market - i.e. now we have boosts: -// `messages_count * per_message + per_lane`. We could add another boost if relayer wants to receive lower reward. -// E.g. if normal reward is 1 DOT per message but relayers claims that he could deliver 10 messages in exchange of -// 1 DOT, we will prefer such transaction over transaction with 10 DOTs reward. +// `messages_count * per_message + per_lane`. We could add another boost if relayer wants to receive +// lower reward. E.g. if normal reward is 1 DOT per message but relayers claims that he could +// deliver 10 messages in exchange of 1 DOT, we will prefer such transaction over transaction with +// 10 DOTs reward. // TODO: better (easier code) handling of reserved funds. Separate calls? @@ -41,13 +45,17 @@ use bp_messages::LaneId; use bp_relayers::{ - LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, + RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; -use sp_runtime::{traits::{CheckedSub, One}, Saturating}; +use sp_runtime::{ + traits::{CheckedSub, One}, + Saturating, +}; use sp_std::marker::PhantomData; pub use pallet::*; @@ -194,9 +202,8 @@ pub mod pallet { ); RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let mut registration = maybe_registration - .take() - .unwrap_or_else(|| Registration::new(valid_till)); + let mut registration = + maybe_registration.take().unwrap_or_else(|| Registration::new(valid_till)); // new `valid_till` must be larger (or equal) than the old one ensure!( @@ -209,10 +216,7 @@ pub mod pallet { registration.set_stake(Self::update_relayer_stake( &relayer, registration.current_stake(), - registration.required_stake( - Self::base_stake(), - Self::stake_per_lane(), - ), + registration.required_stake(Self::base_stake(), Self::stake_per_lane()), )?); log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); @@ -247,16 +251,15 @@ pub mod pallet { // we can't deregister until `valid_till + 1` block and while relayer has active // lane registerations ensure!( - registration.valid_till().map(|valid_till| valid_till < frame_system::Pallet::::block_number()).unwrap_or(false), + registration + .valid_till() + .map(|valid_till| valid_till < frame_system::Pallet::::block_number()) + .unwrap_or(false), Error::::RegistrationIsStillActive, ); // if stake is non-zero, we should do unreserve - Self::update_relayer_stake( - &relayer, - registration.current_stake(), - Zero::zero(), - )?; + Self::update_relayer_stake(&relayer, registration.current_stake(), Zero::zero())?; log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); @@ -269,8 +272,8 @@ pub mod pallet { /// Register relayer intention to serve given messages lane. /// - /// Relayer that registers itself at given message lane gets a priority boost for his message - /// delivery transactions, **verified** at his slots (consecutive range of blocks). + /// Relayer that registers itself at given message lane gets a priority boost for his + /// message delivery transactions, **verified** at his slots (consecutive range of blocks). #[pallet::call_index(3)] #[pallet::weight(Weight::zero())] // TODO pub fn register_at_lane( @@ -280,76 +283,86 @@ pub mod pallet { ) -> DispatchResult { let relayer = ensure_signed(origin)?; - // TODO: we probably need a way for bridge owners (sibling/parent chains) to at least set a maximal - // possible reward for their lane over XCM? + maybe change relayers set? This way they could implement - // their own incentivization mechanisms by setting reward to zero and changing relayers set on their own. + // TODO: we probably need a way for bridge owners (sibling/parent chains) to at least + // set a maximal possible reward for their lane over XCM? + maybe change relayers set? + // This way they could implement their own incentivization mechanisms by setting reward + // to zero and changing relayers set on their own. // TODO: check that `expected_reward` makes sense - RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { - // we only allow registered relayers to have priority boosts - let mut registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; - - // cannot add another lane registration if "base" registration is inactive - ensure!( - registration.is_active( - SystemPallet::::block_number(), - Self::required_registration_lease(), - ), - Error::::RegistrationIsInactive, - ); - - // cannot add another lane registration if relayer has already max allowed - // lane registrations - ensure!(registration.register_at_lane(lane), Error::::FailedToRegisterAtLane); - - // TODO: ideally we shall use the candle auction here (similar to parachain slot auctions) - // let's try to claim a slot in the next set - LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { - let mut lane_relayers = match lane_relayers_ref.take() { - Some(lane_relayers) => lane_relayers, - None => { - // TODO: give some time for initial elections - // TODO: what if all relayers that have registered for the next set then call `deregister_at_lane` - // before `next_set` activates? This could be used by malicious relayers - they could fill - // the whole `next_set` and then clear it right before it is enacted. Think we shall allow more - // entries in the `mnext_set` so that it'll be harder for the attacker to fill the full queue. - LaneRelayersSet::empty( - SystemPallet::::block_number().saturating_add(One::one()).saturating_add(4u32.into()), // TODO - ) - } + RegisteredRelayers::::try_mutate( + &relayer.clone(), + move |maybe_registration| -> DispatchResult { + // we only allow registered relayers to have priority boosts + let mut registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), }; + // cannot add another lane registration if "base" registration is inactive ensure!( - lane_relayers.next_set_try_push(relayer.clone(), expected_reward), - Error::::TooLargeRewardToOccupyAnEntry, + registration.is_active( + SystemPallet::::block_number(), + Self::required_registration_lease(), + ), + Error::::RegistrationIsInactive, ); - *lane_relayers_ref = Some(lane_relayers); - - Ok::<_, Error>(()) - })?; + // cannot add another lane registration if relayer has already max allowed + // lane registrations + ensure!( + registration.register_at_lane(lane), + Error::::FailedToRegisterAtLane + ); - // the relayer need to stake additional amount for every additional lane - registration.set_stake(Self::update_relayer_stake( - &relayer, - registration.current_stake(), - registration.required_stake( - Self::base_stake(), - Self::stake_per_lane(), - ), - )?); + // TODO: ideally we shall use the candle auction here (similar to parachain slot + // auctions) let's try to claim a slot in the next set + LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { + let mut lane_relayers = match lane_relayers_ref.take() { + Some(lane_relayers) => lane_relayers, + None => { + // TODO: give some time for initial elections + // TODO: what if all relayers that have registered for the next set + // then call `deregister_at_lane` before `next_set` activates? + // This could be used by malicious relayers - they could fill + // the whole `next_set` and then clear it right before it is + // enacted. Think we shall allow more entries in the + // `mnext_set` so that it'll be harder for the attacker to fill the + // full queue. + LaneRelayersSet::empty( + SystemPallet::::block_number() + .saturating_add(One::one()) + .saturating_add(4u32.into()), // TODO + ) + }, + }; + + ensure!( + lane_relayers.next_set_try_push(relayer.clone(), expected_reward), + Error::::TooLargeRewardToOccupyAnEntry, + ); + + *lane_relayers_ref = Some(lane_relayers); + + Ok::<_, Error>(()) + })?; + + // the relayer need to stake additional amount for every additional lane + registration.set_stake(Self::update_relayer_stake( + &relayer, + registration.current_stake(), + registration.required_stake(Self::base_stake(), Self::stake_per_lane()), + )?); - // cannot add duplicate lane registration - // ensure!(!registration.lanes.contains(&lane), Error::::DuplicateLaneRegistration); + // cannot add duplicate lane registration + // ensure!(!registration.lanes.contains(&lane), + // Error::::DuplicateLaneRegistration); - *maybe_registration = Some(registration); + *maybe_registration = Some(registration); - Ok(()) - })?; + Ok(()) + }, + )?; Ok(()) } @@ -360,38 +373,41 @@ pub mod pallet { pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { let relayer = ensure_signed(origin)?; - RegisteredRelayers::::try_mutate(&relayer.clone(), move |maybe_registration| -> DispatchResult { - // if relayer doesn't have a basic registration, we know that he is not registered - // at the lane as well - let mut registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; - - // remove relayer from the `next_set` of lane relayers. So relayer is still - LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { - let mut lane_relayers = match lane_relayers_ref.take() { - Some(lane_relayers) => lane_relayers, - None => fail!(Error::::NotRegisteredAtLane), + RegisteredRelayers::::try_mutate( + &relayer.clone(), + move |maybe_registration| -> DispatchResult { + // if relayer doesn't have a basic registration, we know that he is not + // registered at the lane as well + let mut registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), }; - ensure!( - lane_relayers.next_set_try_remove(&relayer), - Error::::NotRegisteredAtLane, - ); + // remove relayer from the `next_set` of lane relayers. So relayer is still + LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { + let mut lane_relayers = match lane_relayers_ref.take() { + Some(lane_relayers) => lane_relayers, + None => fail!(Error::::NotRegisteredAtLane), + }; - *lane_relayers_ref = Some(lane_relayers); + ensure!( + lane_relayers.next_set_try_remove(&relayer), + Error::::NotRegisteredAtLane, + ); - Ok::<_, Error>(()) - })?; + *lane_relayers_ref = Some(lane_relayers); - // ensure that the relayer has lane registration - registration.deregister_at_lane(lane); + Ok::<_, Error>(()) + })?; - *maybe_registration = Some(registration); + // ensure that the relayer has lane registration + registration.deregister_at_lane(lane); - Ok(()) - })?; + *maybe_registration = Some(registration); + + Ok(()) + }, + )?; Ok(()) } @@ -399,15 +415,18 @@ pub mod pallet { // TODO: add another `obsolete` extension for this call of the relayers pallet? /// Enact next set of relayers at a given lane. /// - /// This will replace the set of active relayers with the next scheduled set, for given lane. Anyone could - /// call this method at any point. If the set will be changed, the cost of transaction will be refunded to - /// the submitter. We do not provide any on-chain means to sync between relayers on who will submit this - /// transaction, so first transaction from anyone will be accepted and it will have the zero cost. All - /// subsequent transactions will be paid. We suggest the first relayer from the `next_set` to submit this - /// transaction. + /// This will replace the set of active relayers with the next scheduled set, for given + /// lane. Anyone could call this method at any point. If the set will be changed, the cost + /// of transaction will be refunded to the submitter. We do not provide any on-chain means + /// to sync between relayers on who will submit this transaction, so first transaction from + /// anyone will be accepted and it will have the zero cost. All subsequent transactions will + /// be paid. We suggest the first relayer from the `next_set` to submit this transaction. #[pallet::call_index(5)] #[pallet::weight(Weight::zero())] // TODO - pub fn enact_next_relayers_set_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + pub fn enact_next_relayers_set_at_lane( + origin: OriginFor, + lane: LaneId, + ) -> DispatchResult { let _ = ensure_signed(origin)?; // remove relayer from the `next_set` of lane relayers. So relayer is still @@ -425,10 +444,9 @@ pub mod pallet { ); let new_next_set_may_enact_at = current_block_number.saturating_add(4u32.into()); // TODO - lane_relayers.activate_next_set( - new_next_set_may_enact_at, - |relayer| Self::is_registration_active(relayer) - ); + lane_relayers.activate_next_set(new_next_set_may_enact_at, |relayer| { + Self::is_registration_active(relayer) + }); *lane_relayers_ref = Some(lane_relayers); @@ -594,7 +612,7 @@ pub mod pallet { to_unreserve, relayer, ); - + fail!(Error::::FailedToUnreserve) } } else if let Some(to_reserve) = required_stake.checked_sub(¤t_stake) { @@ -611,7 +629,7 @@ pub mod pallet { fail!(Error::::FailedToReserve) } } - + Ok(required_stake) } } @@ -753,7 +771,10 @@ mod tests { System::::reset_events(); } - fn registration(valid_till: ThisChainBlockNumber, stake: ThisChainBalance) -> Registration { + fn registration( + valid_till: ThisChainBlockNumber, + stake: ThisChainBalance, + ) -> Registration { let mut registration = Registration::new(valid_till); registration.set_stake(stake); registration @@ -970,7 +991,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(150, Stake::get() + 1) + registration(150, Stake::get() + 1), ); TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() + 1).unwrap(); assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 1); @@ -1035,7 +1056,7 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(150, Stake::get() - 1) + registration(150, Stake::get() - 1), ); TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() - 1).unwrap(); @@ -1138,7 +1159,7 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(150, Stake::get() - 1) + registration(150, Stake::get() - 1), ); assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); }); diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 05586a7ede..5945d72334 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -80,7 +80,8 @@ fn register_relayers_rewards( for (relayer, messages) in relayers_rewards { // sane runtime configurations guarantee that the number of messages will be below // `u32::MAX` - let relayer_reward = T::Reward::saturated_from(messages).saturating_mul(message_delivery_reward); + let relayer_reward = + T::Reward::saturated_from(messages).saturating_mul(message_delivery_reward); if relayer != *confirmation_relayer { Pallet::::register_relayer_reward(lane_id, &relayer, relayer_reward); diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index cd271f4afb..ff5bfda232 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -18,7 +18,10 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::{traits::{Get, Zero}, BoundedVec, RuntimeDebug}; +use sp_runtime::{ + traits::{Get, Zero}, + BoundedVec, RuntimeDebug, +}; /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -76,8 +79,10 @@ pub struct LaneRelayersSet, MaxRelayersPerLane>, } -impl LaneRelayersSet where - AccountId: Clone + PartialOrd, +impl + LaneRelayersSet +where + AccountId: Clone + PartialOrd, BlockNumber: Copy + Zero, Reward: Copy + Ord, MaxRelayersPerLane: Get, @@ -109,10 +114,12 @@ impl LaneRelayersSet LaneRelayersSet bool) { + pub fn activate_next_set( + &mut self, + new_next_set_may_enact_at: BlockNumber, + is_relayer_active: impl Fn(&AccountId) -> bool, + ) { self.active_set = self.next_set.clone(); // TODO } fn select_position_in_next_set(&self, reward: Reward) -> usize { - // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it may be used - // to push relayers our of the queue + // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it + // may be used to push relayers our of the queue let mut initial_position = self .next_set .binary_search_by_key(&reward, |entry| entry.reward) .unwrap_or_else(|position| position); - while self.next_set.get(initial_position).map(|entry| entry.reward == reward).unwrap_or(false) { + while self + .next_set + .get(initial_position) + .map(|entry| entry.reward == reward) + .unwrap_or(false) + { initial_position += 1; } initial_position @@ -238,8 +254,8 @@ mod tests { ], ); - // insert couple of relayer that want the same reward as some relayer in the middle of the queue - // => they are inserted **after** existing relayers + // insert couple of relayer that want the same reward as some relayer in the middle of the + // queue => they are inserted **after** existing relayers assert!(relayers.next_set_try_push(8, 10)); assert!(relayers.next_set_try_push(9, 10)); assert_eq!( diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index bc917c58e7..c3af5c11f0 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -51,9 +51,15 @@ use sp_runtime::{ use sp_std::fmt::Debug; /// Relayer registration. -#[derive(CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] +#[derive( + CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, +)] #[scale_info(skip_type_params(MaxLanesPerRelayer))] -pub struct Registration> { +pub struct Registration< + BlockNumber: Clone + Debug + PartialEq, + Balance: Clone + Debug + PartialEq, + MaxLanesPerRelayer: Get, +> { /// The last block number, where this registration is considered active. /// /// Relayer has an option to renew his registration (this may be done before it @@ -84,7 +90,12 @@ pub struct Registration, } -impl> Registration { +impl< + BlockNumber: Clone + Copy + Debug + PartialEq + PartialOrd + Saturating, + Balance: BaseArithmetic + Clone + Debug + PartialEq + Zero, + MaxLanesPerRelayer: Get, + > Registration +{ /// Creates new empty registration that ends at given block. pub fn new(valid_till: BlockNumber) -> Self { Registration { valid_till, stake: Zero::zero(), lanes: BoundedVec::new() } @@ -95,8 +106,9 @@ impl Option { - // TODO: could be used by attacker to bump their current transaction priority while lease ends. We need to prolong valid_till - // at least by [`StakeAndSlash::RequiredRegistrationLease`] if lane is removed + // TODO: could be used by attacker to bump their current transaction priority while lease + // ends. We need to prolong valid_till at least by + // [`StakeAndSlash::RequiredRegistrationLease`] if lane is removed if self.lanes.is_empty() { Some(self.valid_till) } else { @@ -106,8 +118,9 @@ impl BlockNumber { self.valid_till } @@ -119,11 +132,7 @@ impl Balance { + pub fn required_stake(&self, base_stake: Balance, stake_per_lane: Balance) -> Balance { stake_per_lane .saturating_mul(Balance::try_from(self.lanes.len()).unwrap_or(Balance::max_value())) .saturating_add(base_stake) From ec87fbb231a98530df8f2b824d668588e6706214 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 15:35:52 +0300 Subject: [PATCH 11/60] flush --- modules/relayers/src/extension/priority.rs | 2 +- modules/relayers/src/lib.rs | 20 +++++++++++--------- modules/relayers/src/mock.rs | 2 ++ modules/relayers/src/stake_adapter.rs | 10 ++++++---- primitives/relayers/src/lane_relayers.rs | 8 ++------ primitives/relayers/src/registration.rs | 7 ++++++- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 7ea13ea861..59d9c98c16 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -283,7 +283,7 @@ mod tests { assert!(relayers_set.next_set_try_push(relayer1, 0)); assert!(relayers_set.next_set_try_push(relayer2, 0)); assert!(relayers_set.next_set_try_push(relayer3, 0)); - relayers_set.activate_next_set(0, |_| true); + relayers_set.activate_next_set(0); LaneRelayers::::insert(lane_id, relayers_set); // at blocks 1..=SlotLength relayer1 gets the boost diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index bfc1f1efac..a913057ae8 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -274,6 +274,8 @@ pub mod pallet { /// /// Relayer that registers itself at given message lane gets a priority boost for his /// message delivery transactions, **verified** at his slots (consecutive range of blocks). + /// + /// Every additional lane registration requires #[pallet::call_index(3)] #[pallet::weight(Weight::zero())] // TODO pub fn register_at_lane( @@ -315,10 +317,12 @@ pub mod pallet { Error::::FailedToRegisterAtLane ); - // TODO: ideally we shall use the candle auction here (similar to parachain slot - // auctions) let's try to claim a slot in the next set - LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { - let mut lane_relayers = match lane_relayers_ref.take() { + // TODO: ideally we shall use the candle auction for relayers selection (similar to + // parachain slot auctions) rather than a fixed interval + + // let's try to claim a slot in the next set + LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { + let mut lane_relayers = match maybe_lane_relayers.take() { Some(lane_relayers) => lane_relayers, None => { // TODO: give some time for initial elections @@ -342,7 +346,7 @@ pub mod pallet { Error::::TooLargeRewardToOccupyAnEntry, ); - *lane_relayers_ref = Some(lane_relayers); + *maybe_lane_relayers = Some(lane_relayers); Ok::<_, Error>(()) })?; @@ -444,9 +448,7 @@ pub mod pallet { ); let new_next_set_may_enact_at = current_block_number.saturating_add(4u32.into()); // TODO - lane_relayers.activate_next_set(new_next_set_may_enact_at, |relayer| { - Self::is_registration_active(relayer) - }); + lane_relayers.activate_next_set(new_next_set_may_enact_at); *lane_relayers_ref = Some(lane_relayers); @@ -589,7 +591,7 @@ pub mod pallet { T::AccountId, BlockNumberFor, T::Reward, - >>::RequiredStake::get() // TODO + >>::RequiredLaneStake::get() } /// Update relayer stake. diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 779fd414fd..8fad5d1335 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -161,6 +161,7 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< Balances, ReserveId, Stake, + LaneStake, Lease, >; @@ -184,6 +185,7 @@ parameter_types! { pub const ExistentialDeposit: ThisChainBalance = 1; pub const ReserveId: [u8; 8] = *b"brdgrlrs"; pub const Stake: ThisChainBalance = 1_000; + pub const LaneStake: ThisChainBalance = 100; pub const Lease: ThisChainBlockNumber = 8; pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); pub const TransactionBaseFee: ThisChainBalance = 0; diff --git a/modules/relayers/src/stake_adapter.rs b/modules/relayers/src/stake_adapter.rs index 2509e93368..f96b26c80a 100644 --- a/modules/relayers/src/stake_adapter.rs +++ b/modules/relayers/src/stake_adapter.rs @@ -28,21 +28,23 @@ use sp_std::{fmt::Debug, marker::PhantomData}; /// /// **WARNING**: this implementation assumes that the relayers pallet is configured to /// use the [`bp_relayers::PayRewardFromAccount`] as its relayers payment scheme. -pub struct StakeAndSlashNamed( - PhantomData<(AccountId, BlockNumber, Currency, ReserveId, Stake, Lease)>, +pub struct StakeAndSlashNamed( + PhantomData<(AccountId, BlockNumber, Currency, ReserveId, Stake, LaneStake, Lease)>, ); -impl +impl StakeAndSlash - for StakeAndSlashNamed + for StakeAndSlashNamed where AccountId: Codec + Debug, Currency: NamedReservableCurrency, ReserveId: Get, Stake: Get, + LaneStake: Get, Lease: Get, { type RequiredStake = Stake; + type RequiredLaneStake = LaneStake; type RequiredRegistrationLease = Lease; fn reserve(relayer: &AccountId, amount: Currency::Balance) -> DispatchResult { diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index ff5bfda232..47b4fab19f 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -134,13 +134,9 @@ where /// Activate next set of relayers. /// /// The [`Self::active_set`] is replaced with the [`Self::next_set`]. - pub fn activate_next_set( - &mut self, - new_next_set_may_enact_at: BlockNumber, - is_relayer_active: impl Fn(&AccountId) -> bool, - ) { + pub fn activate_next_set(&mut self, new_next_set_may_enact_at: BlockNumber) { self.active_set = self.next_set.clone(); - // TODO + self.next_set_may_enact_at = new_next_set_may_enact_at; } fn select_position_in_next_set(&self, reward: Reward) -> usize { diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index c3af5c11f0..75aaec71f3 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -190,8 +190,12 @@ impl< /// Relayer stake-and-slash mechanism. pub trait StakeAndSlash { - /// The stake that the relayer must have to have its transactions boosted. + /// The stake that the relayer must have to have its message delivery transactions boosted. type RequiredStake: Get; + /// Additional stake that the relayer must have for every additional lane, where he wants to + /// get an extra boost (in addition to `[Self::RequiredStake]`) for message delivery transactions. + type RequiredLaneStake: Get; + /// Required **remaining** registration lease to be able to get transaction priority boost. /// /// If the difference between registration's `valid_till` and the current block number @@ -224,6 +228,7 @@ where BlockNumber: Default, { type RequiredStake = (); + type RequiredLaneStake = (); type RequiredRegistrationLease = (); fn reserve(_relayer: &AccountId, _amount: Balance) -> DispatchResult { From da47b6b057530fe3a2b631c0dcca278fe51df4e6 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 15:41:05 +0300 Subject: [PATCH 12/60] removed couple of obsolete TODOs --- modules/relayers/src/lib.rs | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index a913057ae8..88a7170843 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -18,27 +18,9 @@ //! between relayers. // TODO: allow bridge owners to add "protected" relayers that have a guaranteed slot in the lane -// relayers TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? - -// TODO: lane registration must be time-limited to force relayers to renew it and drop relayers that -// have stopped working. Otherwise one relayer may fill up all lane slots for a fixed returnable -// sum. -// -// Or we may introduce some fine for relayer for not delivering messages. This is near to impossible -// though. -// -// Or we may allow to buy lane registration using relayer rewards only - i.e. has has delivered 100 -// messages and reward is 100 DOTs. He could reserve his 100 DOTs to buy lane registration slots. -// Every slot costs 1 DOT. So after 100 slots, the registration becomes inactive. And he must renew -// it. - -// TODO: additionally we could add a reward market - i.e. now we have boosts: -// `messages_count * per_message + per_lane`. We could add another boost if relayer wants to receive -// lower reward. E.g. if normal reward is 1 DOT per message but relayers claims that he could -// deliver 10 messages in exchange of 1 DOT, we will prefer such transaction over transaction with -// 10 DOTs reward. - -// TODO: better (easier code) handling of reserved funds. Separate calls? +// relayers + +// TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] From 6d52f11727561e80c1ae5c4f5dc0b8dd06ecf120 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 6 Oct 2023 15:46:00 +0300 Subject: [PATCH 13/60] added lost TODO --- modules/relayers/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 88a7170843..3d5d6c1b24 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -22,6 +22,8 @@ // TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? +// TODO: remove relayer from `next_set` when he has not delivered any messages + #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] From 2a0bae9815e82dd1bc7f2791d009bbb134956c09 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 9 Oct 2023 09:43:15 +0300 Subject: [PATCH 14/60] relayers are readded to the next set only if they are delivering at least one message during epoch --- modules/messages/src/lib.rs | 1 + modules/relayers/src/lib.rs | 4 +- modules/relayers/src/payment_adapter.rs | 69 +++++++++++++++++++++++- primitives/messages/src/target_chain.rs | 2 + primitives/relayers/src/lane_relayers.rs | 18 ++++++- primitives/relayers/src/registration.rs | 8 ++- 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index c105b5ce56..f7def768e4 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -320,6 +320,7 @@ pub mod pallet { // let's now deal with relayer payments T::DeliveryPayments::pay_reward( + lane_id, relayer_id_at_this_chain, total_messages, valid_messages, diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 3d5d6c1b24..12c6cc499d 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -301,8 +301,8 @@ pub mod pallet { Error::::FailedToRegisterAtLane ); - // TODO: ideally we shall use the candle auction for relayers selection (similar to - // parachain slot auctions) rather than a fixed interval + // TODO: ideally we shall use the candle auction for relayers selection (similar + // to parachain slot auctions) rather than a fixed interval // let's try to claim a slot in the next set LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 5945d72334..2ab3ff6769 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -16,15 +16,16 @@ //! Code that allows relayers pallet to be used as a payment mechanism for the messages pallet. -use crate::{Config, Pallet}; +use crate::{Config, LaneRelayers, Pallet}; use bp_messages::{ source_chain::{DeliveryConfirmationPayments, RelayersRewards}, + target_chain::DeliveryPayments, LaneId, MessageNonce, }; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; -use frame_support::{sp_runtime::SaturatedConversion, traits::Get}; +use frame_support::{sp_runtime::SaturatedConversion, traits::Get, weights::Weight}; use sp_arithmetic::traits::{Saturating, Zero}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; @@ -68,6 +69,70 @@ where } } +impl DeliveryPayments + for DeliveryConfirmationPaymentsAdapter +where + T: Config + pallet_bridge_messages::Config, + MI: 'static, + DeliveryReward: Get, +{ + type Error = &'static str; + + fn pay_reward( + lane_id: LaneId, + relayer: T::AccountId, + _total_messages: MessageNonce, + valid_messages: MessageNonce, + _actual_weight: Weight, + ) { + if valid_messages == 0 { + return + } + + let _ = LaneRelayers::::try_mutate(lane_id, |maybe_lane_relayers| { + if let Some(lane_relayers) = maybe_lane_relayers { + // if relayer is NOT in the active set, we don't want to do anything here + let relayer_in_active_set = lane_relayers + .active_relayers() + .iter() + .filter(|r| *r.relayer() == relayer) + .next() + .cloned(); + let relayer_in_active_set = match relayer_in_active_set { + Some(relayer_in_active_set) => relayer_in_active_set, + None => return Err(()), + }; + + // if relayer is already in the active set, we don't want to do anything here + let is_in_next_set = lane_relayers + .next_relayers() + .iter() + .filter(|r| *r.relayer() == relayer) + .next() + .is_some(); + if is_in_next_set { + return Err(()) + } + + // if relayer is not willoing to work on that lane anymore, we don't want to do + // anything here + let wants_to_work_on_lane = Pallet::::registered_relayer(&relayer) + .map(|registration| registration.lanes().contains(&lane_id)) + .unwrap_or(false); + if wants_to_work_on_lane { + return Err(()) + } + + if !lane_relayers.next_set_try_push(relayer, *relayer_in_active_set.reward()) { + return Err(()) + } + } + + Ok(()) + }); + } +} + // Update rewards to given relayers, optionally rewarding confirmation relayer. fn register_relayers_rewards( confirmation_relayer: &T::AccountId, diff --git a/primitives/messages/src/target_chain.rs b/primitives/messages/src/target_chain.rs index 90ca24c3bd..501ac86746 100644 --- a/primitives/messages/src/target_chain.rs +++ b/primitives/messages/src/target_chain.rs @@ -129,6 +129,7 @@ pub trait DeliveryPayments { /// `valid_messages` have been accepted. The post-dispatch transaction weight is the /// `actual_weight`. fn pay_reward( + lane_id: LaneId, relayer: AccountId, total_messages: MessageNonce, valid_messages: MessageNonce, @@ -158,6 +159,7 @@ impl DeliveryPayments for () { type Error = &'static str; fn pay_reward( + _lane_id: LaneId, _relayer: AccountId, _total_messages: MessageNonce, _valid_messages: MessageNonce, diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 47b4fab19f..c69e19676e 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -37,6 +37,11 @@ impl RelayerAndReward { pub fn relayer(&self) -> &AccountId { &self.relayer } + + /// Return expected relayer reward. + pub fn reward(&self) -> &Reward { + &self.reward + } } /// A set of relayers that have explicitly registered themselves at a given lane. @@ -102,11 +107,16 @@ where self.next_set_may_enact_at } - /// Returns count of relayers in the active set. + /// Returns relayers in the active set. pub fn active_relayers(&self) -> &[RelayerAndReward] { self.active_set.as_slice() } + /// Returns relayers in the next set. + pub fn next_relayers(&self) -> &[RelayerAndReward] { + self.next_set.as_slice() + } + /// Try insert relayer to the next set. /// /// Returns `true` if relayer has been added to the set and false otherwise. @@ -135,7 +145,11 @@ where /// /// The [`Self::active_set`] is replaced with the [`Self::next_set`]. pub fn activate_next_set(&mut self, new_next_set_may_enact_at: BlockNumber) { - self.active_set = self.next_set.clone(); + sp_std::mem::swap(&mut self.active_set, &mut self.next_set); + // we clear next set here. Relayers from the active set will be readded here if + // they deliver at least one message in epoch and their reward will be concurrent. + // Or else, they'll need to reregister manually. + self.next_set.clear(); self.next_set_may_enact_at = new_next_set_may_enact_at; } diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 75aaec71f3..81ec5c2b60 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -130,6 +130,11 @@ impl< self.stake.clone() } + /// Returns lanes that relayer is serving with priority. + pub fn lanes(&self) -> &[LaneId] { + self.lanes.as_slice() + } + /// Returns minimal stake that the relayer need to have in reserve to be /// considered active. pub fn required_stake(&self, base_stake: Balance, stake_per_lane: Balance) -> Balance { @@ -193,7 +198,8 @@ pub trait StakeAndSlash { /// The stake that the relayer must have to have its message delivery transactions boosted. type RequiredStake: Get; /// Additional stake that the relayer must have for every additional lane, where he wants to - /// get an extra boost (in addition to `[Self::RequiredStake]`) for message delivery transactions. + /// get an extra boost (in addition to `[Self::RequiredStake]`) for message delivery + /// transactions. type RequiredLaneStake: Get; /// Required **remaining** registration lease to be able to get transaction priority boost. From 104754a6c1096da4d59ef51072737bc1ca7e2bff Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 9 Oct 2023 10:45:12 +0300 Subject: [PATCH 15/60] use expected reward from LaneRelayersSet to compute relayer reward --- modules/relayers/src/mock.rs | 7 +-- modules/relayers/src/payment_adapter.rs | 69 +++++++++++++------------ primitives/relayers/src/lib.rs | 5 ++ 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 8fad5d1335..fe09558e71 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -289,11 +289,8 @@ impl pallet_bridge_messages::Config for TestRuntime { type InboundPayload = Vec; type DeliveryPayments = (); - type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< - TestRuntime, - (), - ConstU64<100_000>, - >; + type DeliveryConfirmationPayments = + pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter; type OnMessagesDelivered = (); type MessageDispatch = DummyMessageDispatch; diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 2ab3ff6769..30763420c6 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -25,22 +25,19 @@ use bp_messages::{ }; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; -use frame_support::{sp_runtime::SaturatedConversion, traits::Get, weights::Weight}; +use frame_support::{sp_runtime::SaturatedConversion, weights::Weight}; use sp_arithmetic::traits::{Saturating, Zero}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; /// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism /// for the messages pallet. -pub struct DeliveryConfirmationPaymentsAdapter( - PhantomData<(T, MI, DeliveryReward)>, -); +pub struct DeliveryConfirmationPaymentsAdapter(PhantomData<(T, MI)>); -impl DeliveryConfirmationPayments - for DeliveryConfirmationPaymentsAdapter +impl DeliveryConfirmationPayments + for DeliveryConfirmationPaymentsAdapter where T: Config + pallet_bridge_messages::Config, MI: 'static, - DeliveryReward: Get, { type Error = &'static str; @@ -62,19 +59,16 @@ where T::BridgedChain::ID, RewardsAccountOwner::BridgedChain, ), - DeliveryReward::get(), ); rewarded_relayers as _ } } -impl DeliveryPayments - for DeliveryConfirmationPaymentsAdapter +impl DeliveryPayments for DeliveryConfirmationPaymentsAdapter where T: Config + pallet_bridge_messages::Config, MI: 'static, - DeliveryReward: Get, { type Error = &'static str; @@ -135,39 +129,33 @@ where // Update rewards to given relayers, optionally rewarding confirmation relayer. fn register_relayers_rewards( - confirmation_relayer: &T::AccountId, + _confirmation_relayer: &T::AccountId, relayers_rewards: RelayersRewards, - lane_id: RewardsAccountParams, - message_delivery_reward: T::Reward, + reward_account: RewardsAccountParams, ) { - // reward every relayer except `confirmation_relayer` - let mut confirmation_relayer_reward = T::Reward::zero(); for (relayer, messages) in relayers_rewards { - // sane runtime configurations guarantee that the number of messages will be below - // `u32::MAX` + let message_delivery_reward = LaneRelayers::::get(reward_account.lane_id()) + .and_then(|lane_relayers| { + lane_relayers + .active_relayers() + .iter() + .filter(|r| *r.relayer() == relayer) + .map(|r| *r.reward()) + .next() + }) + .unwrap_or_else(Zero::zero); // TODO: should be default reward, specified by bridge owner OR pallet-wide-default let relayer_reward = T::Reward::saturated_from(messages).saturating_mul(message_delivery_reward); - if relayer != *confirmation_relayer { - Pallet::::register_relayer_reward(lane_id, &relayer, relayer_reward); - } else { - confirmation_relayer_reward = - confirmation_relayer_reward.saturating_add(relayer_reward); - } + Pallet::::register_relayer_reward(reward_account, &relayer, relayer_reward); } - - // finally - pay reward to confirmation relayer - Pallet::::register_relayer_reward( - lane_id, - confirmation_relayer, - confirmation_relayer_reward, - ); } #[cfg(test)] mod tests { use super::*; use crate::{mock::*, RelayerRewards}; + use bp_relayers::LaneRelayersSet; const RELAYER_1: ThisChainAccountId = 1; const RELAYER_2: ThisChainAccountId = 2; @@ -177,14 +165,29 @@ mod tests { vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect() } + fn set_expected_rewards(relayers: &[ThisChainAccountId], reward: ThisChainBalance) { + LaneRelayers::::mutate( + test_reward_account_param().lane_id(), + |maybe_lane_relayers| { + let mut lane_relayers = + maybe_lane_relayers.take().unwrap_or_else(|| LaneRelayersSet::empty(0)); + for relayer in relayers { + assert!(lane_relayers.next_set_try_push(*relayer, reward)); + } + lane_relayers.activate_next_set(1); + *maybe_lane_relayers = Some(lane_relayers) + }, + ); + } + #[test] fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { run_test(|| { + set_expected_rewards(&[RELAYER_1, RELAYER_2, RELAYER_3], 50); register_relayers_rewards::( &RELAYER_2, relayers_rewards(), test_reward_account_param(), - 50, ); assert_eq!( @@ -201,11 +204,11 @@ mod tests { #[test] fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() { run_test(|| { + set_expected_rewards(&[RELAYER_1, RELAYER_2, RELAYER_3], 50); register_relayers_rewards::( &RELAYER_3, relayers_rewards(), test_reward_account_param(), - 50, ); assert_eq!( diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index b0ceef5604..ad6b493750 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -81,6 +81,11 @@ impl RewardsAccountParams { ) -> Self { Self { lane_id, bridged_chain_id, owner } } + + /// Return lane identifier. + pub fn lane_id(&self) -> LaneId { + self.lane_id + } } impl TypeId for RewardsAccountParams { From 034b38f647f4b7b6109ea5ddac5e104f6fc9d65e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 9 Oct 2023 10:56:35 +0300 Subject: [PATCH 16/60] added comment to wrong code --- modules/relayers/src/payment_adapter.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 30763420c6..3e853fa438 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -134,6 +134,9 @@ fn register_relayers_rewards( reward_account: RewardsAccountParams, ) { for (relayer, messages) in relayers_rewards { + // TODO: THIS IS WRONG - `LaneRelayers` is for relayers, which are delivering messages from BRIDGED to THIS + // chain and `register_relayers_rewards` is called for relayers that are delivering messages from THIS + // to BRIDGED chain let message_delivery_reward = LaneRelayers::::get(reward_account.lane_id()) .and_then(|lane_relayers| { lane_relayers From 5df0872b9ab3c9a02276fa6987be3fe0c195be8d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 9 Oct 2023 16:13:04 +0300 Subject: [PATCH 17/60] reward is now a part of unrewarded relayers vector --- Cargo.lock | 1 + modules/messages/src/call_ext.rs | 9 +- modules/messages/src/inbound_lane.rs | 27 +++--- modules/messages/src/lanes_manager.rs | 11 +-- modules/messages/src/lib.rs | 22 ++--- modules/messages/src/outbound_lane.rs | 42 +++++---- modules/messages/src/proofs.rs | 10 +-- .../messages/src/tests/messages_generation.rs | 6 +- modules/messages/src/tests/mock.rs | 16 ++-- modules/messages/src/tests/pallet_tests.rs | 17 ++-- primitives/messages/Cargo.toml | 2 + primitives/messages/src/lib.rs | 86 ++++++++++++------- primitives/messages/src/source_chain.rs | 16 ++-- 13 files changed, 152 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69c74bf0d0..cfa75275cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1256,6 +1256,7 @@ dependencies = [ "serde", "sp-core", "sp-io", + "sp-runtime", "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=master)", ] diff --git a/modules/messages/src/call_ext.rs b/modules/messages/src/call_ext.rs index 9e3eb829b6..2b6bcf9fe5 100644 --- a/modules/messages/src/call_ext.rs +++ b/modules/messages/src/call_ext.rs @@ -24,7 +24,7 @@ use bp_messages::{ LaneId, MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, }; -use bp_runtime::AccountIdOf; +use bp_runtime::{AccountIdOf, BalanceOf}; use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; use sp_runtime::transaction_validity::TransactionValidity; @@ -74,6 +74,7 @@ impl, I: 'static> CallHelper { /// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`. pub trait CallSubType, I: 'static>: IsSubType, T>> +where T: frame_system::Config> { /// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call. fn receive_messages_proof_info(&self) -> Option; @@ -100,6 +101,7 @@ impl< T: frame_system::Config + Config, I: 'static, > CallSubType for T::RuntimeCall +where T: frame_system::Config> { fn receive_messages_proof_info(&self) -> Option { if let Some(crate::Call::::receive_messages_proof { ref proof, .. }) = @@ -202,7 +204,7 @@ impl< /// Returns occupation state of unrewarded relayers vector. fn unrewarded_relayers_occupation, I: 'static>( - inbound_lane_data: &InboundLaneData>>, + inbound_lane_data: &InboundLaneData>, BalanceOf>>, ) -> UnrewardedRelayerOccupation { UnrewardedRelayerOccupation { free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX @@ -237,7 +239,7 @@ mod tests { for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX { inbound_lane_state.relayers.push_back(UnrewardedRelayer { relayer: Default::default(), - messages: DeliveredMessages { begin: n + 1, end: n + 1 }, + messages: DeliveredMessages { begin: n + 1, end: n + 1, reward: 0 }, }); } InboundLanes::::insert(test_lane_id(), inbound_lane_state); @@ -250,6 +252,7 @@ mod tests { messages: DeliveredMessages { begin: 1, end: BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + reward: 0, }, }); InboundLanes::::insert(test_lane_id(), inbound_lane_state); diff --git a/modules/messages/src/inbound_lane.rs b/modules/messages/src/inbound_lane.rs index 8e5d213a84..3f7d34ac22 100644 --- a/modules/messages/src/inbound_lane.rs +++ b/modules/messages/src/inbound_lane.rs @@ -23,16 +23,18 @@ use bp_messages::{ ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, LaneState, MessageKey, MessageNonce, OutboundLaneData, ReceivalResult, UnrewardedRelayer, }; -use bp_runtime::AccountIdOf; +use bp_runtime::{AccountIdOf, BalanceOf}; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::{Type, TypeInfo}; -use sp_runtime::RuntimeDebug; +use sp_runtime::{traits::{One, Saturating}, RuntimeDebug}; use sp_std::prelude::PartialEq; /// Inbound lane storage. pub trait InboundLaneStorage { /// Id of relayer on source chain. type Relayer: Clone + PartialEq; + /// Balance of the source chain. + type Balance: Clone + One + PartialEq + Saturating; /// Lane id. fn id(&self) -> LaneId; @@ -41,9 +43,9 @@ pub trait InboundLaneStorage { /// Return maximal number of unconfirmed messages in inbound lane. fn max_unconfirmed_messages(&self) -> MessageNonce; /// Get lane data from the storage. - fn data(&self) -> InboundLaneData; + fn data(&self) -> InboundLaneData; /// Update lane data in the storage. - fn set_data(&mut self, data: InboundLaneData); + fn set_data(&mut self, data: InboundLaneData); /// Purge lane data from the storage. fn purge(self); } @@ -58,11 +60,11 @@ pub trait InboundLaneStorage { /// The encoding of this type matches encoding of the corresponding `MessageData`. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] pub struct StoredInboundLaneData, I: 'static>( - pub InboundLaneData>>, + pub InboundLaneData>, BalanceOf>>, ); impl, I: 'static> sp_std::ops::Deref for StoredInboundLaneData { - type Target = InboundLaneData>>; + type Target = InboundLaneData>, BalanceOf>>; fn deref(&self) -> &Self::Target { &self.0 @@ -82,7 +84,7 @@ impl, I: 'static> Default for StoredInboundLaneData { } impl, I: 'static> From> - for InboundLaneData>> + for InboundLaneData>, BalanceOf>> { fn from(data: StoredInboundLaneData) -> Self { data.0 @@ -90,7 +92,7 @@ impl, I: 'static> From> } impl, I: 'static> EncodeLike> - for InboundLaneData>> + for InboundLaneData>, BalanceOf>> { } @@ -98,13 +100,13 @@ impl, I: 'static> TypeInfo for StoredInboundLaneData { type Identity = Self; fn type_info() -> Type { - InboundLaneData::>>::type_info() + InboundLaneData::>, BalanceOf>>::type_info() } } impl, I: 'static> MaxEncodedLen for StoredInboundLaneData { fn max_encoded_len() -> usize { - InboundLaneData::>>::encoded_size_hint( + InboundLaneData::>, BalanceOf>>::encoded_size_hint( BridgedChainOf::::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize, ) .unwrap_or(usize::MAX) @@ -210,14 +212,15 @@ impl InboundLane { }); // now let's update inbound lane storage + let relayer_reward_per_message = One::one(); // TODO: it must be returned by some callback!!! match data.relayers.back_mut() { - Some(entry) if entry.relayer == *relayer_at_bridged_chain => { + Some(entry) if entry.relayer == *relayer_at_bridged_chain && entry.messages.reward == relayer_reward_per_message => { entry.messages.note_dispatched_message(); }, _ => { data.relayers.push_back(UnrewardedRelayer { relayer: relayer_at_bridged_chain.clone(), - messages: DeliveredMessages::new(nonce), + messages: DeliveredMessages::new(nonce, relayer_reward_per_message), }); }, }; diff --git a/modules/messages/src/lanes_manager.rs b/modules/messages/src/lanes_manager.rs index a112b93d2d..6a6bbcd696 100644 --- a/modules/messages/src/lanes_manager.rs +++ b/modules/messages/src/lanes_manager.rs @@ -24,7 +24,7 @@ use bp_messages::{ target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneId, LaneState, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; -use bp_runtime::AccountIdOf; +use bp_runtime::{AccountIdOf, BalanceOf}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ensure, PalletError}; use scale_info::TypeInfo; @@ -137,7 +137,7 @@ impl, I: 'static> LanesManager { /// Runtime inbound lane storage. pub struct RuntimeInboundLaneStorage, I: 'static = ()> { pub(crate) lane_id: LaneId, - pub(crate) cached_data: InboundLaneData>>, + pub(crate) cached_data: InboundLaneData>, BalanceOf>>, pub(crate) _phantom: PhantomData, } @@ -194,7 +194,7 @@ impl, I: 'static> RuntimeInboundLaneStorage { let max_encoded_len = StoredInboundLaneData::::max_encoded_len(); let relayers_count = self.data().relayers.len(); let actual_encoded_len = - InboundLaneData::>>::encoded_size_hint(relayers_count) + InboundLaneData::>, BalanceOf>>::encoded_size_hint(relayers_count) .unwrap_or(usize::MAX); max_encoded_len.saturating_sub(actual_encoded_len) as _ } @@ -202,6 +202,7 @@ impl, I: 'static> RuntimeInboundLaneStorage { impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage { type Relayer = AccountIdOf>; + type Balance = BalanceOf>; fn id(&self) -> LaneId { self.lane_id @@ -215,11 +216,11 @@ impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage< BridgedChainOf::::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX } - fn data(&self) -> InboundLaneData>> { + fn data(&self) -> InboundLaneData>, BalanceOf>> { self.cached_data.clone() } - fn set_data(&mut self, data: InboundLaneData>>) { + fn set_data(&mut self, data: InboundLaneData>, BalanceOf>>) { self.cached_data = data.clone(); InboundLanes::::insert(self.lane_id, StoredInboundLaneData::(data)) } diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index f7def768e4..53f92db7cc 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -60,12 +60,12 @@ use bp_messages::{ DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch, ProvedLaneMessages, ProvedMessages, }, - ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, + ChainWithMessages, InboundLaneData, InboundMessageDetails, LaneId, MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ - AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, + AccountIdOf, BalanceOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, }; use codec::{Decode, Encode}; @@ -127,7 +127,7 @@ pub mod pallet { type DeliveryPayments: DeliveryPayments; /// Handler for relayer payments that happen during message delivery confirmation /// transaction. - type DeliveryConfirmationPayments: DeliveryConfirmationPayments; + type DeliveryConfirmationPayments: DeliveryConfirmationPayments>, BalanceOf>>; /// Delivery confirmation callback. type OnMessagesDelivered: OnMessagesDelivered; @@ -153,7 +153,7 @@ pub mod pallet { } #[pallet::call] - impl, I: 'static> Pallet { + impl, I: 'static> Pallet where T: frame_system::Config> { /// Change `PalletOwner`. /// /// May only be called either by root, or by `PalletOwner`. @@ -382,12 +382,12 @@ pub mod pallet { ) .map_err(Error::::ReceivalConfirmation)?; - if let Some(confirmed_messages) = confirmed_messages { + if let Some(received_range) = confirmed_messages { // emit 'delivered' event - let received_range = confirmed_messages.begin..=confirmed_messages.end; Self::deposit_event(Event::MessagesDelivered { lane_id, - messages: confirmed_messages, + messages_begin: *received_range.start(), + messages_end: *received_range.end(), }); // if some new messages have been confirmed, reward relayers @@ -453,8 +453,10 @@ pub mod pallet { MessagesDelivered { /// Lane for which the delivery has been confirmed. lane_id: LaneId, - /// Delivered messages. - messages: DeliveredMessages, + /// Nonce of the first delivered message. + messages_begin: MessageNonce, + /// Nonce of the last delivered message. + messages_end: MessageNonce, }, } @@ -576,7 +578,7 @@ pub mod pallet { /// Return inbound lane data. pub fn inbound_lane_data( lane: LaneId, - ) -> Option>>> { + ) -> Option>, BalanceOf>>> { InboundLanes::::get(lane).map(|lane| lane.0) } } diff --git a/modules/messages/src/outbound_lane.rs b/modules/messages/src/outbound_lane.rs index cdca7e4194..e022c104da 100644 --- a/modules/messages/src/outbound_lane.rs +++ b/modules/messages/src/outbound_lane.rs @@ -19,9 +19,10 @@ use crate::{Config, LOG_TARGET}; use bp_messages::{ - ChainWithMessages, DeliveredMessages, LaneId, LaneState, MessageNonce, MessagePayload, + ChainWithMessages, LaneId, LaneState, MessageNonce, MessagePayload, OutboundLaneData, UnrewardedRelayer, VerificationError, }; +use bp_runtime::RangeInclusiveExt; use codec::{Decode, Encode}; use frame_support::{traits::Get, BoundedVec, PalletError}; use scale_info::TypeInfo; @@ -132,24 +133,21 @@ impl OutboundLane { } /// Confirm messages delivery. - pub fn confirm_delivery( + pub fn confirm_delivery( &mut self, max_allowed_messages: MessageNonce, latest_delivered_nonce: MessageNonce, - relayers: &VecDeque>, - ) -> Result, ReceivalConfirmationError> { + relayers: &VecDeque>, + ) -> Result>, ReceivalConfirmationError> { let mut data = self.storage.data(); - let confirmed_messages = DeliveredMessages { - begin: data.latest_received_nonce.saturating_add(1), - end: latest_delivered_nonce, - }; - if confirmed_messages.total_messages() == 0 { + let confirmed_messages = data.latest_received_nonce.saturating_add(1)..=latest_delivered_nonce; + if confirmed_messages.saturating_len() == 0 { return Ok(None) } - if confirmed_messages.end > data.latest_generated_nonce { + if *confirmed_messages.end() > data.latest_generated_nonce { return Err(ReceivalConfirmationError::FailedToConfirmFutureMessages) } - if confirmed_messages.total_messages() > max_allowed_messages { + if confirmed_messages.saturating_len() > max_allowed_messages { // that the relayer has declared correct number of messages that the proof contains (it // is checked outside of the function). But it may happen (but only if this/bridged // chain storage is corrupted, though) that the actual number of confirmed messages if @@ -158,20 +156,20 @@ impl OutboundLane { log::trace!( target: LOG_TARGET, "Messages delivery proof contains too many messages to confirm: {} vs declared {}", - confirmed_messages.total_messages(), + confirmed_messages.saturating_len(), max_allowed_messages, ); return Err(ReceivalConfirmationError::TryingToConfirmMoreMessagesThanExpected) } - ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?; + ensure_unrewarded_relayers_are_correct(*confirmed_messages.end(), relayers)?; // prune all confirmed messages - for nonce in confirmed_messages.begin..=confirmed_messages.end { + for nonce in *confirmed_messages.start()..=*confirmed_messages.end() { self.storage.remove_message(&nonce); } - data.latest_received_nonce = confirmed_messages.end; + data.latest_received_nonce = *confirmed_messages.end(); data.oldest_unpruned_nonce = data.latest_received_nonce.saturating_add(1); self.storage.set_data(data); @@ -196,9 +194,9 @@ impl OutboundLane { /// /// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged /// chain has invalid runtime storage. -fn ensure_unrewarded_relayers_are_correct( +fn ensure_unrewarded_relayers_are_correct( latest_received_nonce: MessageNonce, - relayers: &VecDeque>, + relayers: &VecDeque>, ) -> Result<(), ReceivalConfirmationError> { let mut expected_entry_begin = relayers.front().map(|entry| entry.messages.begin); for entry in relayers { @@ -232,20 +230,20 @@ mod tests { fn unrewarded_relayers( nonces: RangeInclusive, - ) -> VecDeque> { + ) -> VecDeque> { vec![unrewarded_relayer(*nonces.start(), *nonces.end(), 0)] .into_iter() .collect() } - fn delivered_messages(nonces: RangeInclusive) -> DeliveredMessages { - DeliveredMessages { begin: *nonces.start(), end: *nonces.end() } + fn delivered_messages(nonces: RangeInclusive) -> RangeInclusive { + nonces } fn assert_3_messages_confirmation_fails( latest_received_nonce: MessageNonce, - relayers: &VecDeque>, - ) -> Result, ReceivalConfirmationError> { + relayers: &VecDeque>, + ) -> Result>, ReceivalConfirmationError> { run_test(|| { let mut lane = active_outbound_lane::(test_lane_id()).unwrap(); assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); diff --git a/modules/messages/src/proofs.rs b/modules/messages/src/proofs.rs index f4a6923030..3bdc1a0f8e 100644 --- a/modules/messages/src/proofs.rs +++ b/modules/messages/src/proofs.rs @@ -16,7 +16,7 @@ //! Tools for messages and delivery proof verification. -use crate::{BridgedChainOf, BridgedHeaderChainOf, Config}; +use crate::{BridgedChainOf, BridgedHeaderChainOf, Config, ThisChainOf}; use bp_header_chain::HeaderChain; use bp_messages::{ @@ -25,12 +25,12 @@ use bp_messages::{ ChainWithMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; -use bp_runtime::{HashOf, RangeInclusiveExt, VerifiedStorageProof}; +use bp_runtime::{AccountIdOf, BalanceOf, HashOf, RangeInclusiveExt, VerifiedStorageProof}; use sp_std::vec::Vec; /// 'Parsed' message delivery proof - inbound lane id and its state. -pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = - (LaneId, InboundLaneData<::AccountId>); +pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = + (LaneId, InboundLaneData>, BalanceOf>>); /// Verify proof of Bridged -> This chain messages. /// @@ -97,7 +97,7 @@ pub fn verify_messages_proof, I: 'static>( /// Verify proof of This -> Bridged chain messages delivery. pub fn verify_messages_delivery_proof, I: 'static>( proof: FromBridgedChainMessagesDeliveryProof>>, -) -> Result, VerificationError> { +) -> Result, VerificationError> { let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof; let mut storage = T::BridgedHeaderChain::verify_storage_proof(bridged_header_hash, storage_proof) diff --git a/modules/messages/src/tests/messages_generation.rs b/modules/messages/src/tests/messages_generation.rs index 567d77625e..d5bab23122 100644 --- a/modules/messages/src/tests/messages_generation.rs +++ b/modules/messages/src/tests/messages_generation.rs @@ -21,7 +21,7 @@ use bp_messages::{ MessagePayload, OutboundLaneData, }; use bp_runtime::{ - grow_storage_value, AccountIdOf, Chain, HashOf, HasherOf, RangeInclusiveExt, + grow_storage_value, AccountIdOf, BalanceOf, Chain, HashOf, HasherOf, RangeInclusiveExt, UnverifiedStorageProof, UnverifiedStorageProofParams, }; use codec::Encode; @@ -97,7 +97,7 @@ where /// Prepare storage proof that proves given messages delivery. pub fn prepare_message_delivery_storage_proof( lane: LaneId, - inbound_lane_data: InboundLaneData>, + inbound_lane_data: InboundLaneData, BalanceOf>, proof_params: UnverifiedStorageProofParams, ) -> (HashOf, UnverifiedStorageProof) where @@ -209,7 +209,7 @@ where /// Returns state trie root and partial storage trie. fn do_prepare_message_delivery_storage_proof( lane: LaneId, - inbound_lane_data: InboundLaneData>, + inbound_lane_data: InboundLaneData, BalanceOf>, proof_params: UnverifiedStorageProofParams, ) -> (HashOf, UnverifiedStorageProof) where diff --git a/modules/messages/src/tests/mock.rs b/modules/messages/src/tests/mock.rs index 3b1c6cf430..9cdf74614b 100644 --- a/modules/messages/src/tests/mock.rs +++ b/modules/messages/src/tests/mock.rs @@ -27,7 +27,7 @@ use crate::{ use bp_header_chain::{ChainWithGrandpa, StoredHeaderData}; use bp_messages::{ - calc_relayers_rewards, + calc_relayers_rewards_at_source, source_chain::{DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof}, target_chain::{ DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof, @@ -73,6 +73,7 @@ pub struct TestPayload { pub type TestMessageFee = u64; pub type TestRelayer = u64; pub type TestDispatchLevelResult = (); +pub type TestBalance = Balance; pub struct ThisChain; @@ -329,6 +330,7 @@ impl DeliveryPayments for TestDeliveryPayments { type Error = &'static str; fn pay_reward( + _lane_id: LaneId, relayer: AccountId, _total_messages: MessageNonce, _valid_messages: MessageNonce, @@ -352,16 +354,16 @@ impl TestDeliveryConfirmationPayments { } } -impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { +impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { type Error = &'static str; fn pay_reward( _lane_id: LaneId, - messages_relayers: VecDeque>, + messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, received_range: &RangeInclusive, ) -> MessageNonce { - let relayers_rewards = calc_relayers_rewards(messages_relayers, received_range); + let relayers_rewards = calc_relayers_rewards_at_source(messages_relayers, received_range); let rewarded_relayers = relayers_rewards.len(); for (relayer, reward) in &relayers_rewards { let key = (b":relayer-reward:", relayer, reward).encode(); @@ -462,8 +464,8 @@ pub fn unrewarded_relayer( begin: MessageNonce, end: MessageNonce, relayer: TestRelayer, -) -> UnrewardedRelayer { - UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } } +) -> UnrewardedRelayer { + UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end, reward: 1 } } } /// Returns unrewarded relayers state at given lane. @@ -546,7 +548,7 @@ pub fn prepare_messages_proof( /// `asset_noop` macro calls. pub fn prepare_messages_delivery_proof( lane: LaneId, - inbound_lane_data: InboundLaneData, + inbound_lane_data: InboundLaneData, ) -> FromBridgedChainMessagesDeliveryProof { // first - let's generate storage proof let (storage_root, storage_proof) = diff --git a/modules/messages/src/tests/pallet_tests.rs b/modules/messages/src/tests/pallet_tests.rs index 22b2cdf1ff..f8f0b86032 100644 --- a/modules/messages/src/tests/pallet_tests.rs +++ b/modules/messages/src/tests/pallet_tests.rs @@ -87,7 +87,7 @@ fn receive_messages_delivery_proof() { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, - messages: DeliveredMessages::new(1), + messages: DeliveredMessages::new(1, 0), }] .into(), }, @@ -106,7 +106,8 @@ fn receive_messages_delivery_proof() { phase: Phase::Initialization, event: TestEvent::Messages(Event::MessagesDelivered { lane_id: test_lane_id(), - messages: DeliveredMessages::new(1), + messages_begin: 1, + messages_end: 1, }), topics: vec![], }], @@ -830,7 +831,7 @@ fn proof_size_refund_from_receive_messages_proof_works() { relayers: vec![ UnrewardedRelayer { relayer: 42, - messages: DeliveredMessages { begin: 0, end: 100 } + messages: DeliveredMessages { begin: 0, end: 100, reward: 0 } }; max_entries ] @@ -859,7 +860,7 @@ fn proof_size_refund_from_receive_messages_proof_works() { relayers: vec![ UnrewardedRelayer { relayer: 42, - messages: DeliveredMessages { begin: 0, end: 100 } + messages: DeliveredMessages { begin: 0, end: 100, reward: 0 } }; max_entries - 1 ] @@ -972,7 +973,7 @@ fn test_bridge_messages_call_is_correctly_defined() { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, - messages: DeliveredMessages::new(1), + messages: DeliveredMessages::new(1, 0), }] .into(), }, @@ -1032,8 +1033,8 @@ generate_owned_bridge_module_tests!( #[test] fn inbound_storage_extra_proof_size_bytes_works() { - fn relayer_entry() -> UnrewardedRelayer { - UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100 } } + fn relayer_entry() -> UnrewardedRelayer { + UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100, reward: 0 } } } fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { @@ -1128,7 +1129,7 @@ fn receive_messages_delivery_proof_fails_if_outbound_lane_is_unknown() { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, - messages: DeliveredMessages::new(1), + messages: DeliveredMessages::new(1, 0), }] .into(), }, diff --git a/primitives/messages/Cargo.toml b/primitives/messages/Cargo.toml index 1a3c829b6d..e029348c0b 100644 --- a/primitives/messages/Cargo.toml +++ b/primitives/messages/Cargo.toml @@ -21,6 +21,7 @@ bp-header-chain = { path = "../header-chain", default-features = false } frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false } [dev-dependencies] @@ -38,5 +39,6 @@ std = [ "serde/std", "sp-core/std", "sp-io/std", + "sp-runtime/std", "sp-std/std", ] diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index dd2a0ea233..92866eca60 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -24,16 +24,17 @@ use bp_runtime::{ messages::MessageDispatchResult, BasicOperatingMode, Chain, OperatingMode, RangeInclusiveExt, StorageProofError, UnderlyingChainOf, UnderlyingChainProvider, }; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Compact, Decode, Encode, MaxEncodedLen}; use frame_support::PalletError; // Weight is reexported to avoid additional frame-support dependencies in related crates. pub use frame_support::weights::Weight; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -use source_chain::RelayersRewards; +use source_chain::RelayersRewardsAtSource; use sp_core::{RuntimeDebug, TypeId, H256}; use sp_io::hashing::blake2_256; -use sp_std::{collections::vec_deque::VecDeque, ops::RangeInclusive, prelude::*}; +use sp_runtime::{traits::AtLeast32BitUnsigned, SaturatedConversion}; +use sp_std::{collections::{btree_map::Entry, vec_deque::VecDeque}, ops::RangeInclusive, prelude::*}; pub use call_info::{ BaseMessagesProofInfo, BridgeMessagesCall, BridgeMessagesCallOf, MessagesCallInfo, @@ -307,7 +308,7 @@ pub struct Message { /// Inbound lane data. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct InboundLaneData { +pub struct InboundLaneData { /// Inbound lane state. /// /// If state is `Closed`, then all attempts to deliver messages to this end will fail. @@ -330,7 +331,7 @@ pub struct InboundLaneData { /// When a relayer sends a single message, both of MessageNonces are the same. /// When relayer sends messages in a batch, the first arg is the lowest nonce, second arg the /// highest nonce. Multiple dispatches from the same relayer are allowed. - pub relayers: VecDeque>, + pub relayers: VecDeque>, /// Nonce of the last message that /// a) has been delivered to the target (this) chain and @@ -343,7 +344,7 @@ pub struct InboundLaneData { pub last_confirmed_nonce: MessageNonce, } -impl Default for InboundLaneData { +impl Default for InboundLaneData { fn default() -> Self { InboundLaneData { state: LaneState::Closed, @@ -353,7 +354,7 @@ impl Default for InboundLaneData { } } -impl InboundLaneData { +impl InboundLaneData { /// Returns default inbound lane data with opened state. pub fn opened() -> Self { InboundLaneData { state: LaneState::Opened, ..Default::default() } @@ -366,9 +367,12 @@ impl InboundLaneData { pub fn encoded_size_hint(relayers_entries: usize) -> Option where RelayerId: MaxEncodedLen, + RewardAtSource: MaxEncodedLen, { relayers_entries - .checked_mul(UnrewardedRelayer::::max_encoded_len())? + .checked_mul(UnrewardedRelayer::::max_encoded_len())? + .checked_add(Compact::(relayers_entries as u32).encoded_size())? + .checked_add(LaneState::max_encoded_len())? .checked_add(MessageNonce::max_encoded_len()) } @@ -379,6 +383,7 @@ impl InboundLaneData { pub fn encoded_size_hint_u32(relayers_entries: usize) -> u32 where RelayerId: MaxEncodedLen, + RewardAtSource: MaxEncodedLen, { Self::encoded_size_hint(relayers_entries) .and_then(|x| u32::try_from(x).ok()) @@ -436,11 +441,11 @@ pub struct InboundMessageDetails { /// This struct represents a continuous range of messages that have been delivered by the same /// relayer and whose confirmations are still pending. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct UnrewardedRelayer { - /// Identifier of the relayer. +pub struct UnrewardedRelayer { + /// Identifier of the relayer at the source (sending) chain. pub relayer: RelayerId, /// Messages range, delivered by this relayer. - pub messages: DeliveredMessages, + pub messages: DeliveredMessages, } /// Received messages with their dispatch result. @@ -485,18 +490,22 @@ pub enum ReceivalResult { /// Delivered messages with their dispatch result. #[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct DeliveredMessages { +pub struct DeliveredMessages { /// Nonce of the first message that has been delivered (inclusive). pub begin: MessageNonce, /// Nonce of the last message that has been delivered (inclusive). pub end: MessageNonce, + /// Reward that needs to be paid at the source chain (during confirmation transaction) + /// for every delivered message in the `begin..=end` range. If reward has been paid at the + /// target chain, it may be zero. + pub reward: RewardAtSource, } -impl DeliveredMessages { +impl DeliveredMessages { /// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given /// dispatch result. - pub fn new(nonce: MessageNonce) -> Self { - DeliveredMessages { begin: nonce, end: nonce } + pub fn new(nonce: MessageNonce, reward: RewardAtSource) -> Self { + DeliveredMessages { begin: nonce, end: nonce, reward } } /// Return total count of delivered messages. @@ -534,13 +543,13 @@ pub struct UnrewardedRelayersState { impl UnrewardedRelayersState { /// Verify that the relayers state corresponds with the `InboundLaneData`. - pub fn is_valid(&self, lane_data: &InboundLaneData) -> bool { + pub fn is_valid(&self, lane_data: &InboundLaneData) -> bool { self == &lane_data.into() } } -impl From<&InboundLaneData> for UnrewardedRelayersState { - fn from(lane: &InboundLaneData) -> UnrewardedRelayersState { +impl From<&InboundLaneData> for UnrewardedRelayersState { + fn from(lane: &InboundLaneData) -> UnrewardedRelayersState { UnrewardedRelayersState { unrewarded_relayer_entries: lane.relayers.len() as _, messages_in_oldest_entry: lane @@ -591,21 +600,38 @@ impl Default for OutboundLaneData { } /// Calculate the number of messages that the relayers have delivered. -pub fn calc_relayers_rewards( - messages_relayers: VecDeque>, +pub fn calc_relayers_rewards_at_source( + messages_relayers: VecDeque>, received_range: &RangeInclusive, -) -> RelayersRewards +) -> RelayersRewardsAtSource where AccountId: sp_std::cmp::Ord, + RewardAtSource: AtLeast32BitUnsigned + Clone/*Bounded + Clone + Saturating + UniqueSaturatedFrom + Zero*/, { // remember to reward relayers that have delivered messages // this loop is bounded by `T::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` on the bridged chain - let mut relayers_rewards = RelayersRewards::new(); + let mut relayers_rewards: RelayersRewardsAtSource = RelayersRewardsAtSource::new(); for entry in messages_relayers { + if entry.messages.reward.is_zero() { + continue; + } + let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end()); - if nonce_end >= nonce_begin { - *relayers_rewards.entry(entry.relayer).or_default() += nonce_end - nonce_begin + 1; + let new_confirmations = nonce_begin..=nonce_end; + let new_confirmations_count = new_confirmations.saturating_len(); + if new_confirmations_count == 0 { + continue; + } + + let new_reward = RewardAtSource::saturated_from(new_confirmations_count).saturating_mul(entry.messages.reward); + match relayers_rewards.entry(entry.relayer) { + Entry::Occupied(mut e) => { + e.insert(e.get().clone().saturating_add(new_reward)); + }, + Entry::Vacant(e) => { + e.insert(new_reward); + } } } relayers_rewards @@ -642,7 +668,7 @@ mod tests { #[test] fn lane_is_closed_by_default() { - assert_eq!(InboundLaneData::<()>::default().state, LaneState::Closed); + assert_eq!(InboundLaneData::<(), ()>::default().state, LaneState::Closed); assert_eq!(OutboundLaneData::default().state, LaneState::Closed); } @@ -651,10 +677,10 @@ mod tests { let lane_data = InboundLaneData { state: LaneState::Opened, relayers: vec![ - UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0) }, + UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0, 0) }, UnrewardedRelayer { relayer: 2, - messages: DeliveredMessages::new(MessageNonce::MAX), + messages: DeliveredMessages::new(MessageNonce::MAX, 0), }, ] .into_iter() @@ -675,13 +701,13 @@ mod tests { (13u8, 128u8), ]; for (relayer_entries, messages_count) in test_cases { - let expected_size = InboundLaneData::::encoded_size_hint(relayer_entries as _); + let expected_size = InboundLaneData::::encoded_size_hint(relayer_entries as _); let actual_size = InboundLaneData { state: LaneState::Opened, relayers: (1u8..=relayer_entries) .map(|i| UnrewardedRelayer { relayer: i, - messages: DeliveredMessages::new(i as _), + messages: DeliveredMessages::new(i as _, 0u16), }) .collect(), last_confirmed_nonce: messages_count as _, @@ -698,7 +724,7 @@ mod tests { #[test] fn contains_result_works() { - let delivered_messages = DeliveredMessages { begin: 100, end: 150 }; + let delivered_messages = DeliveredMessages { begin: 100, end: 150, reward: 0 }; assert!(!delivered_messages.contains_message(99)); assert!(delivered_messages.contains_message(100)); diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index b4f91efb4f..e8f274c87d 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -54,12 +54,12 @@ impl Size for FromBridgedChainMessagesDeliveryProof = BTreeMap; +/// Rewards that need to be paid to relayers at the source chain. +pub type RelayersRewardsAtSource = BTreeMap; /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. -pub trait DeliveryConfirmationPayments { +pub trait DeliveryConfirmationPayments { /// Error type. type Error: Debug + Into<&'static str>; @@ -71,18 +71,18 @@ pub trait DeliveryConfirmationPayments { /// Returns number of actually rewarded relayers. fn pay_reward( lane_id: LaneId, - messages_relayers: VecDeque>, + messages_relayers: VecDeque>, confirmation_relayer: &AccountId, received_range: &RangeInclusive, ) -> MessageNonce; } -impl DeliveryConfirmationPayments for () { +impl DeliveryConfirmationPayments for () { type Error = &'static str; fn pay_reward( _lane_id: LaneId, - _messages_relayers: VecDeque>, + _messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, _received_range: &RangeInclusive, ) -> MessageNonce { @@ -140,12 +140,12 @@ impl MessagesBridge for NoopMessagesBridge { /// where outbound messages are forbidden. pub struct ForbidOutboundMessages; -impl DeliveryConfirmationPayments for ForbidOutboundMessages { +impl DeliveryConfirmationPayments for ForbidOutboundMessages { type Error = &'static str; fn pay_reward( _lane_id: LaneId, - _messages_relayers: VecDeque>, + _messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, _received_range: &RangeInclusive, ) -> MessageNonce { From 1a3efe764bf8f62c6912252bf16402d063849595 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 09:20:06 +0300 Subject: [PATCH 18/60] remove unneeded constratind --- modules/messages/src/call_ext.rs | 2 -- modules/messages/src/lib.rs | 4 ++-- modules/messages/src/proofs.rs | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/messages/src/call_ext.rs b/modules/messages/src/call_ext.rs index 2b6bcf9fe5..f24417ae1f 100644 --- a/modules/messages/src/call_ext.rs +++ b/modules/messages/src/call_ext.rs @@ -74,7 +74,6 @@ impl, I: 'static> CallHelper { /// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`. pub trait CallSubType, I: 'static>: IsSubType, T>> -where T: frame_system::Config> { /// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call. fn receive_messages_proof_info(&self) -> Option; @@ -101,7 +100,6 @@ impl< T: frame_system::Config + Config, I: 'static, > CallSubType for T::RuntimeCall -where T: frame_system::Config> { fn receive_messages_proof_info(&self) -> Option { if let Some(crate::Call::::receive_messages_proof { ref proof, .. }) = diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 53f92db7cc..8d3a347eff 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -127,7 +127,7 @@ pub mod pallet { type DeliveryPayments: DeliveryPayments; /// Handler for relayer payments that happen during message delivery confirmation /// transaction. - type DeliveryConfirmationPayments: DeliveryConfirmationPayments>, BalanceOf>>; + type DeliveryConfirmationPayments: DeliveryConfirmationPayments>>; /// Delivery confirmation callback. type OnMessagesDelivered: OnMessagesDelivered; @@ -153,7 +153,7 @@ pub mod pallet { } #[pallet::call] - impl, I: 'static> Pallet where T: frame_system::Config> { + impl, I: 'static> Pallet { /// Change `PalletOwner`. /// /// May only be called either by root, or by `PalletOwner`. diff --git a/modules/messages/src/proofs.rs b/modules/messages/src/proofs.rs index 3bdc1a0f8e..ca529ad692 100644 --- a/modules/messages/src/proofs.rs +++ b/modules/messages/src/proofs.rs @@ -25,12 +25,12 @@ use bp_messages::{ ChainWithMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; -use bp_runtime::{AccountIdOf, BalanceOf, HashOf, RangeInclusiveExt, VerifiedStorageProof}; +use bp_runtime::{BalanceOf, HashOf, RangeInclusiveExt, VerifiedStorageProof}; use sp_std::vec::Vec; /// 'Parsed' message delivery proof - inbound lane id and its state. pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = - (LaneId, InboundLaneData>, BalanceOf>>); + (LaneId, InboundLaneData<::AccountId, BalanceOf>>); /// Verify proof of Bridged -> This chain messages. /// From 9c134da66aef091f2c8f5ed0081e3eb8e9466844 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 10:46:12 +0300 Subject: [PATCH 19/60] set RewardAtSource to be u64 --- modules/messages/src/call_ext.rs | 4 +- modules/messages/src/inbound_lane.rs | 22 +++-- modules/messages/src/lanes_manager.rs | 11 ++- modules/messages/src/lib.rs | 6 +- modules/messages/src/outbound_lane.rs | 12 +-- modules/messages/src/proofs.rs | 10 +-- .../messages/src/tests/messages_generation.rs | 6 +- modules/messages/src/tests/mock.rs | 9 +-- modules/messages/src/tests/pallet_tests.rs | 2 +- modules/relayers/src/extension/mod.rs | 2 +- modules/relayers/src/lib.rs | 8 +- modules/relayers/src/payment_adapter.rs | 81 +++++-------------- primitives/messages/src/lib.rs | 54 +++++++------ primitives/messages/src/source_chain.rs | 16 ++-- primitives/relayers/src/lane_relayers.rs | 33 ++++---- primitives/relayers/src/lib.rs | 2 +- 16 files changed, 120 insertions(+), 158 deletions(-) diff --git a/modules/messages/src/call_ext.rs b/modules/messages/src/call_ext.rs index f24417ae1f..0052aee2cd 100644 --- a/modules/messages/src/call_ext.rs +++ b/modules/messages/src/call_ext.rs @@ -24,7 +24,7 @@ use bp_messages::{ LaneId, MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, }; -use bp_runtime::{AccountIdOf, BalanceOf}; +use bp_runtime::AccountIdOf; use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; use sp_runtime::transaction_validity::TransactionValidity; @@ -202,7 +202,7 @@ impl< /// Returns occupation state of unrewarded relayers vector. fn unrewarded_relayers_occupation, I: 'static>( - inbound_lane_data: &InboundLaneData>, BalanceOf>>, + inbound_lane_data: &InboundLaneData>>, ) -> UnrewardedRelayerOccupation { UnrewardedRelayerOccupation { free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX diff --git a/modules/messages/src/inbound_lane.rs b/modules/messages/src/inbound_lane.rs index 3f7d34ac22..53689286eb 100644 --- a/modules/messages/src/inbound_lane.rs +++ b/modules/messages/src/inbound_lane.rs @@ -23,18 +23,16 @@ use bp_messages::{ ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, LaneState, MessageKey, MessageNonce, OutboundLaneData, ReceivalResult, UnrewardedRelayer, }; -use bp_runtime::{AccountIdOf, BalanceOf}; +use bp_runtime::AccountIdOf; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::{Type, TypeInfo}; -use sp_runtime::{traits::{One, Saturating}, RuntimeDebug}; +use sp_runtime::{traits::One, RuntimeDebug}; use sp_std::prelude::PartialEq; /// Inbound lane storage. pub trait InboundLaneStorage { /// Id of relayer on source chain. type Relayer: Clone + PartialEq; - /// Balance of the source chain. - type Balance: Clone + One + PartialEq + Saturating; /// Lane id. fn id(&self) -> LaneId; @@ -43,9 +41,9 @@ pub trait InboundLaneStorage { /// Return maximal number of unconfirmed messages in inbound lane. fn max_unconfirmed_messages(&self) -> MessageNonce; /// Get lane data from the storage. - fn data(&self) -> InboundLaneData; + fn data(&self) -> InboundLaneData; /// Update lane data in the storage. - fn set_data(&mut self, data: InboundLaneData); + fn set_data(&mut self, data: InboundLaneData); /// Purge lane data from the storage. fn purge(self); } @@ -60,11 +58,11 @@ pub trait InboundLaneStorage { /// The encoding of this type matches encoding of the corresponding `MessageData`. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] pub struct StoredInboundLaneData, I: 'static>( - pub InboundLaneData>, BalanceOf>>, + pub InboundLaneData>>, ); impl, I: 'static> sp_std::ops::Deref for StoredInboundLaneData { - type Target = InboundLaneData>, BalanceOf>>; + type Target = InboundLaneData>>; fn deref(&self) -> &Self::Target { &self.0 @@ -84,7 +82,7 @@ impl, I: 'static> Default for StoredInboundLaneData { } impl, I: 'static> From> - for InboundLaneData>, BalanceOf>> + for InboundLaneData>> { fn from(data: StoredInboundLaneData) -> Self { data.0 @@ -92,7 +90,7 @@ impl, I: 'static> From> } impl, I: 'static> EncodeLike> - for InboundLaneData>, BalanceOf>> + for InboundLaneData>> { } @@ -100,13 +98,13 @@ impl, I: 'static> TypeInfo for StoredInboundLaneData { type Identity = Self; fn type_info() -> Type { - InboundLaneData::>, BalanceOf>>::type_info() + InboundLaneData::>>::type_info() } } impl, I: 'static> MaxEncodedLen for StoredInboundLaneData { fn max_encoded_len() -> usize { - InboundLaneData::>, BalanceOf>>::encoded_size_hint( + InboundLaneData::>>::encoded_size_hint( BridgedChainOf::::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize, ) .unwrap_or(usize::MAX) diff --git a/modules/messages/src/lanes_manager.rs b/modules/messages/src/lanes_manager.rs index 6a6bbcd696..a112b93d2d 100644 --- a/modules/messages/src/lanes_manager.rs +++ b/modules/messages/src/lanes_manager.rs @@ -24,7 +24,7 @@ use bp_messages::{ target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneId, LaneState, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; -use bp_runtime::{AccountIdOf, BalanceOf}; +use bp_runtime::AccountIdOf; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ensure, PalletError}; use scale_info::TypeInfo; @@ -137,7 +137,7 @@ impl, I: 'static> LanesManager { /// Runtime inbound lane storage. pub struct RuntimeInboundLaneStorage, I: 'static = ()> { pub(crate) lane_id: LaneId, - pub(crate) cached_data: InboundLaneData>, BalanceOf>>, + pub(crate) cached_data: InboundLaneData>>, pub(crate) _phantom: PhantomData, } @@ -194,7 +194,7 @@ impl, I: 'static> RuntimeInboundLaneStorage { let max_encoded_len = StoredInboundLaneData::::max_encoded_len(); let relayers_count = self.data().relayers.len(); let actual_encoded_len = - InboundLaneData::>, BalanceOf>>::encoded_size_hint(relayers_count) + InboundLaneData::>>::encoded_size_hint(relayers_count) .unwrap_or(usize::MAX); max_encoded_len.saturating_sub(actual_encoded_len) as _ } @@ -202,7 +202,6 @@ impl, I: 'static> RuntimeInboundLaneStorage { impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage { type Relayer = AccountIdOf>; - type Balance = BalanceOf>; fn id(&self) -> LaneId { self.lane_id @@ -216,11 +215,11 @@ impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage< BridgedChainOf::::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX } - fn data(&self) -> InboundLaneData>, BalanceOf>> { + fn data(&self) -> InboundLaneData>> { self.cached_data.clone() } - fn set_data(&mut self, data: InboundLaneData>, BalanceOf>>) { + fn set_data(&mut self, data: InboundLaneData>>) { self.cached_data = data.clone(); InboundLanes::::insert(self.lane_id, StoredInboundLaneData::(data)) } diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 8d3a347eff..560428190b 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -65,7 +65,7 @@ use bp_messages::{ OutboundMessageDetails, UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ - AccountIdOf, BalanceOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, + AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, Size, }; use codec::{Decode, Encode}; @@ -127,7 +127,7 @@ pub mod pallet { type DeliveryPayments: DeliveryPayments; /// Handler for relayer payments that happen during message delivery confirmation /// transaction. - type DeliveryConfirmationPayments: DeliveryConfirmationPayments>>; + type DeliveryConfirmationPayments: DeliveryConfirmationPayments; /// Delivery confirmation callback. type OnMessagesDelivered: OnMessagesDelivered; @@ -578,7 +578,7 @@ pub mod pallet { /// Return inbound lane data. pub fn inbound_lane_data( lane: LaneId, - ) -> Option>, BalanceOf>>> { + ) -> Option>>> { InboundLanes::::get(lane).map(|lane| lane.0) } } diff --git a/modules/messages/src/outbound_lane.rs b/modules/messages/src/outbound_lane.rs index e022c104da..6599a30a90 100644 --- a/modules/messages/src/outbound_lane.rs +++ b/modules/messages/src/outbound_lane.rs @@ -133,11 +133,11 @@ impl OutboundLane { } /// Confirm messages delivery. - pub fn confirm_delivery( + pub fn confirm_delivery( &mut self, max_allowed_messages: MessageNonce, latest_delivered_nonce: MessageNonce, - relayers: &VecDeque>, + relayers: &VecDeque>, ) -> Result>, ReceivalConfirmationError> { let mut data = self.storage.data(); let confirmed_messages = data.latest_received_nonce.saturating_add(1)..=latest_delivered_nonce; @@ -194,9 +194,9 @@ impl OutboundLane { /// /// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged /// chain has invalid runtime storage. -fn ensure_unrewarded_relayers_are_correct( +fn ensure_unrewarded_relayers_are_correct( latest_received_nonce: MessageNonce, - relayers: &VecDeque>, + relayers: &VecDeque>, ) -> Result<(), ReceivalConfirmationError> { let mut expected_entry_begin = relayers.front().map(|entry| entry.messages.begin); for entry in relayers { @@ -230,7 +230,7 @@ mod tests { fn unrewarded_relayers( nonces: RangeInclusive, - ) -> VecDeque> { + ) -> VecDeque> { vec![unrewarded_relayer(*nonces.start(), *nonces.end(), 0)] .into_iter() .collect() @@ -242,7 +242,7 @@ mod tests { fn assert_3_messages_confirmation_fails( latest_received_nonce: MessageNonce, - relayers: &VecDeque>, + relayers: &VecDeque>, ) -> Result>, ReceivalConfirmationError> { run_test(|| { let mut lane = active_outbound_lane::(test_lane_id()).unwrap(); diff --git a/modules/messages/src/proofs.rs b/modules/messages/src/proofs.rs index ca529ad692..f4a6923030 100644 --- a/modules/messages/src/proofs.rs +++ b/modules/messages/src/proofs.rs @@ -16,7 +16,7 @@ //! Tools for messages and delivery proof verification. -use crate::{BridgedChainOf, BridgedHeaderChainOf, Config, ThisChainOf}; +use crate::{BridgedChainOf, BridgedHeaderChainOf, Config}; use bp_header_chain::HeaderChain; use bp_messages::{ @@ -25,12 +25,12 @@ use bp_messages::{ ChainWithMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; -use bp_runtime::{BalanceOf, HashOf, RangeInclusiveExt, VerifiedStorageProof}; +use bp_runtime::{HashOf, RangeInclusiveExt, VerifiedStorageProof}; use sp_std::vec::Vec; /// 'Parsed' message delivery proof - inbound lane id and its state. -pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = - (LaneId, InboundLaneData<::AccountId, BalanceOf>>); +pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = + (LaneId, InboundLaneData<::AccountId>); /// Verify proof of Bridged -> This chain messages. /// @@ -97,7 +97,7 @@ pub fn verify_messages_proof, I: 'static>( /// Verify proof of This -> Bridged chain messages delivery. pub fn verify_messages_delivery_proof, I: 'static>( proof: FromBridgedChainMessagesDeliveryProof>>, -) -> Result, VerificationError> { +) -> Result, VerificationError> { let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof; let mut storage = T::BridgedHeaderChain::verify_storage_proof(bridged_header_hash, storage_proof) diff --git a/modules/messages/src/tests/messages_generation.rs b/modules/messages/src/tests/messages_generation.rs index d5bab23122..567d77625e 100644 --- a/modules/messages/src/tests/messages_generation.rs +++ b/modules/messages/src/tests/messages_generation.rs @@ -21,7 +21,7 @@ use bp_messages::{ MessagePayload, OutboundLaneData, }; use bp_runtime::{ - grow_storage_value, AccountIdOf, BalanceOf, Chain, HashOf, HasherOf, RangeInclusiveExt, + grow_storage_value, AccountIdOf, Chain, HashOf, HasherOf, RangeInclusiveExt, UnverifiedStorageProof, UnverifiedStorageProofParams, }; use codec::Encode; @@ -97,7 +97,7 @@ where /// Prepare storage proof that proves given messages delivery. pub fn prepare_message_delivery_storage_proof( lane: LaneId, - inbound_lane_data: InboundLaneData, BalanceOf>, + inbound_lane_data: InboundLaneData>, proof_params: UnverifiedStorageProofParams, ) -> (HashOf, UnverifiedStorageProof) where @@ -209,7 +209,7 @@ where /// Returns state trie root and partial storage trie. fn do_prepare_message_delivery_storage_proof( lane: LaneId, - inbound_lane_data: InboundLaneData, BalanceOf>, + inbound_lane_data: InboundLaneData>, proof_params: UnverifiedStorageProofParams, ) -> (HashOf, UnverifiedStorageProof) where diff --git a/modules/messages/src/tests/mock.rs b/modules/messages/src/tests/mock.rs index 9cdf74614b..c5af14bcc5 100644 --- a/modules/messages/src/tests/mock.rs +++ b/modules/messages/src/tests/mock.rs @@ -73,7 +73,6 @@ pub struct TestPayload { pub type TestMessageFee = u64; pub type TestRelayer = u64; pub type TestDispatchLevelResult = (); -pub type TestBalance = Balance; pub struct ThisChain; @@ -354,12 +353,12 @@ impl TestDeliveryConfirmationPayments { } } -impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { +impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { type Error = &'static str; fn pay_reward( _lane_id: LaneId, - messages_relayers: VecDeque>, + messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, received_range: &RangeInclusive, ) -> MessageNonce { @@ -464,7 +463,7 @@ pub fn unrewarded_relayer( begin: MessageNonce, end: MessageNonce, relayer: TestRelayer, -) -> UnrewardedRelayer { +) -> UnrewardedRelayer { UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end, reward: 1 } } } @@ -548,7 +547,7 @@ pub fn prepare_messages_proof( /// `asset_noop` macro calls. pub fn prepare_messages_delivery_proof( lane: LaneId, - inbound_lane_data: InboundLaneData, + inbound_lane_data: InboundLaneData, ) -> FromBridgedChainMessagesDeliveryProof { // first - let's generate storage proof let (storage_root, storage_proof) = diff --git a/modules/messages/src/tests/pallet_tests.rs b/modules/messages/src/tests/pallet_tests.rs index f8f0b86032..c1da5bc4c8 100644 --- a/modules/messages/src/tests/pallet_tests.rs +++ b/modules/messages/src/tests/pallet_tests.rs @@ -1033,7 +1033,7 @@ generate_owned_bridge_module_tests!( #[test] fn inbound_storage_extra_proof_size_bytes_works() { - fn relayer_entry() -> UnrewardedRelayer { + fn relayer_entry() -> UnrewardedRelayer { UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100, reward: 0 } } } diff --git a/modules/relayers/src/extension/mod.rs b/modules/relayers/src/extension/mod.rs index cb569bb762..1cf00b4747 100644 --- a/modules/relayers/src/extension/mod.rs +++ b/modules/relayers/src/extension/mod.rs @@ -1805,7 +1805,7 @@ mod tests { last_confirmed_nonce: 0, relayers: vec![UnrewardedRelayer { relayer: relayer_account_at_bridged_chain(), - messages: DeliveredMessages { begin: 1, end: best_delivered_message }, + messages: DeliveredMessages { begin: 1, end: best_delivered_message, reward: 0 }, }] .into(), ..Default::default() diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 12c6cc499d..210d67e338 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -30,7 +30,7 @@ use bp_messages::LaneId; use bp_relayers::{ LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, - RewardsAccountParams, StakeAndSlash, + RewardAtSource, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; @@ -74,7 +74,7 @@ pub mod pallet { pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Type of relayer reward. + /// Type of relayer reward, that is paid at this chain. type Reward: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen; /// Pay rewards scheme. type PaymentProcedure: PaymentProcedure; @@ -265,7 +265,7 @@ pub mod pallet { pub fn register_at_lane( origin: OriginFor, lane: LaneId, - expected_reward: T::Reward, + expected_reward: RewardAtSource, ) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -732,7 +732,7 @@ pub mod pallet { _, Identity, LaneId, - LaneRelayersSet, T::Reward, T::MaxRelayersPerLane>, + LaneRelayersSet, T::MaxRelayersPerLane>, OptionQuery, >; } diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 3e853fa438..2e06ce450d 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -19,18 +19,20 @@ use crate::{Config, LaneRelayers, Pallet}; use bp_messages::{ - source_chain::{DeliveryConfirmationPayments, RelayersRewards}, + source_chain::{DeliveryConfirmationPayments, RelayersRewardsAtSource}, target_chain::DeliveryPayments, LaneId, MessageNonce, }; -use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_relayers::{RewardAtSource, RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; use frame_support::{sp_runtime::SaturatedConversion, weights::Weight}; -use sp_arithmetic::traits::{Saturating, Zero}; +use sp_arithmetic::traits::UniqueSaturatedFrom; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; /// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism /// for the messages pallet. +/// +/// This adapter uses 1:1 mapping of `RewardAtSource` to `T::Reward`. pub struct DeliveryConfirmationPaymentsAdapter(PhantomData<(T, MI)>); impl DeliveryConfirmationPayments @@ -38,6 +40,7 @@ impl DeliveryConfirmationPayments where T: Config + pallet_bridge_messages::Config, MI: 'static, + T::Reward: UniqueSaturatedFrom, { type Error = &'static str; @@ -48,7 +51,7 @@ where received_range: &RangeInclusive, ) -> MessageNonce { let relayers_rewards = - bp_messages::calc_relayers_rewards::(messages_relayers, received_range); + bp_messages::calc_relayers_rewards_at_source::(messages_relayers, received_range); let rewarded_relayers = relayers_rewards.len(); register_relayers_rewards::( @@ -117,7 +120,7 @@ where return Err(()) } - if !lane_relayers.next_set_try_push(relayer, *relayer_in_active_set.reward()) { + if !lane_relayers.next_set_try_push(relayer, relayer_in_active_set.reward()) { return Err(()) } } @@ -130,26 +133,11 @@ where // Update rewards to given relayers, optionally rewarding confirmation relayer. fn register_relayers_rewards( _confirmation_relayer: &T::AccountId, - relayers_rewards: RelayersRewards, + relayers_rewards: RelayersRewardsAtSource, reward_account: RewardsAccountParams, -) { - for (relayer, messages) in relayers_rewards { - // TODO: THIS IS WRONG - `LaneRelayers` is for relayers, which are delivering messages from BRIDGED to THIS - // chain and `register_relayers_rewards` is called for relayers that are delivering messages from THIS - // to BRIDGED chain - let message_delivery_reward = LaneRelayers::::get(reward_account.lane_id()) - .and_then(|lane_relayers| { - lane_relayers - .active_relayers() - .iter() - .filter(|r| *r.relayer() == relayer) - .map(|r| *r.reward()) - .next() - }) - .unwrap_or_else(Zero::zero); // TODO: should be default reward, specified by bridge owner OR pallet-wide-default - let relayer_reward = - T::Reward::saturated_from(messages).saturating_mul(message_delivery_reward); - +) where T::Reward: UniqueSaturatedFrom { + for (relayer, relayer_reward) in relayers_rewards { + let relayer_reward = T::Reward::saturated_from(relayer_reward); Pallet::::register_relayer_reward(reward_account, &relayer, relayer_reward); } } @@ -158,74 +146,41 @@ fn register_relayers_rewards( mod tests { use super::*; use crate::{mock::*, RelayerRewards}; - use bp_relayers::LaneRelayersSet; const RELAYER_1: ThisChainAccountId = 1; const RELAYER_2: ThisChainAccountId = 2; const RELAYER_3: ThisChainAccountId = 3; - fn relayers_rewards() -> RelayersRewards { + fn relayers_rewards() -> RelayersRewardsAtSource { vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect() } - fn set_expected_rewards(relayers: &[ThisChainAccountId], reward: ThisChainBalance) { - LaneRelayers::::mutate( - test_reward_account_param().lane_id(), - |maybe_lane_relayers| { - let mut lane_relayers = - maybe_lane_relayers.take().unwrap_or_else(|| LaneRelayersSet::empty(0)); - for relayer in relayers { - assert!(lane_relayers.next_set_try_push(*relayer, reward)); - } - lane_relayers.activate_next_set(1); - *maybe_lane_relayers = Some(lane_relayers) - }, - ); - } - #[test] fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { run_test(|| { - set_expected_rewards(&[RELAYER_1, RELAYER_2, RELAYER_3], 50); register_relayers_rewards::( &RELAYER_2, relayers_rewards(), test_reward_account_param(), ); - assert_eq!( - RelayerRewards::::get(RELAYER_1, test_reward_account_param()), - Some(100) - ); - assert_eq!( - RelayerRewards::::get(RELAYER_2, test_reward_account_param()), - Some(150) - ); + assert_eq!(RelayerRewards::::get(RELAYER_1, test_reward_account_param()), Some(2)); + assert_eq!(RelayerRewards::::get(RELAYER_2, test_reward_account_param()), Some(3)); }); } #[test] fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() { run_test(|| { - set_expected_rewards(&[RELAYER_1, RELAYER_2, RELAYER_3], 50); register_relayers_rewards::( &RELAYER_3, relayers_rewards(), test_reward_account_param(), ); - assert_eq!( - RelayerRewards::::get(RELAYER_1, test_reward_account_param()), - Some(100) - ); - assert_eq!( - RelayerRewards::::get(RELAYER_2, test_reward_account_param()), - Some(150) - ); - assert_eq!( - RelayerRewards::::get(RELAYER_3, test_reward_account_param()), - None - ); + assert_eq!(RelayerRewards::::get(RELAYER_1, test_reward_account_param()), Some(2)); + assert_eq!(RelayerRewards::::get(RELAYER_2, test_reward_account_param()), Some(3)); + assert_eq!(RelayerRewards::::get(RELAYER_3, test_reward_account_param()), None); }); } } diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index 92866eca60..ac9397945c 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -33,7 +33,7 @@ use serde::{Deserialize, Serialize}; use source_chain::RelayersRewardsAtSource; use sp_core::{RuntimeDebug, TypeId, H256}; use sp_io::hashing::blake2_256; -use sp_runtime::{traits::AtLeast32BitUnsigned, SaturatedConversion}; +use sp_runtime::SaturatedConversion; use sp_std::{collections::{btree_map::Entry, vec_deque::VecDeque}, ops::RangeInclusive, prelude::*}; pub use call_info::{ @@ -308,7 +308,7 @@ pub struct Message { /// Inbound lane data. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct InboundLaneData { +pub struct InboundLaneData { /// Inbound lane state. /// /// If state is `Closed`, then all attempts to deliver messages to this end will fail. @@ -331,7 +331,7 @@ pub struct InboundLaneData { /// When a relayer sends a single message, both of MessageNonces are the same. /// When relayer sends messages in a batch, the first arg is the lowest nonce, second arg the /// highest nonce. Multiple dispatches from the same relayer are allowed. - pub relayers: VecDeque>, + pub relayers: VecDeque>, /// Nonce of the last message that /// a) has been delivered to the target (this) chain and @@ -344,7 +344,7 @@ pub struct InboundLaneData { pub last_confirmed_nonce: MessageNonce, } -impl Default for InboundLaneData { +impl Default for InboundLaneData { fn default() -> Self { InboundLaneData { state: LaneState::Closed, @@ -354,7 +354,7 @@ impl Default for InboundLaneData InboundLaneData { +impl InboundLaneData { /// Returns default inbound lane data with opened state. pub fn opened() -> Self { InboundLaneData { state: LaneState::Opened, ..Default::default() } @@ -367,10 +367,9 @@ impl InboundLaneData { pub fn encoded_size_hint(relayers_entries: usize) -> Option where RelayerId: MaxEncodedLen, - RewardAtSource: MaxEncodedLen, { relayers_entries - .checked_mul(UnrewardedRelayer::::max_encoded_len())? + .checked_mul(UnrewardedRelayer::::max_encoded_len())? .checked_add(Compact::(relayers_entries as u32).encoded_size())? .checked_add(LaneState::max_encoded_len())? .checked_add(MessageNonce::max_encoded_len()) @@ -383,7 +382,6 @@ impl InboundLaneData { pub fn encoded_size_hint_u32(relayers_entries: usize) -> u32 where RelayerId: MaxEncodedLen, - RewardAtSource: MaxEncodedLen, { Self::encoded_size_hint(relayers_entries) .and_then(|x| u32::try_from(x).ok()) @@ -441,11 +439,11 @@ pub struct InboundMessageDetails { /// This struct represents a continuous range of messages that have been delivered by the same /// relayer and whose confirmations are still pending. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct UnrewardedRelayer { +pub struct UnrewardedRelayer { /// Identifier of the relayer at the source (sending) chain. pub relayer: RelayerId, /// Messages range, delivered by this relayer. - pub messages: DeliveredMessages, + pub messages: DeliveredMessages, } /// Received messages with their dispatch result. @@ -488,20 +486,31 @@ pub enum ReceivalResult { TooManyUnconfirmedMessages, } +/// Type of reward that needs to be paid at the source chain for delivering messages to the +/// target chain. It does not necessary mapped 1:1 onto source chain tokens. The +/// `DeliveryConfirmationPayments` implementation decides how to map it and what actual +/// reward the relayer would receive. +/// +/// Why our code is not generic over this type? That's mostly because we are using single +/// `pallet-bridge-relayers` instance to register rewards for all bridges. Those bridges +/// may be connected to chains, using different `Balance` types. Why not to use encoded version? +/// Because we need to order relayers by the reward they get for delivering a single message. +pub type RewardAtSource = u64; + /// Delivered messages with their dispatch result. #[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct DeliveredMessages { +pub struct DeliveredMessages { /// Nonce of the first message that has been delivered (inclusive). pub begin: MessageNonce, /// Nonce of the last message that has been delivered (inclusive). pub end: MessageNonce, /// Reward that needs to be paid at the source chain (during confirmation transaction) /// for every delivered message in the `begin..=end` range. If reward has been paid at the - /// target chain, it may be zero. + /// target chain, it may be zero. pub reward: RewardAtSource, } -impl DeliveredMessages { +impl DeliveredMessages { /// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given /// dispatch result. pub fn new(nonce: MessageNonce, reward: RewardAtSource) -> Self { @@ -543,13 +552,13 @@ pub struct UnrewardedRelayersState { impl UnrewardedRelayersState { /// Verify that the relayers state corresponds with the `InboundLaneData`. - pub fn is_valid(&self, lane_data: &InboundLaneData) -> bool { + pub fn is_valid(&self, lane_data: &InboundLaneData) -> bool { self == &lane_data.into() } } -impl From<&InboundLaneData> for UnrewardedRelayersState { - fn from(lane: &InboundLaneData) -> UnrewardedRelayersState { +impl From<&InboundLaneData> for UnrewardedRelayersState { + fn from(lane: &InboundLaneData) -> UnrewardedRelayersState { UnrewardedRelayersState { unrewarded_relayer_entries: lane.relayers.len() as _, messages_in_oldest_entry: lane @@ -599,20 +608,19 @@ impl Default for OutboundLaneData { } } -/// Calculate the number of messages that the relayers have delivered. -pub fn calc_relayers_rewards_at_source( - messages_relayers: VecDeque>, +/// Calculate the total relayer reward that need to be paid at the source chain. +pub fn calc_relayers_rewards_at_source( + messages_relayers: VecDeque>, received_range: &RangeInclusive, -) -> RelayersRewardsAtSource +) -> RelayersRewardsAtSource where AccountId: sp_std::cmp::Ord, - RewardAtSource: AtLeast32BitUnsigned + Clone/*Bounded + Clone + Saturating + UniqueSaturatedFrom + Zero*/, { // remember to reward relayers that have delivered messages // this loop is bounded by `T::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` on the bridged chain - let mut relayers_rewards: RelayersRewardsAtSource = RelayersRewardsAtSource::new(); + let mut relayers_rewards: RelayersRewardsAtSource = RelayersRewardsAtSource::new(); for entry in messages_relayers { - if entry.messages.reward.is_zero() { + if entry.messages.reward == 0 { continue; } diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index e8f274c87d..8c57363e35 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the source chain. -use crate::{LaneId, MessageNonce, UnrewardedRelayer}; +use crate::{LaneId, MessageNonce, RewardAtSource, UnrewardedRelayer}; use bp_runtime::{Size, UnverifiedStorageProof}; use codec::{Decode, Encode}; @@ -55,11 +55,11 @@ impl Size for FromBridgedChainMessagesDeliveryProof = BTreeMap; +pub type RelayersRewardsAtSource = BTreeMap; /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. -pub trait DeliveryConfirmationPayments { +pub trait DeliveryConfirmationPayments { /// Error type. type Error: Debug + Into<&'static str>; @@ -71,18 +71,18 @@ pub trait DeliveryConfirmationPayments { /// Returns number of actually rewarded relayers. fn pay_reward( lane_id: LaneId, - messages_relayers: VecDeque>, + messages_relayers: VecDeque>, confirmation_relayer: &AccountId, received_range: &RangeInclusive, ) -> MessageNonce; } -impl DeliveryConfirmationPayments for () { +impl DeliveryConfirmationPayments for () { type Error = &'static str; fn pay_reward( _lane_id: LaneId, - _messages_relayers: VecDeque>, + _messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, _received_range: &RangeInclusive, ) -> MessageNonce { @@ -140,12 +140,12 @@ impl MessagesBridge for NoopMessagesBridge { /// where outbound messages are forbidden. pub struct ForbidOutboundMessages; -impl DeliveryConfirmationPayments for ForbidOutboundMessages { +impl DeliveryConfirmationPayments for ForbidOutboundMessages { type Error = &'static str; fn pay_reward( _lane_id: LaneId, - _messages_relayers: VecDeque>, + _messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, _received_range: &RangeInclusive, ) -> MessageNonce { diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index c69e19676e..2d71555ba5 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -16,6 +16,8 @@ //! Bridge lane relayers. +pub use bp_messages::RewardAtSource; + use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ @@ -23,24 +25,26 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; +/// We are using + /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct RelayerAndReward { +pub struct RelayerAndReward { /// A relayer account identifier. relayer: AccountId, /// A reward that is paid to relayer for delivering a single message. - reward: Reward, + reward: RewardAtSource, } -impl RelayerAndReward { +impl RelayerAndReward { /// Return relayer account identifier. pub fn relayer(&self) -> &AccountId { &self.relayer } /// Return expected relayer reward. - pub fn reward(&self) -> &Reward { - &self.reward + pub fn reward(&self) -> RewardAtSource { + self.reward } } @@ -63,7 +67,7 @@ impl RelayerAndReward { /// relayer in the [`Self::next_set`]. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxRelayersPerLane))] -pub struct LaneRelayersSet> { +pub struct LaneRelayersSet> { /// Number of block, where the active set has been enacted. enacted_at: BlockNumber, /// Number of block, where the active set may be replaced with the [`Self::next_set`]. @@ -76,20 +80,19 @@ pub struct LaneRelayersSet, MaxRelayersPerLane>, + active_set: BoundedVec, MaxRelayersPerLane>, /// Next set of lane relayers. /// /// It is a bounded priority queue. Relayers that are working for larger reward are replaced /// with relayers, that are working for smaller reward. - next_set: BoundedVec, MaxRelayersPerLane>, + next_set: BoundedVec, MaxRelayersPerLane>, } -impl - LaneRelayersSet +impl + LaneRelayersSet where AccountId: Clone + PartialOrd, BlockNumber: Copy + Zero, - Reward: Copy + Ord, MaxRelayersPerLane: Get, { /// Creates new empty relayers set, where next sets enacts at given block. @@ -108,19 +111,19 @@ where } /// Returns relayers in the active set. - pub fn active_relayers(&self) -> &[RelayerAndReward] { + pub fn active_relayers(&self) -> &[RelayerAndReward] { self.active_set.as_slice() } /// Returns relayers in the next set. - pub fn next_relayers(&self) -> &[RelayerAndReward] { + pub fn next_relayers(&self) -> &[RelayerAndReward] { self.next_set.as_slice() } /// Try insert relayer to the next set. /// /// Returns `true` if relayer has been added to the set and false otherwise. - pub fn next_set_try_push(&mut self, relayer: AccountId, reward: Reward) -> bool { + pub fn next_set_try_push(&mut self, relayer: AccountId, reward: RewardAtSource) -> bool { // first, remove existing entry for the same relayer from the set self.next_set_try_remove(&relayer); // now try to insert new entry into the queue @@ -153,7 +156,7 @@ where self.next_set_may_enact_at = new_next_set_may_enact_at; } - fn select_position_in_next_set(&self, reward: Reward) -> usize { + fn select_position_in_next_set(&self, reward: RewardAtSource) -> usize { // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it // may be used to push relayers our of the queue let mut initial_position = self diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index ad6b493750..cbddcabde9 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -23,7 +23,7 @@ pub use extension::{ BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig, RuntimeWithUtilityPallet, }; -pub use lane_relayers::{LaneRelayersSet, RelayerAndReward}; +pub use lane_relayers::{LaneRelayersSet, RelayerAndReward, RewardAtSource}; pub use registration::{Registration, StakeAndSlash}; use bp_messages::LaneId; From 5a86ed46890eb2011f4ec891de9031926ef64f85 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 13:08:55 +0300 Subject: [PATCH 20/60] removed fixed TODO --- modules/relayers/src/lib.rs | 2 -- modules/relayers/src/payment_adapter.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 210d67e338..3c5d71433c 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -22,8 +22,6 @@ // TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? -// TODO: remove relayer from `next_set` when he has not delivered any messages - #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 2e06ce450d..0c26b977dc 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -111,7 +111,7 @@ where return Err(()) } - // if relayer is not willoing to work on that lane anymore, we don't want to do + // if relayer is not willing to work on that lane anymore, we don't want to do // anything here let wants_to_work_on_lane = Pallet::::registered_relayer(&relayer) .map(|registration| registration.lanes().contains(&lane_id)) From 9b174cd654dc29de34649b892fc8482ca6eccd67 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 13:09:05 +0300 Subject: [PATCH 21/60] fmt --- modules/messages/src/inbound_lane.rs | 5 ++- modules/messages/src/lib.rs | 6 ++-- modules/messages/src/outbound_lane.rs | 7 ++-- modules/messages/src/tests/pallet_tests.rs | 5 ++- modules/relayers/src/extension/mod.rs | 6 +++- modules/relayers/src/lib.rs | 4 +-- modules/relayers/src/payment_adapter.rs | 37 ++++++++++++++++------ primitives/messages/src/lib.rs | 17 ++++++---- primitives/relayers/src/lane_relayers.rs | 2 +- 9 files changed, 62 insertions(+), 27 deletions(-) diff --git a/modules/messages/src/inbound_lane.rs b/modules/messages/src/inbound_lane.rs index 53689286eb..9261edb847 100644 --- a/modules/messages/src/inbound_lane.rs +++ b/modules/messages/src/inbound_lane.rs @@ -212,7 +212,10 @@ impl InboundLane { // now let's update inbound lane storage let relayer_reward_per_message = One::one(); // TODO: it must be returned by some callback!!! match data.relayers.back_mut() { - Some(entry) if entry.relayer == *relayer_at_bridged_chain && entry.messages.reward == relayer_reward_per_message => { + Some(entry) + if entry.relayer == *relayer_at_bridged_chain && + entry.messages.reward == relayer_reward_per_message => + { entry.messages.note_dispatched_message(); }, _ => { diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 560428190b..4748847838 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -60,9 +60,9 @@ use bp_messages::{ DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch, ProvedLaneMessages, ProvedMessages, }, - ChainWithMessages, InboundLaneData, InboundMessageDetails, LaneId, - MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, - OutboundMessageDetails, UnrewardedRelayersState, VerificationError, + ChainWithMessages, InboundLaneData, InboundMessageDetails, LaneId, MessageKey, MessageNonce, + MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, + UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, diff --git a/modules/messages/src/outbound_lane.rs b/modules/messages/src/outbound_lane.rs index 6599a30a90..d72b60fe25 100644 --- a/modules/messages/src/outbound_lane.rs +++ b/modules/messages/src/outbound_lane.rs @@ -19,8 +19,8 @@ use crate::{Config, LOG_TARGET}; use bp_messages::{ - ChainWithMessages, LaneId, LaneState, MessageNonce, MessagePayload, - OutboundLaneData, UnrewardedRelayer, VerificationError, + ChainWithMessages, LaneId, LaneState, MessageNonce, MessagePayload, OutboundLaneData, + UnrewardedRelayer, VerificationError, }; use bp_runtime::RangeInclusiveExt; use codec::{Decode, Encode}; @@ -140,7 +140,8 @@ impl OutboundLane { relayers: &VecDeque>, ) -> Result>, ReceivalConfirmationError> { let mut data = self.storage.data(); - let confirmed_messages = data.latest_received_nonce.saturating_add(1)..=latest_delivered_nonce; + let confirmed_messages = + data.latest_received_nonce.saturating_add(1)..=latest_delivered_nonce; if confirmed_messages.saturating_len() == 0 { return Ok(None) } diff --git a/modules/messages/src/tests/pallet_tests.rs b/modules/messages/src/tests/pallet_tests.rs index c1da5bc4c8..3fccb6a12e 100644 --- a/modules/messages/src/tests/pallet_tests.rs +++ b/modules/messages/src/tests/pallet_tests.rs @@ -1034,7 +1034,10 @@ generate_owned_bridge_module_tests!( #[test] fn inbound_storage_extra_proof_size_bytes_works() { fn relayer_entry() -> UnrewardedRelayer { - UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100, reward: 0 } } + UnrewardedRelayer { + relayer: 42u64, + messages: DeliveredMessages { begin: 0, end: 100, reward: 0 }, + } } fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { diff --git a/modules/relayers/src/extension/mod.rs b/modules/relayers/src/extension/mod.rs index 1cf00b4747..2ec5f68e83 100644 --- a/modules/relayers/src/extension/mod.rs +++ b/modules/relayers/src/extension/mod.rs @@ -1805,7 +1805,11 @@ mod tests { last_confirmed_nonce: 0, relayers: vec![UnrewardedRelayer { relayer: relayer_account_at_bridged_chain(), - messages: DeliveredMessages { begin: 1, end: best_delivered_message, reward: 0 }, + messages: DeliveredMessages { + begin: 1, + end: best_delivered_message, + reward: 0, + }, }] .into(), ..Default::default() diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 3c5d71433c..ad8067875f 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -27,8 +27,8 @@ use bp_messages::LaneId; use bp_relayers::{ - LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, - RewardAtSource, RewardsAccountParams, StakeAndSlash, + LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardAtSource, + RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 0c26b977dc..70b81d5c37 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -32,7 +32,7 @@ use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeIn /// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism /// for the messages pallet. /// -/// This adapter uses 1:1 mapping of `RewardAtSource` to `T::Reward`. +/// This adapter uses 1:1 mapping of `RewardAtSource` to `T::Reward`. pub struct DeliveryConfirmationPaymentsAdapter(PhantomData<(T, MI)>); impl DeliveryConfirmationPayments @@ -50,8 +50,10 @@ where confirmation_relayer: &T::AccountId, received_range: &RangeInclusive, ) -> MessageNonce { - let relayers_rewards = - bp_messages::calc_relayers_rewards_at_source::(messages_relayers, received_range); + let relayers_rewards = bp_messages::calc_relayers_rewards_at_source::( + messages_relayers, + received_range, + ); let rewarded_relayers = relayers_rewards.len(); register_relayers_rewards::( @@ -135,7 +137,9 @@ fn register_relayers_rewards( _confirmation_relayer: &T::AccountId, relayers_rewards: RelayersRewardsAtSource, reward_account: RewardsAccountParams, -) where T::Reward: UniqueSaturatedFrom { +) where + T::Reward: UniqueSaturatedFrom, +{ for (relayer, relayer_reward) in relayers_rewards { let relayer_reward = T::Reward::saturated_from(relayer_reward); Pallet::::register_relayer_reward(reward_account, &relayer, relayer_reward); @@ -164,8 +168,14 @@ mod tests { test_reward_account_param(), ); - assert_eq!(RelayerRewards::::get(RELAYER_1, test_reward_account_param()), Some(2)); - assert_eq!(RelayerRewards::::get(RELAYER_2, test_reward_account_param()), Some(3)); + assert_eq!( + RelayerRewards::::get(RELAYER_1, test_reward_account_param()), + Some(2) + ); + assert_eq!( + RelayerRewards::::get(RELAYER_2, test_reward_account_param()), + Some(3) + ); }); } @@ -178,9 +188,18 @@ mod tests { test_reward_account_param(), ); - assert_eq!(RelayerRewards::::get(RELAYER_1, test_reward_account_param()), Some(2)); - assert_eq!(RelayerRewards::::get(RELAYER_2, test_reward_account_param()), Some(3)); - assert_eq!(RelayerRewards::::get(RELAYER_3, test_reward_account_param()), None); + assert_eq!( + RelayerRewards::::get(RELAYER_1, test_reward_account_param()), + Some(2) + ); + assert_eq!( + RelayerRewards::::get(RELAYER_2, test_reward_account_param()), + Some(3) + ); + assert_eq!( + RelayerRewards::::get(RELAYER_3, test_reward_account_param()), + None + ); }); } } diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index ac9397945c..3fca08de74 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -34,7 +34,11 @@ use source_chain::RelayersRewardsAtSource; use sp_core::{RuntimeDebug, TypeId, H256}; use sp_io::hashing::blake2_256; use sp_runtime::SaturatedConversion; -use sp_std::{collections::{btree_map::Entry, vec_deque::VecDeque}, ops::RangeInclusive, prelude::*}; +use sp_std::{ + collections::{btree_map::Entry, vec_deque::VecDeque}, + ops::RangeInclusive, + prelude::*, +}; pub use call_info::{ BaseMessagesProofInfo, BridgeMessagesCall, BridgeMessagesCallOf, MessagesCallInfo, @@ -621,25 +625,26 @@ where let mut relayers_rewards: RelayersRewardsAtSource = RelayersRewardsAtSource::new(); for entry in messages_relayers { if entry.messages.reward == 0 { - continue; + continue } - + let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end()); let new_confirmations = nonce_begin..=nonce_end; let new_confirmations_count = new_confirmations.saturating_len(); if new_confirmations_count == 0 { - continue; + continue } - let new_reward = RewardAtSource::saturated_from(new_confirmations_count).saturating_mul(entry.messages.reward); + let new_reward = RewardAtSource::saturated_from(new_confirmations_count) + .saturating_mul(entry.messages.reward); match relayers_rewards.entry(entry.relayer) { Entry::Occupied(mut e) => { e.insert(e.get().clone().saturating_add(new_reward)); }, Entry::Vacant(e) => { e.insert(new_reward); - } + }, } } relayers_rewards diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 2d71555ba5..c907a50cb4 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -25,7 +25,7 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; -/// We are using +/// We are using /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] From 579b90b1831ee1655f66b86803d82e40c25f0015 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 14:53:24 +0300 Subject: [PATCH 22/60] MaxRewardPerMessage + fixed TODO --- modules/messages/src/tests/mock.rs | 6 +- modules/relayers/src/lib.rs | 20 +++---- modules/relayers/src/mock.rs | 2 +- modules/relayers/src/payment_adapter.rs | 74 ++++++++++++------------- primitives/messages/src/lib.rs | 14 +++-- primitives/messages/src/source_chain.rs | 4 +- 6 files changed, 57 insertions(+), 63 deletions(-) diff --git a/modules/messages/src/tests/mock.rs b/modules/messages/src/tests/mock.rs index c5af14bcc5..154fa1742f 100644 --- a/modules/messages/src/tests/mock.rs +++ b/modules/messages/src/tests/mock.rs @@ -362,7 +362,11 @@ impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayment _confirmation_relayer: &AccountId, received_range: &RangeInclusive, ) -> MessageNonce { - let relayers_rewards = calc_relayers_rewards_at_source(messages_relayers, received_range); + let relayers_rewards = calc_relayers_rewards_at_source::( + messages_relayers, + received_range, + |messages, reward_per_message| messages * reward_per_message, + ); let rewarded_relayers = relayers_rewards.len(); for (relayer, reward) in &relayers_rewards { let key = (b":relayer-reward:", relayer, reward).encode(); diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index ad8067875f..47e70abb2b 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -17,11 +17,6 @@ //! Runtime module that is used to store relayer rewards and to coordinate relations //! between relayers. -// TODO: allow bridge owners to add "protected" relayers that have a guaranteed slot in the lane -// relayers - -// TODO: or else ONLY allow bridge owners to add registered relayers (through XCM calls)??? - #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] @@ -257,7 +252,13 @@ pub mod pallet { /// Relayer that registers itself at given message lane gets a priority boost for his /// message delivery transactions, **verified** at his slots (consecutive range of blocks). /// - /// Every additional lane registration requires + /// Every lane registration requires additional stake. Relayer registration is considered + /// active while it is registered at least at one lane. + /// + /// Relayer may request large reward here (using `expected_reward`), but in the end, the + /// reward amount is computed at the bridged (source chain). In the case if + /// [`DeliveryConfirmationPaymentsAdapter`] is used to register rewards, the maximal reward + /// per message is limited by the `MaxRewardPerMessage` parameter. #[pallet::call_index(3)] #[pallet::weight(Weight::zero())] // TODO pub fn register_at_lane( @@ -267,13 +268,6 @@ pub mod pallet { ) -> DispatchResult { let relayer = ensure_signed(origin)?; - // TODO: we probably need a way for bridge owners (sibling/parent chains) to at least - // set a maximal possible reward for their lane over XCM? + maybe change relayers set? - // This way they could implement their own incentivization mechanisms by setting reward - // to zero and changing relayers set on their own. - - // TODO: check that `expected_reward` makes sense - RegisteredRelayers::::try_mutate( &relayer.clone(), move |maybe_registration| -> DispatchResult { diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index fe09558e71..b3a4b1071d 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -290,7 +290,7 @@ impl pallet_bridge_messages::Config for TestRuntime { type DeliveryPayments = (); type DeliveryConfirmationPayments = - pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter; + pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter>; type OnMessagesDelivered = (); type MessageDispatch = DummyMessageDispatch; diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 70b81d5c37..a3e8840e46 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -25,39 +25,55 @@ use bp_messages::{ }; use bp_relayers::{RewardAtSource, RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; -use frame_support::{sp_runtime::SaturatedConversion, weights::Weight}; +use frame_support::weights::Weight; use sp_arithmetic::traits::UniqueSaturatedFrom; +use sp_runtime::{ + traits::{Get, UniqueSaturatedInto}, + Saturating, +}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; /// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism /// for the messages pallet. /// -/// This adapter uses 1:1 mapping of `RewardAtSource` to `T::Reward`. -pub struct DeliveryConfirmationPaymentsAdapter(PhantomData<(T, MI)>); - -impl DeliveryConfirmationPayments - for DeliveryConfirmationPaymentsAdapter +/// This adapter uses 1:1 mapping of `RewardAtSource` to `T::Reward`. The reward for delivering +/// a single message, will never be larger than the `MaxRewardPerMessage`. +pub struct DeliveryConfirmationPaymentsAdapter( + PhantomData<(T, MI, MaxRewardPerMessage)>, +); + +impl DeliveryConfirmationPayments + for DeliveryConfirmationPaymentsAdapter where T: Config + pallet_bridge_messages::Config, MI: 'static, - T::Reward: UniqueSaturatedFrom, + T::Reward: UniqueSaturatedFrom + UniqueSaturatedFrom, + MaxRewardPerMessage: Get, { type Error = &'static str; fn pay_reward( lane_id: LaneId, messages_relayers: VecDeque>, - confirmation_relayer: &T::AccountId, + _confirmation_relayer: &T::AccountId, received_range: &RangeInclusive, ) -> MessageNonce { - let relayers_rewards = bp_messages::calc_relayers_rewards_at_source::( - messages_relayers, - received_range, - ); + let relayers_rewards = + bp_messages::calc_relayers_rewards_at_source::( + messages_relayers, + received_range, + |messages, reward_per_message| { + let reward_per_message = sp_std::cmp::min( + MaxRewardPerMessage::get(), + reward_per_message.unique_saturated_into(), + ); + + T::Reward::unique_saturated_from(messages).saturating_mul(reward_per_message) + }, + ); let rewarded_relayers = relayers_rewards.len(); register_relayers_rewards::( - confirmation_relayer, relayers_rewards, RewardsAccountParams::new( lane_id, @@ -70,7 +86,8 @@ where } } -impl DeliveryPayments for DeliveryConfirmationPaymentsAdapter +impl DeliveryPayments + for DeliveryConfirmationPaymentsAdapter where T: Config + pallet_bridge_messages::Config, MI: 'static, @@ -134,14 +151,12 @@ where // Update rewards to given relayers, optionally rewarding confirmation relayer. fn register_relayers_rewards( - _confirmation_relayer: &T::AccountId, - relayers_rewards: RelayersRewardsAtSource, + relayers_rewards: RelayersRewardsAtSource, reward_account: RewardsAccountParams, ) where T::Reward: UniqueSaturatedFrom, { for (relayer, relayer_reward) in relayers_rewards { - let relayer_reward = T::Reward::saturated_from(relayer_reward); Pallet::::register_relayer_reward(reward_account, &relayer, relayer_reward); } } @@ -155,35 +170,14 @@ mod tests { const RELAYER_2: ThisChainAccountId = 2; const RELAYER_3: ThisChainAccountId = 3; - fn relayers_rewards() -> RelayersRewardsAtSource { + fn relayers_rewards() -> RelayersRewardsAtSource { vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect() } #[test] - fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { - run_test(|| { - register_relayers_rewards::( - &RELAYER_2, - relayers_rewards(), - test_reward_account_param(), - ); - - assert_eq!( - RelayerRewards::::get(RELAYER_1, test_reward_account_param()), - Some(2) - ); - assert_eq!( - RelayerRewards::::get(RELAYER_2, test_reward_account_param()), - Some(3) - ); - }); - } - - #[test] - fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() { + fn register_relayers_rewards_works() { run_test(|| { register_relayers_rewards::( - &RELAYER_3, relayers_rewards(), test_reward_account_param(), ); diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index 3fca08de74..7ea4aacd4c 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -33,7 +33,7 @@ use serde::{Deserialize, Serialize}; use source_chain::RelayersRewardsAtSource; use sp_core::{RuntimeDebug, TypeId, H256}; use sp_io::hashing::blake2_256; -use sp_runtime::SaturatedConversion; +use sp_runtime::traits::Saturating; use sp_std::{ collections::{btree_map::Entry, vec_deque::VecDeque}, ops::RangeInclusive, @@ -613,16 +613,19 @@ impl Default for OutboundLaneData { } /// Calculate the total relayer reward that need to be paid at the source chain. -pub fn calc_relayers_rewards_at_source( +pub fn calc_relayers_rewards_at_source( messages_relayers: VecDeque>, received_range: &RangeInclusive, -) -> RelayersRewardsAtSource + compute_reward: impl Fn(MessageNonce, RewardAtSource) -> Reward, +) -> RelayersRewardsAtSource where AccountId: sp_std::cmp::Ord, + Reward: Copy + Saturating, { // remember to reward relayers that have delivered messages // this loop is bounded by `T::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` on the bridged chain - let mut relayers_rewards: RelayersRewardsAtSource = RelayersRewardsAtSource::new(); + let mut relayers_rewards: RelayersRewardsAtSource = + RelayersRewardsAtSource::new(); for entry in messages_relayers { if entry.messages.reward == 0 { continue @@ -636,8 +639,7 @@ where continue } - let new_reward = RewardAtSource::saturated_from(new_confirmations_count) - .saturating_mul(entry.messages.reward); + let new_reward = compute_reward(new_confirmations_count, entry.messages.reward); match relayers_rewards.entry(entry.relayer) { Entry::Occupied(mut e) => { e.insert(e.get().clone().saturating_add(new_reward)); diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs index 8c57363e35..2a9488ec8c 100644 --- a/primitives/messages/src/source_chain.rs +++ b/primitives/messages/src/source_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the source chain. -use crate::{LaneId, MessageNonce, RewardAtSource, UnrewardedRelayer}; +use crate::{LaneId, MessageNonce, UnrewardedRelayer}; use bp_runtime::{Size, UnverifiedStorageProof}; use codec::{Decode, Encode}; @@ -55,7 +55,7 @@ impl Size for FromBridgedChainMessagesDeliveryProof = BTreeMap; +pub type RelayersRewardsAtSource = BTreeMap; /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. From 60b55af04568b4d6560fa378fe1aee23c98567ea Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 16:38:23 +0300 Subject: [PATCH 23/60] fixed couple of TODOs + added comments + added new TODO --- modules/relayers/src/lib.rs | 46 +++++++++++++++--------- primitives/relayers/src/lane_relayers.rs | 2 -- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 47e70abb2b..d21d288abb 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -75,13 +75,24 @@ pub mod pallet { /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; - /// TODO + /// Maximal number of lanes, where relayer may be registered. + /// + /// This is an artificial limit that only exists to make PoV size predictable. #[pallet::constant] type MaxLanesPerRelayer: Get; /// Maximal number of relayers that can register themselves on a single lane. + /// + /// Lowering this value leads to additional concurrency between relayers, potentially + /// making messages cheaper. So it shall not be too large. #[pallet::constant] type MaxRelayersPerLane: Get; + /// Length of initial relayer elections in chain blocks. + /// + /// When the first relayer registers itself on the lane, we give some time to other relayers + /// to register as well. Otherwise there'll be only one relayer in active set, for the whole + /// (first) epoch. + type InitialElectionLength: Get>; /// Length of slots in chain blocks. /// /// Registered relayer may explicitly register himself at some lane to get priority boost @@ -94,6 +105,18 @@ pub mod pallet { /// Shall not be too low to have an effect, because there's some (at least one block) lag /// between moments when priority is computed and when active slot changes. type SlotLength: Get>; + /// Length of epoch in chain blocks. + /// + /// Epoch is a set of slots, where a fixed set of lane relayers receives a priority boost + /// for their message delivery transactions. Epochs transition is a manual action, performed + /// by the `advance_lane_epoch` call. + /// + /// This value should allow every relayer from the active set to have at least one slot. So + /// it shall be not less than the `Self::MaxRelayersPerLane::get() * + /// Self::SlotLength::get()`. Normally, it should allow more than one slot for each relayer + /// (given max relayers in the set). + type EpochLength: Get>; + /// Priority boost that the registered relayer gets for every additional message in the /// message delivery transaction. type PriorityBoostPerMessage: Get; @@ -277,15 +300,6 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; - // cannot add another lane registration if "base" registration is inactive - ensure!( - registration.is_active( - SystemPallet::::block_number(), - Self::required_registration_lease(), - ), - Error::::RegistrationIsInactive, - ); - // cannot add another lane registration if relayer has already max allowed // lane registrations ensure!( @@ -301,7 +315,6 @@ pub mod pallet { let mut lane_relayers = match maybe_lane_relayers.take() { Some(lane_relayers) => lane_relayers, None => { - // TODO: give some time for initial elections // TODO: what if all relayers that have registered for the next set // then call `deregister_at_lane` before `next_set` activates? // This could be used by malicious relayers - they could fill @@ -311,8 +324,7 @@ pub mod pallet { // full queue. LaneRelayersSet::empty( SystemPallet::::block_number() - .saturating_add(One::one()) - .saturating_add(4u32.into()), // TODO + .saturating_add(T::InitialElectionLength::get()), ) }, }; @@ -403,10 +415,7 @@ pub mod pallet { /// be paid. We suggest the first relayer from the `next_set` to submit this transaction. #[pallet::call_index(5)] #[pallet::weight(Weight::zero())] // TODO - pub fn enact_next_relayers_set_at_lane( - origin: OriginFor, - lane: LaneId, - ) -> DispatchResult { + pub fn advance_lane_epoch(origin: OriginFor, lane: LaneId) -> DispatchResult { let _ = ensure_signed(origin)?; // remove relayer from the `next_set` of lane relayers. So relayer is still @@ -442,6 +451,9 @@ pub mod pallet { /// it'll return false if registered stake is lower than required or if remaining lease /// is less than `RequiredRegistrationLease`. pub fn is_registration_active(relayer: &T::AccountId) -> bool { + // TODO: physically change `valid_till` to `current_block + RequiredRegistrationLease` + // after last lane registration has been removed. Otherwise relayers can get basic boost + // near `valid_till` match Self::registered_relayer(relayer) { Some(registration) => registration.is_active( SystemPallet::::block_number(), diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index c907a50cb4..5c6cfc5472 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -25,8 +25,6 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; -/// We are using - /// A relayer and the reward that it wants to receive for delivering a single message. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct RelayerAndReward { From 72d5bbe77d8485343af8c02df64c2c4d025faaef Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 16:48:47 +0300 Subject: [PATCH 24/60] more TODO fixes --- modules/relayers/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index d21d288abb..4dc4856642 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -375,7 +375,8 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; - // remove relayer from the `next_set` of lane relayers. So relayer is still + // remove relayer from the `next_set` of lane relayers. Relayer still remains + // in the active set until current epoch ends LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { let mut lane_relayers = match lane_relayers_ref.take() { Some(lane_relayers) => lane_relayers, @@ -432,7 +433,8 @@ pub mod pallet { Error::::TooEarlyToActivateNextRelayersSet, ); - let new_next_set_may_enact_at = current_block_number.saturating_add(4u32.into()); // TODO + let new_next_set_may_enact_at = + current_block_number.saturating_add(T::EpochLength::get()); lane_relayers.activate_next_set(new_next_set_may_enact_at); *lane_relayers_ref = Some(lane_relayers); From e2251644fb3c1f9fbc3d3a77a1bc38033660e776 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 10 Oct 2023 16:59:30 +0300 Subject: [PATCH 25/60] fixed another TODO --- modules/relayers/src/lib.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 4dc4856642..02cee1c213 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -375,6 +375,9 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; + // remove relayer from the next set. It still may be in the active set + ensure!(registration.deregister_at_lane(lane), Error::::NotRegisteredAtLane); + // remove relayer from the `next_set` of lane relayers. Relayer still remains // in the active set until current epoch ends LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { @@ -383,19 +386,31 @@ pub mod pallet { None => fail!(Error::::NotRegisteredAtLane), }; - ensure!( - lane_relayers.next_set_try_remove(&relayer), - Error::::NotRegisteredAtLane, - ); + // remove relayer from the next set if it is there + lane_relayers.next_set_try_remove(&relayer); + + // make sure that the `valid_till` covers current epoch if relayer is in the + // active lane relayers set + let is_in_active_set = lane_relayers + .active_relayers() + .iter() + .filter(|r| *r.relayer() == relayer) + .next() + .is_some(); + if is_in_active_set { + registration.set_valid_till(sp_std::cmp::max( + lane_relayers.valid_till(), + lane_relayers.next_set_may_enact_at().saturating_add( + T::StakeAndSlash::RequiredRegistrationLease::get(), + ), + )); + } *lane_relayers_ref = Some(lane_relayers); Ok::<_, Error>(()) })?; - // ensure that the relayer has lane registration - registration.deregister_at_lane(lane); - *maybe_registration = Some(registration); Ok(()) @@ -453,9 +468,6 @@ pub mod pallet { /// it'll return false if registered stake is lower than required or if remaining lease /// is less than `RequiredRegistrationLease`. pub fn is_registration_active(relayer: &T::AccountId) -> bool { - // TODO: physically change `valid_till` to `current_block + RequiredRegistrationLease` - // after last lane registration has been removed. Otherwise relayers can get basic boost - // near `valid_till` match Self::registered_relayer(relayer) { Some(registration) => registration.is_active( SystemPallet::::block_number(), From 081523c762c6ff613fe454c81976892f56e07c78 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 11 Oct 2023 12:21:33 +0300 Subject: [PATCH 26/60] make next relayers set larger than the actual set --- bin/millau/runtime/src/lib.rs | 2 +- bin/rialto-parachain/runtime/src/lib.rs | 2 +- bin/rialto/runtime/src/lib.rs | 2 +- modules/relayers/src/lib.rs | 27 ++++++++++++++++++------ modules/relayers/src/mock.rs | 5 ++++- primitives/relayers/src/lane_relayers.rs | 26 ++++++++++++++++------- primitives/relayers/src/registration.rs | 4 +++- 7 files changed, 48 insertions(+), 20 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index 57267ab9b8..3a1ad78abe 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -399,7 +399,7 @@ impl pallet_bridge_relayers::Config for Runtime { ConstU64<1_000>, ConstU64<8>, >; - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = ConstU32<16>; type SlotLength = ConstU64<16>; type PriorityBoostPerMessage = PriorityBoostPerMessage; type PriorityBoostForLaneRelayer = ConstU64<0>; diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 9558c932fa..f97397bd7a 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -541,7 +541,7 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = ConstU32<16>; type SlotLength = ConstU32<16>; type PriorityBoostPerMessage = ConstU64<0>; type PriorityBoostForLaneRelayer = ConstU64<0>; diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index ffd0235d0f..aa4a5c33ed 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -393,7 +393,7 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = ConstU32<16>; type SlotLength = ConstU32<16>; type PriorityBoostPerMessage = ConstU64<0>; type PriorityBoostForLaneRelayer = ConstU64<0>; diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 02cee1c213..176aed5587 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -30,7 +30,7 @@ use frame_support::fail; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_runtime::{ - traits::{CheckedSub, One}, + traits::CheckedSub, Saturating, }; use sp_std::marker::PhantomData; @@ -80,12 +80,25 @@ pub mod pallet { /// This is an artificial limit that only exists to make PoV size predictable. #[pallet::constant] type MaxLanesPerRelayer: Get; - /// Maximal number of relayers that can register themselves on a single lane. + /// Maximal number of relayers that can reside in the active lane relayers set on a single lane. /// /// Lowering this value leads to additional concurrency between relayers, potentially /// making messages cheaper. So it shall not be too large. #[pallet::constant] - type MaxRelayersPerLane: Get; + type MaxActiveRelayersPerLane: Get; + /// Maximal number of relayers that can reside in the next lane relayers set on a single lane. + /// + /// Relayers set is a bounded priority queue, where relayers with lower expected reward are + /// prioritized over greedier relayers. At the end of epoch, we select top `MaxActiveRelayersPerLane` + /// relayers from the next set and move them to the next set. To alleviate possible spam attacks, + /// where relayers are registering at lane with zero reward (pushing out actual relayers with + /// larger expected reward) and then deregistering themselves right before epoch end, we make + /// the next relayers set larger than the active set. It would make it more expensive for attackers + /// to fill the whole next set. + /// + /// This value must be larger than or equal to the [`Self::MaxActiveRelayersPerLane`]. + #[pallet::constant] + type MaxNextRelayersPerLane: Get; /// Length of initial relayer elections in chain blocks. /// @@ -112,7 +125,7 @@ pub mod pallet { /// by the `advance_lane_epoch` call. /// /// This value should allow every relayer from the active set to have at least one slot. So - /// it shall be not less than the `Self::MaxRelayersPerLane::get() * + /// it shall be not less than the `Self::MaxActiveRelayersPerLane::get() * /// Self::SlotLength::get()`. Normally, it should allow more than one slot for each relayer /// (given max relayers in the set). type EpochLength: Get>; @@ -399,9 +412,9 @@ pub mod pallet { .is_some(); if is_in_active_set { registration.set_valid_till(sp_std::cmp::max( - lane_relayers.valid_till(), + registration.valid_till_ignore_lanes(), lane_relayers.next_set_may_enact_at().saturating_add( - T::StakeAndSlash::RequiredRegistrationLease::get(), + Self::required_registration_lease(), ), )); } @@ -750,7 +763,7 @@ pub mod pallet { _, Identity, LaneId, - LaneRelayersSet, T::MaxRelayersPerLane>, + LaneRelayersSet, T::MaxActiveRelayersPerLane, T::MaxNextRelayersPerLane>, OptionQuery, >; } diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index b3a4b1071d..b4807b9231 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -305,8 +305,11 @@ impl pallet_bridge_relayers::Config for TestRuntime { type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; type MaxLanesPerRelayer = MaxLanesPerRelayer; - type MaxRelayersPerLane = ConstU32<4>; + type MaxActiveRelayersPerLane = ConstU32<4>; + type MaxNextRelayersPerLane = ConstU32<1_024>; + type InitialElectionLength = ConstU32<4>; type SlotLength = ConstU32<16>; + type EpochLength = ConstU32<1_024>; type PriorityBoostPerMessage = ConstU64<1>; type PriorityBoostForLaneRelayer = PriorityBoostForLaneRelayer; type WeightInfo = (); diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 5c6cfc5472..ed1e567da3 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -64,8 +64,8 @@ impl RelayerAndReward { /// messages. Relayer, which agress to get a lower reward will likely to replace a "more greedy" /// relayer in the [`Self::next_set`]. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[scale_info(skip_type_params(MaxRelayersPerLane))] -pub struct LaneRelayersSet> { +#[scale_info(skip_type_params(MaxActiveRelayersPerLane, MaxNextRelayersPerLane))] +pub struct LaneRelayersSet, MaxNextRelayersPerLane: Get> { /// Number of block, where the active set has been enacted. enacted_at: BlockNumber, /// Number of block, where the active set may be replaced with the [`Self::next_set`]. @@ -78,20 +78,21 @@ pub struct LaneRelayersSet> /// It is a circular queue. Every relayer in the queue is assigned the slot (fixed number /// of blocks), starting from [`Self::enacted_at`]. Once the slot of last relayer ends, /// next slot will be assigned to the first relayer and so on. - active_set: BoundedVec, MaxRelayersPerLane>, + active_set: BoundedVec, MaxActiveRelayersPerLane>, /// Next set of lane relayers. /// /// It is a bounded priority queue. Relayers that are working for larger reward are replaced /// with relayers, that are working for smaller reward. - next_set: BoundedVec, MaxRelayersPerLane>, + next_set: BoundedVec, MaxNextRelayersPerLane>, } -impl - LaneRelayersSet +impl + LaneRelayersSet where AccountId: Clone + PartialOrd, BlockNumber: Copy + Zero, - MaxRelayersPerLane: Get, + MaxActiveRelayersPerLane: Get, + MaxNextRelayersPerLane: Get, { /// Creates new empty relayers set, where next sets enacts at given block. pub fn empty(next_set_may_enact_at: BlockNumber) -> Self { @@ -146,7 +147,16 @@ where /// /// The [`Self::active_set`] is replaced with the [`Self::next_set`]. pub fn activate_next_set(&mut self, new_next_set_may_enact_at: BlockNumber) { - sp_std::mem::swap(&mut self.active_set, &mut self.next_set); + // move relayers from the next set to the active set + self.active_set.clear(); + let relayers_in_active_set = sp_std::cmp::min(MaxActiveRelayersPerLane::get(), self.next_set.len() as u32); + for _ in 0..relayers_in_active_set { + // we know that the next set has at least `relayers_in_active_set` + // => so calling `remove(0)` is safe + // we know that the active set is empty and we select at most `MaxActiveRelayersPerLane` relayers + // => ignoring `try_push` result is safe + let _ = self.active_set.try_push(self.next_set.remove(0)); + } // we clear next set here. Relayers from the active set will be readded here if // they deliver at least one message in epoch and their reward will be concurrent. // Or else, they'll need to reregister manually. diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 81ec5c2b60..4167d909de 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -188,8 +188,10 @@ impl< } /// Remove lane registration. - pub fn deregister_at_lane(&mut self, lane: LaneId) { + pub fn deregister_at_lane(&mut self, lane: LaneId) -> bool { + let old_lanes_len = self.lanes.len(); self.lanes.retain(|l| *l != lane); + old_lanes_len != self.lanes.len() } } From e0e16cd0fa70c0088d3fe59b1c4ada0ef130628f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 11 Oct 2023 12:21:40 +0300 Subject: [PATCH 27/60] fmt --- modules/relayers/src/lib.rs | 36 +++++++++++++----------- primitives/relayers/src/lane_relayers.rs | 14 ++++++--- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 176aed5587..feeb48e27a 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -29,10 +29,7 @@ use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; -use sp_runtime::{ - traits::CheckedSub, - Saturating, -}; +use sp_runtime::{traits::CheckedSub, Saturating}; use sp_std::marker::PhantomData; pub use pallet::*; @@ -80,21 +77,23 @@ pub mod pallet { /// This is an artificial limit that only exists to make PoV size predictable. #[pallet::constant] type MaxLanesPerRelayer: Get; - /// Maximal number of relayers that can reside in the active lane relayers set on a single lane. + /// Maximal number of relayers that can reside in the active lane relayers set on a single + /// lane. /// /// Lowering this value leads to additional concurrency between relayers, potentially /// making messages cheaper. So it shall not be too large. #[pallet::constant] type MaxActiveRelayersPerLane: Get; - /// Maximal number of relayers that can reside in the next lane relayers set on a single lane. + /// Maximal number of relayers that can reside in the next lane relayers set on a single + /// lane. /// /// Relayers set is a bounded priority queue, where relayers with lower expected reward are - /// prioritized over greedier relayers. At the end of epoch, we select top `MaxActiveRelayersPerLane` - /// relayers from the next set and move them to the next set. To alleviate possible spam attacks, - /// where relayers are registering at lane with zero reward (pushing out actual relayers with - /// larger expected reward) and then deregistering themselves right before epoch end, we make - /// the next relayers set larger than the active set. It would make it more expensive for attackers - /// to fill the whole next set. + /// prioritized over greedier relayers. At the end of epoch, we select top + /// `MaxActiveRelayersPerLane` relayers from the next set and move them to the next set. To + /// alleviate possible spam attacks, where relayers are registering at lane with zero reward + /// (pushing out actual relayers with larger expected reward) and then deregistering + /// themselves right before epoch end, we make the next relayers set larger than the active + /// set. It would make it more expensive for attackers to fill the whole next set. /// /// This value must be larger than or equal to the [`Self::MaxActiveRelayersPerLane`]. #[pallet::constant] @@ -413,9 +412,9 @@ pub mod pallet { if is_in_active_set { registration.set_valid_till(sp_std::cmp::max( registration.valid_till_ignore_lanes(), - lane_relayers.next_set_may_enact_at().saturating_add( - Self::required_registration_lease(), - ), + lane_relayers + .next_set_may_enact_at() + .saturating_add(Self::required_registration_lease()), )); } @@ -763,7 +762,12 @@ pub mod pallet { _, Identity, LaneId, - LaneRelayersSet, T::MaxActiveRelayersPerLane, T::MaxNextRelayersPerLane>, + LaneRelayersSet< + T::AccountId, + BlockNumberFor, + T::MaxActiveRelayersPerLane, + T::MaxNextRelayersPerLane, + >, OptionQuery, >; } diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index ed1e567da3..0a3c8da88a 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -65,7 +65,12 @@ impl RelayerAndReward { /// relayer in the [`Self::next_set`]. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxActiveRelayersPerLane, MaxNextRelayersPerLane))] -pub struct LaneRelayersSet, MaxNextRelayersPerLane: Get> { +pub struct LaneRelayersSet< + AccountId, + BlockNumber, + MaxActiveRelayersPerLane: Get, + MaxNextRelayersPerLane: Get, +> { /// Number of block, where the active set has been enacted. enacted_at: BlockNumber, /// Number of block, where the active set may be replaced with the [`Self::next_set`]. @@ -149,12 +154,13 @@ where pub fn activate_next_set(&mut self, new_next_set_may_enact_at: BlockNumber) { // move relayers from the next set to the active set self.active_set.clear(); - let relayers_in_active_set = sp_std::cmp::min(MaxActiveRelayersPerLane::get(), self.next_set.len() as u32); + let relayers_in_active_set = + sp_std::cmp::min(MaxActiveRelayersPerLane::get(), self.next_set.len() as u32); for _ in 0..relayers_in_active_set { // we know that the next set has at least `relayers_in_active_set` // => so calling `remove(0)` is safe - // we know that the active set is empty and we select at most `MaxActiveRelayersPerLane` relayers - // => ignoring `try_push` result is safe + // we know that the active set is empty and we select at most `MaxActiveRelayersPerLane` + // relayers => ignoring `try_push` result is safe let _ = self.active_set.try_push(self.next_set.remove(0)); } // we clear next set here. Relayers from the active set will be readded here if From 730e36852a485d5a15a3b836e4d732f75abaf145 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 11 Oct 2023 13:36:12 +0300 Subject: [PATCH 28/60] fix compilation --- bin/millau/runtime/src/lib.rs | 5 +++++ bin/rialto-parachain/runtime/src/lib.rs | 4 ++++ bin/rialto/runtime/src/lib.rs | 4 ++++ modules/relayers/src/extension/priority.rs | 17 ++++++++++------- modules/relayers/src/lib.rs | 19 ++----------------- primitives/messages/src/lib.rs | 6 +++--- primitives/relayers/src/lane_relayers.rs | 17 ++++++++++++----- 7 files changed, 40 insertions(+), 32 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index cae0c51945..8573311fd4 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -397,10 +397,15 @@ impl pallet_bridge_relayers::Config for Runtime { Balances, RelayerStakeReserveId, ConstU64<1_000>, + ConstU64<1_000>, ConstU64<8>, >; + type MaxLanesPerRelayer = ConstU32<16>; type MaxActiveRelayersPerLane = ConstU32<16>; + type MaxNextRelayersPerLane = ConstU32<1_024>; + type InitialElectionLength = ConstU64<16>; type SlotLength = ConstU64<16>; + type EpochLength = ConstU64<1_024>; type PriorityBoostPerMessage = PriorityBoostPerMessage; type PriorityBoostForActiveLaneRelayer = ConstU64<0>; type WeightInfo = (); diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index e60dbb1b6d..5d1ff4c97d 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -541,8 +541,12 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); + type MaxLanesPerRelayer = ConstU32<16>; type MaxActiveRelayersPerLane = ConstU32<16>; + type MaxNextRelayersPerLane = ConstU32<1_024>; + type InitialElectionLength = ConstU32<16>; type SlotLength = ConstU32<16>; + type EpochLength = ConstU32<1_024>; type PriorityBoostPerMessage = ConstU64<0>; type PriorityBoostForActiveLaneRelayer = ConstU64<0>; type WeightInfo = (); diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index ab8e8b3bae..d6afb2d4ae 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -393,8 +393,12 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); + type MaxLanesPerRelayer = ConstU32<16>; type MaxActiveRelayersPerLane = ConstU32<16>; + type MaxNextRelayersPerLane = ConstU32<1_024>; + type InitialElectionLength = ConstU32<16>; type SlotLength = ConstU32<16>; + type EpochLength = ConstU32<1_024>; type PriorityBoostPerMessage = ConstU64<0>; type PriorityBoostForActiveLaneRelayer = ConstU64<0>; type WeightInfo = (); diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 23fe642fd2..b5ed5c486d 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -268,19 +268,22 @@ mod integrity_tests { mod tests { use super::*; use crate::{mock::*, LaneRelayers}; + use bp_relayers::LaneRelayersSet; #[test] fn compute_per_lane_priority_boost_works() { run_test(|| { // insert 3 relayers to the queue let lane_id = LaneId::new(1, 2); - let relayer1 = 1_000; - let relayer2 = 2_000; - let relayer3 = 3_000; - LaneRelayers::::insert( - lane_id, - sp_runtime::BoundedVec::try_from(vec![relayer1, relayer2, relayer3]).unwrap(), - ); + let relayer1 = 100; + let relayer2 = 200; + let relayer3 = 300; + let mut relayers_set = LaneRelayersSet::empty(0); + assert!(relayers_set.next_set_try_push(relayer1, 0)); + assert!(relayers_set.next_set_try_push(relayer2, 0)); + assert!(relayers_set.next_set_try_push(relayer3, 0)); + relayers_set.activate_next_set(0); + LaneRelayers::::insert(lane_id, relayers_set); // at blocks 1..=SlotLength relayer1 gets the boost System::set_block_number(0); diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 1ffffde9fa..bbfd7a2685 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -141,9 +141,9 @@ pub mod pallet { /// transaction will get the following additional priority boost: /// /// ```nocompile - /// T::PriorityBoostForLaneRelayer::get() + T::PriorityBoostPerMessage::get() * (msgs - 1) + /// T::PriorityBoostForActiveLaneRelayer::get() + T::PriorityBoostPerMessage::get() * (msgs - 1) /// ``` - type PriorityBoostForLaneRelayer: Get; + type PriorityBoostForActiveLaneRelayer: Get; /// Pallet call weights. type WeightInfo: WeightInfoExt; @@ -770,21 +770,6 @@ pub mod pallet { >, OptionQuery, >; - - /// A set of relayers that have explicitly registered themselves at a given lane. - /// - /// Every relayer inside this set receives additional priority boost when it submits - /// message delivers messages at given lane. The boost only happens inside the slot, - /// assigned to relayer. - #[pallet::storage] - #[pallet::getter(fn lane_relayers)] - pub type LaneRelayers = StorageMap< - _, - Identity, - LaneId, - BoundedVec, - ValueQuery, - >; } #[cfg(test)] diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index 7ea4aacd4c..d8c0066e90 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -683,7 +683,7 @@ mod tests { #[test] fn lane_is_closed_by_default() { - assert_eq!(InboundLaneData::<(), ()>::default().state, LaneState::Closed); + assert_eq!(InboundLaneData::<()>::default().state, LaneState::Closed); assert_eq!(OutboundLaneData::default().state, LaneState::Closed); } @@ -716,13 +716,13 @@ mod tests { (13u8, 128u8), ]; for (relayer_entries, messages_count) in test_cases { - let expected_size = InboundLaneData::::encoded_size_hint(relayer_entries as _); + let expected_size = InboundLaneData::::encoded_size_hint(relayer_entries as _); let actual_size = InboundLaneData { state: LaneState::Opened, relayers: (1u8..=relayer_entries) .map(|i| UnrewardedRelayer { relayer: i, - messages: DeliveredMessages::new(i as _, 0u16), + messages: DeliveredMessages::new(i as _, 0u64), }) .collect(), last_confirmed_nonce: messages_count as _, diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 0a3c8da88a..0b53fe3ee2 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -194,8 +194,14 @@ mod tests { use super::*; use sp_runtime::traits::ConstU32; - const MAX_LANE_RELAYERS: u32 = 4; - type TestLaneRelayersSet = LaneRelayersSet>; + const MAX_ACTIVE_LANE_RELAYERS: u32 = 2; + const MAX_NEXT_LANE_RELAYERS: u32 = 4; + type TestLaneRelayersSet = LaneRelayersSet< + u64, + u64, + ConstU32, + ConstU32, + >; #[test] fn next_set_try_push_works() { @@ -206,9 +212,10 @@ mod tests { next_set: vec![].try_into().unwrap(), }; - // first `MAX_LANE_RELAYERS` are simply filling the set - for i in 0..MAX_LANE_RELAYERS { - assert!(relayers.next_set_try_push(i, (MAX_LANE_RELAYERS - i) * 10)); + // first `MAX_NEXT_LANE_RELAYERS` are simply filling the set + let max_next_lane_relayers: u64 = MAX_NEXT_LANE_RELAYERS as _; + for i in 0..max_next_lane_relayers { + assert!(relayers.next_set_try_push(i, (max_next_lane_relayers - i) * 10)); } assert_eq!( relayers.next_set.as_slice(), From f926504b29751e036c0e376f314d245c6515fefc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 Oct 2023 10:27:15 +0300 Subject: [PATCH 29/60] added TODO --- modules/relayers/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index bbfd7a2685..f605db15a1 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -496,6 +496,8 @@ pub mod pallet { relayer: &T::AccountId, slash_destination: RewardsAccountParams, ) { + // TODO: also remove from all lanes? + let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, None => { From 043908f63b642178af478bd05cbc1a48cd007178 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 Oct 2023 11:53:57 +0300 Subject: [PATCH 30/60] tests + more TODOs --- modules/relayers/src/lib.rs | 186 ++++++++++++++++++++--- modules/relayers/src/mock.rs | 23 ++- primitives/relayers/src/lane_relayers.rs | 5 + primitives/relayers/src/registration.rs | 2 +- 4 files changed, 190 insertions(+), 26 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index f605db15a1..e576d7d937 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -313,32 +313,20 @@ pub mod pallet { }; // cannot add another lane registration if relayer has already max allowed - // lane registrations + // lane registrations OR if it is already registered at that lane ensure!( registration.register_at_lane(lane), Error::::FailedToRegisterAtLane ); - // TODO: ideally we shall use the candle auction for relayers selection (similar - // to parachain slot auctions) rather than a fixed interval - // let's try to claim a slot in the next set LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { let mut lane_relayers = match maybe_lane_relayers.take() { Some(lane_relayers) => lane_relayers, - None => { - // TODO: what if all relayers that have registered for the next set - // then call `deregister_at_lane` before `next_set` activates? - // This could be used by malicious relayers - they could fill - // the whole `next_set` and then clear it right before it is - // enacted. Think we shall allow more entries in the - // `mnext_set` so that it'll be harder for the attacker to fill the - // full queue. - LaneRelayersSet::empty( - SystemPallet::::block_number() - .saturating_add(T::InitialElectionLength::get()), - ) - }, + None => LaneRelayersSet::empty( + SystemPallet::::block_number() + .saturating_add(T::InitialElectionLength::get()), + ), }; ensure!( @@ -358,10 +346,6 @@ pub mod pallet { registration.required_stake(Self::base_stake(), Self::stake_per_lane()), )?); - // cannot add duplicate lane registration - // ensure!(!registration.lanes.contains(&lane), - // Error::::DuplicateLaneRegistration); - *maybe_registration = Some(registration); Ok(()) @@ -753,6 +737,11 @@ pub mod pallet { OptionQuery, >; + // TODO: split active set and `LaneRelayers`! Active set is read at every delivery transaction + // and other fields we need only in pallet calls. + + // TODO: make it ValueQuery? After it is created, it is never removed. But it is not default + /// A set of relayers that have explicitly registered themselves at a given lane. /// /// Every relayer inside this set receives additional priority boost when it submits @@ -781,7 +770,7 @@ mod tests { use crate::Event::RewardPaid; use bp_messages::LaneId; - use bp_relayers::RewardsAccountOwner; + use bp_relayers::{RelayerAndReward, RewardsAccountOwner}; use frame_support::{ assert_noop, assert_ok, traits::fungible::{Inspect, Mutate}, @@ -803,6 +792,16 @@ mod tests { registration } + fn registration_with_lane( + valid_till: ThisChainBlockNumber, + stake: ThisChainBalance, + lane: LaneId, + ) -> Registration { + let mut registration = registration(valid_till, stake); + assert!(registration.register_at_lane(lane)); + registration + } + #[test] fn root_cant_claim_anything() { run_test(|| { @@ -1226,4 +1225,147 @@ mod tests { assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); }); } + + #[test] + fn register_at_lane_fails_for_unregistered_relayer() { + run_test(|| { + assert_noop!( + Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + ), + Error::::NotRegistered, + ); + }); + } + + #[test] + fn register_at_lane_fails_if_relayer_is_already_registered_at_lane() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration_with_lane(151, Stake::get(), test_lane_id()), + ); + + assert_noop!( + Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + ), + Error::::FailedToRegisterAtLane, + ); + }); + } + + #[test] + fn register_at_lane_fails_if_relayer_has_max_lane_registrations() { + run_test(|| { + let mut registration = registration(151, Stake::get()); + for i in 0..MaxLanesPerRelayer::get() { + assert!(registration.register_at_lane(LaneId::new(42, i))); + } + + RegisteredRelayers::::insert(REGISTER_RELAYER, registration); + + assert_noop!( + Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + LaneId::new(77, 77), + 0 + ), + Error::::FailedToRegisterAtLane, + ); + }); + } + + #[test] + fn register_at_lane_fails_if_relayer_requests_too_large_reward_to_claim_the_slot() { + run_test(|| { + let mut lane_relayers = LaneRelayersSet::empty(100); + for i in 1..=MAX_NEXT_RELAYERS_PER_LANE as u64 { + assert!(lane_relayers.next_set_try_push(REGISTER_RELAYER + i, 0)); + } + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + LaneRelayers::::insert(test_lane_id(), lane_relayers); + + assert_noop!( + Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 1 + ), + Error::::TooLargeRewardToOccupyAnEntry, + ); + }); + } + + #[test] + fn register_at_lane_fails_if_relayer_does_not_have_required_balance() { + run_test(|| { + RegisteredRelayers::::insert( + FAILING_RELAYER, + registration(151, Stake::get()), + ); + + assert_noop!( + Pallet::::register_at_lane( + RuntimeOrigin::signed(FAILING_RELAYER), + test_lane_id(), + 0 + ), + Error::::FailedToReserve, + ); + }); + } + + #[test] + fn register_at_lane_works() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + RegisteredRelayers::::insert( + REGISTER_RELAYER_2, + registration(151, Stake::get()), + ); + + // when first relayer registers, we allow other relayers to register in next + // `InitialElectionLength` blocks + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 1 + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); + assert_eq!(lane_relayers.active_relayers(), &[]); + assert_eq!( + lane_relayers.next_relayers(), + &[RelayerAndReward::new(REGISTER_RELAYER, 1)] + ); + + // next relayer registers, it occupies the correct slot in the set + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER_2), + test_lane_id(), + 0 + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); + assert_eq!(lane_relayers.active_relayers(), &[]); + assert_eq!( + lane_relayers.next_relayers(), + &[ + RelayerAndReward::new(REGISTER_RELAYER_2, 0), + RelayerAndReward::new(REGISTER_RELAYER, 1) + ] + ); + }); + } } diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index a81601803a..26bd00538b 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -79,6 +79,9 @@ pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg"; /// Maximal extrinsic size at the `BridgedChain`. pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; +/// Maximal number of relayers in the next set. +pub const MAX_NEXT_RELAYERS_PER_LANE: u32 = 16; + /// Underlying chain of `ThisChain`. pub struct ThisUnderlyingChain; @@ -193,6 +196,7 @@ parameter_types! { pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); + pub InitialElectionLength: u32 = 4; pub SlotLength: u32 = 16; pub MaxLanesPerRelayer: u32 = 4; pub PriorityBoostForActiveLaneRelayer: TransactionPriority = 4; @@ -306,8 +310,8 @@ impl pallet_bridge_relayers::Config for TestRuntime { type StakeAndSlash = TestStakeAndSlash; type MaxLanesPerRelayer = MaxLanesPerRelayer; type MaxActiveRelayersPerLane = ConstU32<4>; - type MaxNextRelayersPerLane = ConstU32<1_024>; - type InitialElectionLength = ConstU32<4>; + type MaxNextRelayersPerLane = ConstU32; + type InitialElectionLength = InitialElectionLength; type SlotLength = ConstU32<16>; type EpochLength = ConstU32<1_024>; type PriorityBoostPerMessage = ConstU64<1>; @@ -338,6 +342,8 @@ pub const FAILING_RELAYER: ThisChainAccountId = 2; /// Relayer that is able to register. pub const REGISTER_RELAYER: ThisChainAccountId = 42; +/// Another relayer that is able to register. +pub const REGISTER_RELAYER_2: ThisChainAccountId = 43; /// Payment procedure that rejects payments to the `FAILING_RELAYER`. pub struct TestPaymentProcedure; @@ -392,9 +398,18 @@ impl MessageDispatch for DummyMessageDispatch { } } +/// Lane identifier used in tests. +pub fn test_lane_id() -> LaneId { + LaneId::new(1, 2) +} + /// Reward account params that we are using in tests. pub fn test_reward_account_param() -> RewardsAccountParams { - RewardsAccountParams::new(LaneId::new(1, 2), *b"test", RewardsAccountOwner::ThisChain) + RewardsAccountParams::new( + test_lane_id(), + TEST_BRIDGED_CHAIN_ID, + RewardsAccountOwner::BridgedChain, + ) } /// Return test externalities to use in tests. @@ -408,6 +423,8 @@ pub fn run_test(test: impl FnOnce() -> T) -> T { new_test_ext().execute_with(|| { Balances::mint_into(®ISTER_RELAYER, ExistentialDeposit::get() + 10 * Stake::get()) .unwrap(); + Balances::mint_into(®ISTER_RELAYER_2, ExistentialDeposit::get() + 10 * Stake::get()) + .unwrap(); test() }) diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 0b53fe3ee2..bba76590b7 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -35,6 +35,11 @@ pub struct RelayerAndReward { } impl RelayerAndReward { + /// Create new instance. + pub fn new(relayer: AccountId, reward: RewardAtSource) -> Self { + RelayerAndReward { relayer, reward } + } + /// Return relayer account identifier. pub fn relayer(&self) -> &AccountId { &self.relayer diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 4167d909de..f023e7455f 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -183,7 +183,7 @@ impl< if !self.lanes.contains(&lane) { self.lanes.try_push(lane).is_ok() } else { - true + false } } From 2c425de9e9ee13aa66be40e5840d75127aecdc82 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 Oct 2023 14:53:30 +0300 Subject: [PATCH 31/60] more tests --- modules/relayers/src/lib.rs | 125 +++++++++++++++++------ primitives/relayers/src/lane_relayers.rs | 31 ++++++ primitives/relayers/src/registration.rs | 3 +- 3 files changed, 129 insertions(+), 30 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index e576d7d937..68ad326bd5 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -792,16 +792,6 @@ mod tests { registration } - fn registration_with_lane( - valid_till: ThisChainBlockNumber, - stake: ThisChainBalance, - lane: LaneId, - ) -> Registration { - let mut registration = registration(valid_till, stake); - assert!(registration.register_at_lane(lane)); - registration - } - #[test] fn root_cant_claim_anything() { run_test(|| { @@ -1240,25 +1230,6 @@ mod tests { }); } - #[test] - fn register_at_lane_fails_if_relayer_is_already_registered_at_lane() { - run_test(|| { - RegisteredRelayers::::insert( - REGISTER_RELAYER, - registration_with_lane(151, Stake::get(), test_lane_id()), - ); - - assert_noop!( - Pallet::::register_at_lane( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id(), - 0 - ), - Error::::FailedToRegisterAtLane, - ); - }); - } - #[test] fn register_at_lane_fails_if_relayer_has_max_lane_registrations() { run_test(|| { @@ -1368,4 +1339,100 @@ mod tests { ); }); } + + #[test] + fn register_at_lane_may_be_used_to_change_expected_reward() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + + // at first we want reward `1` + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 1 + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!( + lane_relayers.next_relayers(), + &[RelayerAndReward::new(REGISTER_RELAYER, 1)] + ); + + // but then we change our expected reward + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!( + lane_relayers.next_relayers(), + &[RelayerAndReward::new(REGISTER_RELAYER, 0)] + ); + }); + } + + #[test] + fn relayer_still_has_lane_registration_after_he_is_pushed_out_of_next_set() { + run_test(|| { + // leave one free entry in next set by relayers with bid = 10 + let mut lane_relayers = LaneRelayersSet::empty(100); + for i in 1..MAX_NEXT_RELAYERS_PER_LANE as u64 { + assert!(lane_relayers.next_set_try_push(REGISTER_RELAYER + 100 + i, 10)); + } + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + RegisteredRelayers::::insert( + REGISTER_RELAYER_2, + registration(151, Stake::get()), + ); + LaneRelayers::::insert(test_lane_id(), lane_relayers); + + // occupy last entry by `REGISTER_RELAYER` with bid = 15 + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 15 + ),); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); + assert_eq!( + lane_relayers.next_relayers().last(), + Some(&RelayerAndReward::new(REGISTER_RELAYER, 15)) + ); + + // then the `REGISTER_RELAYER_2` comes with better bid = 14 + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER_2), + test_lane_id(), + 14 + ),); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); + assert_eq!( + lane_relayers.next_relayers().last(), + Some(&RelayerAndReward::new(REGISTER_RELAYER_2, 14)) + ); + + // => `REGISTER_RELAYER` is pushed out of the next set, but it still has the lane in + // his base "registration" structure, so it can rejoin anytime by calling + // `register_at_lane` with updated reward + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 13 + ),); + + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); + assert_eq!( + lane_relayers.next_relayers().last(), + Some(&RelayerAndReward::new(REGISTER_RELAYER, 13)) + ); + }); + } } diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index bba76590b7..561a56fe4b 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -331,4 +331,35 @@ mod tests { ], ); } + + #[test] + fn next_set_try_push_works_edge_case_1() { + // all relayers have the same reward = 10 + let mut relayers: TestLaneRelayersSet = LaneRelayersSet { + enacted_at: 0, + next_set_may_enact_at: 100, + active_set: vec![].try_into().unwrap(), + next_set: (0..MAX_NEXT_LANE_RELAYERS - 1) + .map(|i| RelayerAndReward::new(i as u64, 10)) + .collect::>() + .try_into() + .unwrap(), + }; + + // then comes the next relayer with reward = 15 + assert!(relayers.next_set_try_push((MAX_NEXT_LANE_RELAYERS - 1) as u64, 15)); + assert_eq!(relayers.next_set.len(), MAX_NEXT_LANE_RELAYERS as usize); + assert_eq!( + relayers.next_set.last(), + Some(&RelayerAndReward::new((MAX_NEXT_LANE_RELAYERS - 1) as u64, 15)) + ); + + // then comes the next relayer with reward = 14 + assert!(relayers.next_set_try_push(MAX_NEXT_LANE_RELAYERS as u64, 14)); + assert_eq!(relayers.next_set.len(), MAX_NEXT_LANE_RELAYERS as usize); + assert_eq!( + relayers.next_set.last(), + Some(&RelayerAndReward::new(MAX_NEXT_LANE_RELAYERS as u64, 14)) + ); + } } diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index f023e7455f..8e1881d80f 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -183,7 +183,8 @@ impl< if !self.lanes.contains(&lane) { self.lanes.try_push(lane).is_ok() } else { - false + // still return true - we allow relayer to change his bid (expected reward) + true } } From 2c3fed89974aa74960d86e9a566f8733058d291f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 Oct 2023 14:55:34 +0300 Subject: [PATCH 32/60] more tests --- modules/relayers/src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 68ad326bd5..3858b1ec62 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -1125,6 +1125,28 @@ mod tests { }); } + #[test] + fn deregister_fails_if_relayer_has_lanes_registrations() { + run_test(|| { + assert_ok!(Pallet::::register( + RuntimeOrigin::signed(REGISTER_RELAYER), + 150 + )); + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0, + )); + + System::::set_block_number(151); + + assert_noop!( + Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), + Error::::RegistrationIsStillActive, + ); + }); + } + #[test] fn deregister_works() { run_test(|| { @@ -1159,6 +1181,29 @@ mod tests { }); } + #[test] + fn deregister_works_after_last_lane_registration_is_removed() { + run_test(|| { + assert_ok!(Pallet::::register( + RuntimeOrigin::signed(REGISTER_RELAYER), + 150 + )); + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0, + )); + assert_ok!(Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + )); + + System::::set_block_number(151); + + assert_ok!(Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER))); + }); + } + #[test] fn is_registration_active_is_false_for_unregistered_relayer() { run_test(|| { From 4ad58da680411699005f0aa1f286be6835e6ad7a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 Oct 2023 19:25:31 +0300 Subject: [PATCH 33/60] tests for deregister_at_lane --- modules/relayers/src/lib.rs | 151 +++++++++++++++++++++++++++++++++-- modules/relayers/src/mock.rs | 3 +- 2 files changed, 148 insertions(+), 6 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 3858b1ec62..8ef82d8e01 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -282,7 +282,7 @@ pub mod pallet { }) } - /// Register relayer intention to serve given messages lane. + /// Register relayer intention to deliver inbound messages at given messages lane. /// /// Relayer that registers itself at given message lane gets a priority boost for his /// message delivery transactions, **verified** at his slots (consecutive range of blocks). @@ -290,6 +290,14 @@ pub mod pallet { /// Every lane registration requires additional stake. Relayer registration is considered /// active while it is registered at least at one lane. /// + /// This call (if successful), puts relayer in the relayers set that will be active during + /// next epoch. So boost is not immediate - it will be activated after `advance_lane_epoch` + /// call. However, before that call, relayer may be pushed from the next set by relayers, + /// offering lower `expected_reward`. If that happens, relayer may either try to re-register + /// itself by repeating the `register_at_lane` call, offering lower reward. Or it may claim + /// his lane stake back, by updating his registration with `register` call or deregistering + /// at all using `deregister` call. + /// /// Relayer may request large reward here (using `expected_reward`), but in the end, the /// reward amount is computed at the bridged (source chain). In the case if /// [`DeliveryConfirmationPaymentsAdapter`] is used to register rewards, the maximal reward @@ -355,7 +363,16 @@ pub mod pallet { Ok(()) } - /// TODO + /// Deregister relayer intention to deliver inbound messages at given messages lane. + /// + /// After deregistration, relayer won't get lane-specific boost for message delivery + /// transactions at that lane. It would still get the basic boost until the `deregister` + /// call. + /// + /// This call (if successful), removes relayer from the relayers set that will be active + /// during next epoch. If relayer is still in the active set, it keeps getting additional + /// priority boost for his message delivery transaction at that lane. The relayer will be + /// able to claim his lane stake back when it is removed from both active and the next set. #[pallet::call_index(4)] #[pallet::weight(Weight::zero())] // TODO pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { @@ -371,13 +388,17 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; + // TODO: can't simply remove from `registration.lanes` because then the relayer + // may get his funds back. So we need to check if relayer is in the active/next + // set when computing required total stake. + // remove relayer from the next set. It still may be in the active set ensure!(registration.deregister_at_lane(lane), Error::::NotRegisteredAtLane); // remove relayer from the `next_set` of lane relayers. Relayer still remains // in the active set until current epoch ends - LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { - let mut lane_relayers = match lane_relayers_ref.take() { + LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { + let mut lane_relayers = match maybe_lane_relayers.take() { Some(lane_relayers) => lane_relayers, None => fail!(Error::::NotRegisteredAtLane), }; @@ -402,7 +423,7 @@ pub mod pallet { )); } - *lane_relayers_ref = Some(lane_relayers); + *maybe_lane_relayers = Some(lane_relayers); Ok::<_, Error>(()) })?; @@ -1480,4 +1501,124 @@ mod tests { ); }); } + + #[test] + fn deregister_at_lane_fails_for_unregistered_relayer() { + run_test(|| { + assert_noop!( + Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + ), + Error::::NotRegistered, + ); + }); + } + + #[test] + fn deregister_at_lane_fails_if_theres_no_lane_registration() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + + assert_noop!( + Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + ), + Error::::NotRegisteredAtLane, + ); + }) + } + + #[test] + fn deregister_at_lane_fails_if_lane_relayers_are_missing() { + run_test(|| { + let mut registration = registration(151, Stake::get()); + registration.register_at_lane(test_lane_id()); + RegisteredRelayers::::insert(REGISTER_RELAYER, registration); + + assert_noop!( + Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + ), + Error::::NotRegisteredAtLane, + ); + }) + } + + #[test] + fn deregister_at_lane_works() { + run_test(|| { + let initial_valid_till = 151 + EpochLength::get(); + let registration = registration(initial_valid_till, Stake::get()); + System::::set_block_number(100); + RegisteredRelayers::::insert(REGISTER_RELAYER, registration); + + // when the relayer is not in the active set, `valid_till` is not changed + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + assert_ok!(Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_relayers().len(), 0); + + // when the relayer is in the active set BUT current block + required lease is lte than + // the `valid_till`, `valid_till` is not changed + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + System::::set_block_number(lane_relayers.next_set_may_enact_at()); + assert_ok!(Pallet::::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.active_relayers().len(), 1); + assert_eq!(lane_relayers.next_relayers().len(), 0); + System::::set_block_number(lane_relayers.next_set_may_enact_at()); + assert_ok!(Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + assert_eq!( + RegisteredRelayers::::get(REGISTER_RELAYER).unwrap().valid_till(), + Some(initial_valid_till) + ); + + // when the relayers is in the active set AND current block + required lease is gt than + // the `valid_till`, `valid_till` is changed + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + assert_ok!(Pallet::::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.active_relayers().len(), 1); + assert_eq!(lane_relayers.next_relayers().len(), 0); + System::::set_block_number(lane_relayers.next_set_may_enact_at()); + assert_ok!(Pallet::::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + assert!( + RegisteredRelayers::::get(REGISTER_RELAYER).unwrap().valid_till() > + Some(initial_valid_till) + ); + }); + } } diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 26bd00538b..3e1bd55ec0 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -198,6 +198,7 @@ parameter_types! { pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); pub InitialElectionLength: u32 = 4; pub SlotLength: u32 = 16; + pub EpochLength: u32 = 1_024; pub MaxLanesPerRelayer: u32 = 4; pub PriorityBoostForActiveLaneRelayer: TransactionPriority = 4; } @@ -313,7 +314,7 @@ impl pallet_bridge_relayers::Config for TestRuntime { type MaxNextRelayersPerLane = ConstU32; type InitialElectionLength = InitialElectionLength; type SlotLength = ConstU32<16>; - type EpochLength = ConstU32<1_024>; + type EpochLength = EpochLength; type PriorityBoostPerMessage = ConstU64<1>; type PriorityBoostForActiveLaneRelayer = PriorityBoostForActiveLaneRelayer; type WeightInfo = (); From 27e2669cf7b6422b990cafefd185cfa301d1dc52 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 Oct 2023 19:40:27 +0300 Subject: [PATCH 34/60] tests for advance_lane_epoch --- modules/relayers/src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 8ef82d8e01..35bc1cc3b6 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -461,7 +461,7 @@ pub mod pallet { // ensure that the current block number allows us to enact next set let current_block_number = SystemPallet::::block_number(); ensure!( - lane_relayers.next_set_may_enact_at() >= current_block_number, + lane_relayers.next_set_may_enact_at() <= current_block_number, Error::::TooEarlyToActivateNextRelayersSet, ); @@ -1621,4 +1621,84 @@ mod tests { ); }); } + + #[test] + fn advance_lane_epoch_requires_signed_origin() { + run_test(|| { + assert_noop!( + Pallet::::advance_lane_epoch(RuntimeOrigin::root(), test_lane_id(),), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn advance_lane_epoch_fails_if_lane_relayers_are_missing() { + run_test(|| { + assert_noop!( + Pallet::::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + ), + Error::::NoRelayersAtLane + ); + }); + } + + #[test] + fn advance_lane_epoch_fails_if_next_set_may_not_be_enacted_yet() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0, + )); + + assert_noop!( + Pallet::::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + ), + Error::::TooEarlyToActivateNextRelayersSet, + ); + }); + } + + #[test] + fn advance_lane_epoch_works() { + run_test(|| { + // when first relayer registers, we allow other relayers to register for + // `InitialElectionLength` blocks + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get()), + ); + assert_ok!(Pallet::::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0, + )); + + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); + + // when active epoch is advanced, new epoch starts at the block, where it has been + // actually started, not the epoch where previous epoch was supposed to end + System::::set_block_number(lane_relayers.next_set_may_enact_at() + 77); + assert_ok!(Pallet::::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + + let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert_eq!( + lane_relayers.next_set_may_enact_at(), + InitialElectionLength::get() + 77 + EpochLength::get() + ); + }); + } } From 1f04623f3e94c205cd7fc2059f2ec166f9f60b32 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 13 Oct 2023 10:37:52 +0300 Subject: [PATCH 35/60] separate calls to manage relayers stake --- modules/relayers/src/extension/mod.rs | 30 + modules/relayers/src/lib.rs | 699 ++++++++++++++++------- modules/relayers/src/payment_adapter.rs | 14 +- primitives/relayers/src/lane_relayers.rs | 28 +- 4 files changed, 535 insertions(+), 236 deletions(-) diff --git a/modules/relayers/src/extension/mod.rs b/modules/relayers/src/extension/mod.rs index 5a92468acb..ada044c1d6 100644 --- a/modules/relayers/src/extension/mod.rs +++ b/modules/relayers/src/extension/mod.rs @@ -1013,6 +1013,11 @@ mod tests { run_test(|| { initialize_environment(100, 100, 100); + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(relayer_account_at_this_chain()), + Stake::get(), + ) + .unwrap(); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); @@ -1043,6 +1048,11 @@ mod tests { run_test(|| { initialize_environment(100, 100, 100); + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(relayer_account_at_this_chain()), + Stake::get(), + ) + .unwrap(); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); @@ -1579,6 +1589,11 @@ mod tests { ); // slashing works for message delivery calls + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(relayer_account_at_this_chain()), + test_stake, + ) + .unwrap(); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake); @@ -1589,6 +1604,11 @@ mod tests { Balances::free_balance(delivery_rewards_account()) ); + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(relayer_account_at_this_chain()), + test_stake, + ) + .unwrap(); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake); @@ -1599,6 +1619,11 @@ mod tests { Balances::free_balance(delivery_rewards_account()) ); + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(relayer_account_at_this_chain()), + test_stake, + ) + .unwrap(); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake); @@ -1888,6 +1913,11 @@ mod tests { initialize_environment(100, 100, best_delivered_message); // register relayer so it gets priority boost + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(relayer_account_at_this_chain()), + Stake::get(), + ) + .unwrap(); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 35bc1cc3b6..c81e36eea0 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -190,6 +190,76 @@ pub mod pallet { ) } + /// Reserve some funds on relayer account to be able to register later. + /// + /// Basic (`register` call) and lane registration require some funds on relayer account to + /// be reserved. This stake is slashed if relayer is submitting invalid transaction when + /// his registration is active. In exchange, relayer gets priority boosts for his message + /// delivery transactions and, as a result, reward for delivering messages. + /// + /// This call must be followed by `register` and (optionally) `register_at_lane` calls to + /// activate priority boosts. + #[pallet::call_index(1)] + #[pallet::weight(Weight::zero())] // TODO + pub fn increase_stake( + origin: OriginFor, + additional_amount: T::Reward, + ) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { + // by default registration is valid until block `0`, which means that it is not + // active. To activate it, relayer must use the `register` call + let mut registration = + maybe_registration.take().unwrap_or_else(|| Registration::new(Zero::zero())); + + registration.set_stake(Self::update_relayer_stake( + &relayer, + registration.current_stake(), + registration.current_stake().saturating_add(additional_amount), + )?); + + *maybe_registration = Some(registration); + + Ok(()) + }) + } + + /// Unreserve some or all funds, reserved previously by the `reserve_funds` call. + /// + /// The reserved amount after this call must cover basic registration and all lane + /// registrations that relayer has. + #[pallet::call_index(2)] + #[pallet::weight(Weight::zero())] // TODO + pub fn decrease_stake(origin: OriginFor, to_unreserve: T::Reward) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { + let mut registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; + + // check if reserved amount after the call is enough to cover all remaining + // registrations + let stake_before = registration.current_stake(); + let stake_after = stake_before.saturating_sub(to_unreserve); + let required_stake = Self::required_stake(®istration); + ensure!(stake_after >= required_stake, Error::::StakeIsTooLow); + + // ok, now we know that we can increase the stake => let's do it + registration.set_stake(Self::update_relayer_stake( + &relayer, + stake_before, + stake_after, + )?); + + *maybe_registration = Some(registration); + + Ok(()) + }) + } + /// Register relayer or update its registration. /// /// Registration allows relayer to get priority boost for its message delivery transactions. @@ -200,7 +270,7 @@ pub mod pallet { /// /// Relayers may get additional priority boost by registering their intention to relay /// messages at given lanes, using `register_at_lane` method. - #[pallet::call_index(1)] + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::register())] pub fn register(origin: OriginFor, valid_till: BlockNumberFor) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -214,8 +284,14 @@ pub mod pallet { ); RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let mut registration = - maybe_registration.take().unwrap_or_else(|| Registration::new(valid_till)); + // registration must have been created by the `increase_stake` before + let mut registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; + + // ensure that the stake is enough + ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); // new `valid_till` must be larger (or equal) than the old one ensure!( @@ -224,13 +300,6 @@ pub mod pallet { ); registration.set_valid_till(valid_till); - // reserve stake on relayer account - registration.set_stake(Self::update_relayer_stake( - &relayer, - registration.current_stake(), - registration.required_stake(Self::base_stake(), Self::stake_per_lane()), - )?); - log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); Self::deposit_event(Event::::RegistrationUpdated { relayer: relayer.clone(), @@ -249,7 +318,9 @@ pub mod pallet { /// boost. Keep in mind that the relayer can't deregister until `valid_till` block, which /// he has specified in the registration call. The relayer is also unregistered from all /// lanes, where he has explicitly registered using `register_at_lane`. - #[pallet::call_index(2)] + /// + /// The stake on relayer account is unreserved. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::deregister())] pub fn deregister(origin: OriginFor) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -302,7 +373,7 @@ pub mod pallet { /// reward amount is computed at the bridged (source chain). In the case if /// [`DeliveryConfirmationPaymentsAdapter`] is used to register rewards, the maximal reward /// per message is limited by the `MaxRewardPerMessage` parameter. - #[pallet::call_index(3)] + #[pallet::call_index(5)] #[pallet::weight(Weight::zero())] // TODO pub fn register_at_lane( origin: OriginFor, @@ -321,12 +392,15 @@ pub mod pallet { }; // cannot add another lane registration if relayer has already max allowed - // lane registrations OR if it is already registered at that lane + // lane registrations ensure!( registration.register_at_lane(lane), Error::::FailedToRegisterAtLane ); + // ensure that the relayer stake is enough + ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); + // let's try to claim a slot in the next set LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { let mut lane_relayers = match maybe_lane_relayers.take() { @@ -373,7 +447,7 @@ pub mod pallet { /// during next epoch. If relayer is still in the active set, it keeps getting additional /// priority boost for his message delivery transaction at that lane. The relayer will be /// able to claim his lane stake back when it is removed from both active and the next set. - #[pallet::call_index(4)] + #[pallet::call_index(6)] #[pallet::weight(Weight::zero())] // TODO pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -388,13 +462,6 @@ pub mod pallet { None => fail!(Error::::NotRegistered), }; - // TODO: can't simply remove from `registration.lanes` because then the relayer - // may get his funds back. So we need to check if relayer is in the active/next - // set when computing required total stake. - - // remove relayer from the next set. It still may be in the active set - ensure!(registration.deregister_at_lane(lane), Error::::NotRegisteredAtLane); - // remove relayer from the `next_set` of lane relayers. Relayer still remains // in the active set until current epoch ends LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { @@ -406,21 +473,13 @@ pub mod pallet { // remove relayer from the next set if it is there lane_relayers.next_set_try_remove(&relayer); - // make sure that the `valid_till` covers current epoch if relayer is in the - // active lane relayers set - let is_in_active_set = lane_relayers - .active_relayers() - .iter() - .filter(|r| *r.relayer() == relayer) - .next() - .is_some(); - if is_in_active_set { - registration.set_valid_till(sp_std::cmp::max( - registration.valid_till_ignore_lanes(), - lane_relayers - .next_set_may_enact_at() - .saturating_add(Self::required_registration_lease()), - )); + // if relayer is still in the active set, we can't simply remove this lane + // registration - it needs to keep additional stake until current epoch is + // finished. So we will make it later, in the `advance_lane_epoch` call + let is_in_active_set = + lane_relayers.relayer_from_active_set(&relayer).is_some(); + if !is_in_active_set { + Self::remove_lane_from_relayer_registration(&mut registration, lane); } *maybe_lane_relayers = Some(lane_relayers); @@ -446,14 +505,14 @@ pub mod pallet { /// to sync between relayers on who will submit this transaction, so first transaction from /// anyone will be accepted and it will have the zero cost. All subsequent transactions will /// be paid. We suggest the first relayer from the `next_set` to submit this transaction. - #[pallet::call_index(5)] + #[pallet::call_index(7)] #[pallet::weight(Weight::zero())] // TODO pub fn advance_lane_epoch(origin: OriginFor, lane: LaneId) -> DispatchResult { let _ = ensure_signed(origin)?; // remove relayer from the `next_set` of lane relayers. So relayer is still - LaneRelayers::::try_mutate(lane, |lane_relayers_ref| { - let mut lane_relayers = match lane_relayers_ref.take() { + LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { + let mut lane_relayers = match maybe_lane_relayers.take() { Some(lane_relayers) => lane_relayers, None => fail!(Error::::NoRelayersAtLane), }; @@ -465,11 +524,37 @@ pub mod pallet { Error::::TooEarlyToActivateNextRelayersSet, ); + // activate next set of relayers + let old_active_set = lane_relayers + .active_relayers() + .iter() + .map(|r| r.relayer().clone()) + .collect::>(); let new_next_set_may_enact_at = current_block_number.saturating_add(T::EpochLength::get()); lane_relayers.activate_next_set(new_next_set_may_enact_at); - *lane_relayers_ref = Some(lane_relayers); + // for every relayer, who was in the active set, but is missing from the new active + // set, check if we need to remove lane registration + for old_relayer in old_active_set { + if lane_relayers.relayer_from_active_set(&old_relayer).is_some() { + continue + } + + let _ = RegisteredRelayers::::try_mutate(old_relayer, |registration| { + if let Some(registration) = registration { + if Self::remove_lane_from_relayer_registration(registration, lane) { + Ok(()) + } else { + Err(()) + } + } else { + Err(()) + } + }); + } + + *maybe_lane_relayers = Some(lane_relayers); Ok::<_, Error>(()) })?; @@ -615,6 +700,49 @@ pub mod pallet { >>::RequiredLaneStake::get() } + /// Returns total stake that the relayer must hold reserved on his account. + fn required_stake( + registration: &Registration, T::Reward, T::MaxLanesPerRelayer>, + ) -> T::Reward { + registration.required_stake(Self::base_stake(), Self::stake_per_lane()) + } + + /// Returns true if relayer stake is enough to cover basic and all lane registrations. + fn is_stake_enough( + registration: &Registration, T::Reward, T::MaxLanesPerRelayer>, + ) -> bool { + registration.current_stake() >= Self::required_stake(registration) + } + + /// Remove lane from basic relayer registration. It shall only be called if relayer is + /// already removed from both active and next relayers set. + /// + /// Returns true if registration has been modified. + fn remove_lane_from_relayer_registration( + registration: &mut Registration, T::Reward, T::MaxLanesPerRelayer>, + lane: LaneId, + ) -> bool { + // if we are already removed from the + if !registration.deregister_at_lane(lane) { + return false + } + + // since relayer may get priority boost for transactions, verified at **this** block, we + // require its registration for aty least another `required_registration_lease` blocks. + // We do not need to do that if relayer still have other lane registrations - the valid + // till only works when relayer has no lane registrations and when it'll be + // deregistering from the last lane, we will increase it. + if let Some(valid_till) = registration.valid_till() { + registration.set_valid_till(sp_std::cmp::max( + valid_till, + SystemPallet::::block_number() + .saturating_add(Self::required_registration_lease()), + )); + } + + true + } + /// Update relayer stake. fn update_relayer_stake( relayer: &T::AccountId, @@ -715,18 +843,18 @@ pub mod pallet { DuplicateLaneRegistration, /// Relayer has too many lane registrations. FailedToRegisterAtLane, - /// - TooManyScheduledChanges, - /// - TooManyLaneRelayersAtLane, - /// + /// The expected reward, specified by relayer during `register_at_lane` call is too large + /// to occupy an entry in the next relayer set. TooLargeRewardToOccupyAnEntry, - /// + /// The relayer is not registered at given lane. NotRegisteredAtLane, - /// + /// Lane has no relayers set. NoRelayersAtLane, - /// + /// Next set of lane relayers cannot be activated now. It can be activated later, once + /// at `next_set_may_enact_at` block. TooEarlyToActivateNextRelayersSet, + /// Relayer stake is too low to add a basic registration and/or lane registration. + StakeIsTooLow, } /// Map of the relayer => accumulated reward. @@ -817,10 +945,7 @@ mod tests { fn root_cant_claim_anything() { run_test(|| { assert_noop!( - Pallet::::claim_rewards( - RuntimeOrigin::root(), - test_reward_account_param() - ), + BridgeRelayers::claim_rewards(RuntimeOrigin::root(), test_reward_account_param()), DispatchError::BadOrigin, ); }); @@ -830,7 +955,7 @@ mod tests { fn relayer_cant_claim_if_no_reward_exists() { run_test(|| { assert_noop!( - Pallet::::claim_rewards( + BridgeRelayers::claim_rewards( RuntimeOrigin::signed(REGULAR_RELAYER), test_reward_account_param() ), @@ -848,7 +973,7 @@ mod tests { 100, ); assert_noop!( - Pallet::::claim_rewards( + BridgeRelayers::claim_rewards( RuntimeOrigin::signed(FAILING_RELAYER), test_reward_account_param() ), @@ -867,7 +992,7 @@ mod tests { test_reward_account_param(), 100, ); - assert_ok!(Pallet::::claim_rewards( + assert_ok!(BridgeRelayers::claim_rewards( RuntimeOrigin::signed(REGULAR_RELAYER), test_reward_account_param() )); @@ -932,178 +1057,215 @@ mod tests { } #[test] - fn register_fails_if_valid_till_is_a_past_block() { + fn increase_stake_requires_signed_origin() { run_test(|| { - System::::set_block_number(100); - assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 50), - Error::::InvalidRegistrationLease, + BridgeRelayers::increase_stake(RuntimeOrigin::root(), 1), + DispatchError::BadOrigin ); }); } #[test] - fn register_fails_if_valid_till_lease_is_less_than_required() { + fn increase_stake_creates_empty_registration() { run_test(|| { - System::::set_block_number(100); + assert_ok!(BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + Stake::get() + 42 + )); + assert_eq!( + BridgeRelayers::registered_relayer(®ISTER_RELAYER), + Some(registration(0, Stake::get() + 42)) + ); + assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 42); + }); + } + #[test] + fn increase_stake_works_for_existing_registration() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get() + 42), + ); + + assert_ok!(BridgeRelayers::increase_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 8)); + assert_eq!( + BridgeRelayers::registered_relayer(®ISTER_RELAYER), + Some(registration(150, Stake::get() + 50)) + ); + assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), 8); + }); + } + + #[test] + fn increase_stake_fails_if_it_fails_to_reserve_additional_stake() { + run_test(|| { assert_noop!( - Pallet::::register( + BridgeRelayers::increase_stake( RuntimeOrigin::signed(REGISTER_RELAYER), - 99 + Lease::get() + ThisChainBalance::MAX ), - Error::::InvalidRegistrationLease, + Error::::FailedToReserve, ); }); } #[test] - fn register_works() { + fn decrease_stake_requires_signed_origin() { run_test(|| { - get_ready_for_events(); - - assert_ok!(Pallet::::register( - RuntimeOrigin::signed(REGISTER_RELAYER), - 150 - )); - assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get()); - assert_eq!( - Pallet::::registered_relayer(REGISTER_RELAYER), - Some(registration(150, Stake::get())), + assert_noop!( + BridgeRelayers::decrease_stake(RuntimeOrigin::root(), 1), + DispatchError::BadOrigin ); + }); + } - assert_eq!( - System::::events().last(), - Some(&EventRecord { - phase: Phase::Initialization, - event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { - relayer: REGISTER_RELAYER, - registration: registration(150, Stake::get()), - }), - topics: vec![], - }), + #[test] + fn decrease_stake_fails_if_relayer_is_not_registered() { + run_test(|| { + assert_noop!( + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::NotRegistered ); }); } #[test] - fn register_fails_if_new_valid_till_is_lesser_than_previous() { + fn decrease_stake_fails_if_stake_after_call_is_too_low_to_cover_all_registrations() { run_test(|| { - assert_ok!(Pallet::::register( - RuntimeOrigin::signed(REGISTER_RELAYER), - 150 - )); + let lane_1_id = test_lane_id(); + let lane_2_id = LaneId::new(lane_1_id, lane_1_id); + + // first, check that it accounts both basic and lane registrations + let mut reg = registration(150, Stake::get() + LaneStake::get() + LaneStake::get()); + assert!(reg.register_at_lane(lane_1_id)); + assert!(reg.register_at_lane(lane_2_id)); + RegisteredRelayers::::insert(REGISTER_RELAYER, reg); + assert_noop!( + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::StakeIsTooLow + ); + // first, check that it accounts basic registration + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get()), + ); assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 125), - Error::::CannotReduceRegistrationLease, + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::StakeIsTooLow ); }); } #[test] - fn register_fails_if_it_cant_unreserve_some_balance_if_required_stake_decreases() { + fn decrease_stake_fails_if_it_cant_unreserve_stake() { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, registration(150, Stake::get() + 1), ); - assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), - Error::::FailedToUnreserve, + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::FailedToUnreserve ); }); } #[test] - fn register_unreserves_some_balance_if_required_stake_decreases() { + fn decrease_stake_works() { run_test(|| { - get_ready_for_events(); + let lane_1_id = test_lane_id(); + let lane_2_id = LaneId::new(lane_1_id, lane_1_id); + + // check that it works for both basic and lane registrations + TestStakeAndSlash::reserve( + ®ISTER_RELAYER, + Stake::get() + LaneStake::get() + LaneStake::get() + 1, + ) + .unwrap(); + let mut reg = registration(150, Stake::get() + LaneStake::get() + LaneStake::get() + 1); + assert!(reg.register_at_lane(lane_1_id)); + assert!(reg.register_at_lane(lane_2_id)); + RegisteredRelayers::::insert(REGISTER_RELAYER, reg); + assert_ok!(BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1)); + assert_eq!( + Balances::reserved_balance(REGISTER_RELAYER), + Stake::get() + LaneStake::get() + LaneStake::get() + ); + // first, check that it works for basic registration RegisteredRelayers::::insert( REGISTER_RELAYER, registration(150, Stake::get() + 1), ); - TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() + 1).unwrap(); - assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 1); - let free_balance = Balances::free_balance(REGISTER_RELAYER); - - assert_ok!(Pallet::::register( - RuntimeOrigin::signed(REGISTER_RELAYER), - 150 - )); - assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get()); - assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + 1); + assert_ok!(BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1)); assert_eq!( - Pallet::::registered_relayer(REGISTER_RELAYER), - Some(registration(150, Stake::get())), + Balances::reserved_balance(REGISTER_RELAYER), + Stake::get() + LaneStake::get() + LaneStake::get() - 1 ); + }); + } - assert_eq!( - System::::events().last(), - Some(&EventRecord { - phase: Phase::Initialization, - event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { - relayer: REGISTER_RELAYER, - registration: registration(150, Stake::get()), - }), - topics: vec![], - }), + #[test] + fn register_fails_if_valid_till_is_a_past_block() { + run_test(|| { + System::::set_block_number(100); + + assert_noop!( + BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 50), + Error::::InvalidRegistrationLease, ); }); } #[test] - fn register_fails_if_it_cant_reserve_some_balance() { + fn register_fails_if_valid_till_lease_is_less_than_required() { run_test(|| { - Balances::set_balance(®ISTER_RELAYER, 0); + System::::set_block_number(100); + assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), - Error::::FailedToReserve, + BridgeRelayers::register( + RuntimeOrigin::signed(REGISTER_RELAYER), + 99 + Lease::get() + ), + Error::::InvalidRegistrationLease, ); }); } #[test] - fn register_fails_if_it_cant_reserve_some_balance_if_required_stake_increases() { + fn register_fails_if_stake_is_not_enough() { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(150, Stake::get() - 1), + registration(0, Stake::get() - 1), ); - Balances::set_balance(®ISTER_RELAYER, 0); assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), - Error::::FailedToReserve, + BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), + Error::::StakeIsTooLow, ); }); } #[test] - fn register_reserves_some_balance_if_required_stake_increases() { + fn register_works() { run_test(|| { get_ready_for_events(); - - RegisteredRelayers::::insert( - REGISTER_RELAYER, - registration(150, Stake::get() - 1), - ); - TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() - 1).unwrap(); - - let free_balance = Balances::free_balance(REGISTER_RELAYER); - assert_ok!(Pallet::::register( + assert_ok!(BridgeRelayers::increase_stake( RuntimeOrigin::signed(REGISTER_RELAYER), - 150 + Stake::get() )); assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get()); - assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance - 1); + + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); assert_eq!( - Pallet::::registered_relayer(REGISTER_RELAYER), - Some(registration(150, Stake::get())), + BridgeRelayers::registered_relayer(REGISTER_RELAYER), + Some(registration(150, Stake::get())) ); + assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get()); assert_eq!( System::::events().last(), @@ -1119,11 +1281,28 @@ mod tests { }); } + #[test] + fn register_fails_if_new_valid_till_is_lesser_than_previous() { + run_test(|| { + assert_ok!(BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + Stake::get() + )); + + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); + + assert_noop!( + BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 125), + Error::::CannotReduceRegistrationLease, + ); + }); + } + #[test] fn deregister_fails_if_not_registered() { run_test(|| { assert_noop!( - Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), + BridgeRelayers::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), Error::::NotRegistered, ); }); @@ -1132,15 +1311,16 @@ mod tests { #[test] fn deregister_fails_if_registration_is_still_active() { run_test(|| { - assert_ok!(Pallet::::register( + assert_ok!(BridgeRelayers::increase_stake( RuntimeOrigin::signed(REGISTER_RELAYER), - 150 + Stake::get() )); + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); System::::set_block_number(100); assert_noop!( - Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), + BridgeRelayers::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), Error::::RegistrationIsStillActive, ); }); @@ -1149,11 +1329,12 @@ mod tests { #[test] fn deregister_fails_if_relayer_has_lanes_registrations() { run_test(|| { - assert_ok!(Pallet::::register( + assert_ok!(BridgeRelayers::increase_stake( RuntimeOrigin::signed(REGISTER_RELAYER), - 150 + Stake::get() + LaneStake::get() )); - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0, @@ -1162,7 +1343,7 @@ mod tests { System::::set_block_number(151); assert_noop!( - Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), + BridgeRelayers::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), Error::::RegistrationIsStillActive, ); }); @@ -1173,16 +1354,17 @@ mod tests { run_test(|| { get_ready_for_events(); - assert_ok!(Pallet::::register( + assert_ok!(BridgeRelayers::increase_stake( RuntimeOrigin::signed(REGISTER_RELAYER), - 150 + Stake::get() )); + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); System::::set_block_number(151); let reserved_balance = Balances::reserved_balance(REGISTER_RELAYER); let free_balance = Balances::free_balance(REGISTER_RELAYER); - assert_ok!(Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER))); + assert_ok!(BridgeRelayers::deregister(RuntimeOrigin::signed(REGISTER_RELAYER))); assert_eq!( Balances::reserved_balance(REGISTER_RELAYER), reserved_balance - Stake::get() @@ -1205,30 +1387,31 @@ mod tests { #[test] fn deregister_works_after_last_lane_registration_is_removed() { run_test(|| { - assert_ok!(Pallet::::register( + assert_ok!(BridgeRelayers::increase_stake( RuntimeOrigin::signed(REGISTER_RELAYER), - 150 + Stake::get() + LaneStake::get() )); - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0, )); - assert_ok!(Pallet::::deregister_at_lane( + assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), )); System::::set_block_number(151); - assert_ok!(Pallet::::deregister(RuntimeOrigin::signed(REGISTER_RELAYER))); + assert_ok!(BridgeRelayers::deregister(RuntimeOrigin::signed(REGISTER_RELAYER))); }); } #[test] fn is_registration_active_is_false_for_unregistered_relayer() { run_test(|| { - assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(!BridgeRelayers::is_registration_active(®ISTER_RELAYER)); }); } @@ -1239,7 +1422,7 @@ mod tests { REGISTER_RELAYER, registration(150, Stake::get() - 1), ); - assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(BridgeRelayers::is_registration_active(®ISTER_RELAYER)); }); } @@ -1252,20 +1435,20 @@ mod tests { REGISTER_RELAYER, registration(150, Stake::get()), ); - assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(!BridgeRelayers::is_registration_active(®ISTER_RELAYER)); }); } #[test] fn is_registration_active_is_true_when_relayer_is_registered_at_lanes() { run_test(|| { - System::::set_block_number(150 - Lease::get()); + System::::set_block_number(151 - Lease::get()); let mut registration = registration(150, Stake::get()); assert!(registration.register_at_lane(LaneId::new(1, 2))); RegisteredRelayers::::insert(REGISTER_RELAYER, registration); - assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(BridgeRelayers::is_registration_active(®ISTER_RELAYER)); }); } @@ -1278,7 +1461,7 @@ mod tests { REGISTER_RELAYER, registration(151, Stake::get()), ); - assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(BridgeRelayers::is_registration_active(®ISTER_RELAYER)); }); } @@ -1286,7 +1469,7 @@ mod tests { fn register_at_lane_fails_for_unregistered_relayer() { run_test(|| { assert_noop!( - Pallet::::register_at_lane( + BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 @@ -1299,7 +1482,7 @@ mod tests { #[test] fn register_at_lane_fails_if_relayer_has_max_lane_registrations() { run_test(|| { - let mut registration = registration(151, Stake::get()); + let mut registration = registration(151, Stake::get() + LaneStake::get()); for i in 0..MaxLanesPerRelayer::get() { assert!(registration.register_at_lane(LaneId::new(42, i))); } @@ -1307,7 +1490,7 @@ mod tests { RegisteredRelayers::::insert(REGISTER_RELAYER, registration); assert_noop!( - Pallet::::register_at_lane( + BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), LaneId::new(77, 77), 0 @@ -1326,12 +1509,12 @@ mod tests { } RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); LaneRelayers::::insert(test_lane_id(), lane_relayers); assert_noop!( - Pallet::::register_at_lane( + BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 1 @@ -1346,16 +1529,16 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( FAILING_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get() - 1), ); assert_noop!( - Pallet::::register_at_lane( + BridgeRelayers::register_at_lane( RuntimeOrigin::signed(FAILING_RELAYER), test_lane_id(), 0 ), - Error::::FailedToReserve, + Error::::StakeIsTooLow, ); }); } @@ -1365,21 +1548,21 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); RegisteredRelayers::::insert( REGISTER_RELAYER_2, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); // when first relayer registers, we allow other relayers to register in next // `InitialElectionLength` blocks - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 1 )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); assert_eq!(lane_relayers.active_relayers(), &[]); assert_eq!( @@ -1388,12 +1571,12 @@ mod tests { ); // next relayer registers, it occupies the correct slot in the set - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER_2), test_lane_id(), 0 )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); assert_eq!(lane_relayers.active_relayers(), &[]); assert_eq!( @@ -1411,28 +1594,28 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); // at first we want reward `1` - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 1 )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!( lane_relayers.next_relayers(), &[RelayerAndReward::new(REGISTER_RELAYER, 1)] ); // but then we change our expected reward - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!( lane_relayers.next_relayers(), &[RelayerAndReward::new(REGISTER_RELAYER, 0)] @@ -1450,21 +1633,21 @@ mod tests { } RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); RegisteredRelayers::::insert( REGISTER_RELAYER_2, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); LaneRelayers::::insert(test_lane_id(), lane_relayers); // occupy last entry by `REGISTER_RELAYER` with bid = 15 - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 15 ),); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); assert_eq!( lane_relayers.next_relayers().last(), @@ -1472,12 +1655,12 @@ mod tests { ); // then the `REGISTER_RELAYER_2` comes with better bid = 14 - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER_2), test_lane_id(), 14 ),); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); assert_eq!( lane_relayers.next_relayers().last(), @@ -1487,13 +1670,13 @@ mod tests { // => `REGISTER_RELAYER` is pushed out of the next set, but it still has the lane in // his base "registration" structure, so it can rejoin anytime by calling // `register_at_lane` with updated reward - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 13 ),); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); assert_eq!( lane_relayers.next_relayers().last(), @@ -1506,7 +1689,7 @@ mod tests { fn deregister_at_lane_fails_for_unregistered_relayer() { run_test(|| { assert_noop!( - Pallet::::deregister_at_lane( + BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), ), @@ -1520,11 +1703,11 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); assert_noop!( - Pallet::::deregister_at_lane( + BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), ), @@ -1536,12 +1719,12 @@ mod tests { #[test] fn deregister_at_lane_fails_if_lane_relayers_are_missing() { run_test(|| { - let mut registration = registration(151, Stake::get()); + let mut registration = registration(151, Stake::get() + LaneStake::get()); registration.register_at_lane(test_lane_id()); RegisteredRelayers::::insert(REGISTER_RELAYER, registration); assert_noop!( - Pallet::::deregister_at_lane( + BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), ), @@ -1554,79 +1737,151 @@ mod tests { fn deregister_at_lane_works() { run_test(|| { let initial_valid_till = 151 + EpochLength::get(); - let registration = registration(initial_valid_till, Stake::get()); + let registration = registration(initial_valid_till, Stake::get() + LaneStake::get()); System::::set_block_number(100); RegisteredRelayers::::insert(REGISTER_RELAYER, registration); // when the relayer is not in the active set, `valid_till` is not changed - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 )); - assert_ok!(Pallet::::deregister_at_lane( + assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + assert!(BridgeRelayers::registered_relayer(®ISTER_RELAYER) + .unwrap() + .lanes() + .is_empty()); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_relayers().len(), 0); // when the relayer is in the active set BUT current block + required lease is lte than // the `valid_till`, `valid_till` is not changed - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 )); System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(Pallet::::advance_lane_epoch( + assert_ok!(BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.active_relayers().len(), 1); assert_eq!(lane_relayers.next_relayers().len(), 0); System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(Pallet::::deregister_at_lane( + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); + assert!(BridgeRelayers::registered_relayer(®ISTER_RELAYER) + .unwrap() + .lanes() + .is_empty()); assert_eq!( - RegisteredRelayers::::get(REGISTER_RELAYER).unwrap().valid_till(), + BridgeRelayers::registered_relayer(®ISTER_RELAYER).unwrap().valid_till(), Some(initial_valid_till) ); // when the relayers is in the active set AND current block + required lease is gt than // the `valid_till`, `valid_till` is changed - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 )); - assert_ok!(Pallet::::advance_lane_epoch( + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); + System::::set_block_number(lane_relayers.next_set_may_enact_at()); + assert_ok!(BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.active_relayers().len(), 1); assert_eq!(lane_relayers.next_relayers().len(), 0); System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(Pallet::::deregister_at_lane( + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); + assert!(BridgeRelayers::registered_relayer(®ISTER_RELAYER) + .unwrap() + .lanes() + .is_empty()); assert!( - RegisteredRelayers::::get(REGISTER_RELAYER).unwrap().valid_till() > + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().valid_till() > Some(initial_valid_till) ); }); } + #[test] + fn deregister_at_lane_does_not_remove_lane_from_registration_if_relayer_is_in_active_set() { + run_test(|| { + // register at lane + assert_ok!(BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + Stake::get() + LaneStake::get() + )); + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 100)); + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + + // advance lane epoch => relayer is in the active set + System::::set_block_number( + BridgeRelayers::lane_relayers(test_lane_id()).unwrap().next_set_may_enact_at(), + ); + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + + // deregister at lane => lane registration is not removed immeditely, it will be + // removed later, during next `advance_lane_epoch` call + assert_ok!(BridgeRelayers::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + assert_eq!( + BridgeRelayers::registered_relayer(®ISTER_RELAYER).unwrap().lanes(), + &[test_lane_id()] + ); + + // call `advance_lane_epoch` and ensure that the lane registraion is actually removed + System::::set_block_number( + BridgeRelayers::lane_relayers(test_lane_id()).unwrap().next_set_may_enact_at(), + ); + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + assert_eq!( + BridgeRelayers::registered_relayer(®ISTER_RELAYER).unwrap().lanes(), + &[test_lane_id()] + ); + }); + } + #[test] fn advance_lane_epoch_requires_signed_origin() { run_test(|| { assert_noop!( - Pallet::::advance_lane_epoch(RuntimeOrigin::root(), test_lane_id(),), + BridgeRelayers::advance_lane_epoch(RuntimeOrigin::root(), test_lane_id(),), DispatchError::BadOrigin ); }); @@ -1636,7 +1891,7 @@ mod tests { fn advance_lane_epoch_fails_if_lane_relayers_are_missing() { run_test(|| { assert_noop!( - Pallet::::advance_lane_epoch( + BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), ), @@ -1650,16 +1905,16 @@ mod tests { run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0, )); assert_noop!( - Pallet::::advance_lane_epoch( + BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() ), @@ -1675,26 +1930,26 @@ mod tests { // `InitialElectionLength` blocks RegisteredRelayers::::insert( REGISTER_RELAYER, - registration(151, Stake::get()), + registration(151, Stake::get() + LaneStake::get()), ); - assert_ok!(Pallet::::register_at_lane( + assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0, )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); // when active epoch is advanced, new epoch starts at the block, where it has been // actually started, not the epoch where previous epoch was supposed to end System::::set_block_number(lane_relayers.next_set_may_enact_at() + 77); - assert_ok!(Pallet::::advance_lane_epoch( + assert_ok!(BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); - let lane_relayers = LaneRelayers::::get(test_lane_id()).unwrap(); + let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); assert_eq!( lane_relayers.next_set_may_enact_at(), InitialElectionLength::get() + 77 + EpochLength::get() diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index a3e8840e46..4a6464aa61 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -108,24 +108,14 @@ where let _ = LaneRelayers::::try_mutate(lane_id, |maybe_lane_relayers| { if let Some(lane_relayers) = maybe_lane_relayers { // if relayer is NOT in the active set, we don't want to do anything here - let relayer_in_active_set = lane_relayers - .active_relayers() - .iter() - .filter(|r| *r.relayer() == relayer) - .next() - .cloned(); + let relayer_in_active_set = lane_relayers.relayer_from_active_set(&relayer); let relayer_in_active_set = match relayer_in_active_set { Some(relayer_in_active_set) => relayer_in_active_set, None => return Err(()), }; // if relayer is already in the active set, we don't want to do anything here - let is_in_next_set = lane_relayers - .next_relayers() - .iter() - .filter(|r| *r.relayer() == relayer) - .next() - .is_some(); + let is_in_next_set = lane_relayers.relayer_from_next_set(&relayer).is_some(); if is_in_next_set { return Err(()) } diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 561a56fe4b..32e9c171ea 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -119,12 +119,28 @@ where self.next_set_may_enact_at } - /// Returns relayers in the active set. + /// Returns relayer entry from the active set. + pub fn relayer_from_active_set( + &self, + relayer: &AccountId, + ) -> Option<&RelayerAndReward> { + self.active_set.iter().filter(|r| r.relayer() == relayer).next() + } + + /// Returns relayer entry from the next set. + pub fn relayer_from_next_set( + &self, + relayer: &AccountId, + ) -> Option<&RelayerAndReward> { + self.next_set.iter().filter(|r| r.relayer() == relayer).next() + } + + /// Returns relayers from the active set. pub fn active_relayers(&self) -> &[RelayerAndReward] { self.active_set.as_slice() } - /// Returns relayers in the next set. + /// Returns relayers from the next set. pub fn next_relayers(&self) -> &[RelayerAndReward] { self.next_set.as_slice() } @@ -168,6 +184,14 @@ where // relayers => ignoring `try_push` result is safe let _ = self.active_set.try_push(self.next_set.remove(0)); } + + // since capacity of the `next_set` is larger than the capacity of the `active_set`, we may + // or may not remove relayers, that have not been advanced to the `active_set` from the + // new `next_set`. Current implementation removes them, which may be a bit harsh towards + // such relayers. But since we assume that most likely active relayers will reappear in the + // next set later (when they relay at least one message), there's no much point in keeping + // them here. + // we clear next set here. Relayers from the active set will be readded here if // they deliver at least one message in epoch and their reward will be concurrent. // Or else, they'll need to reregister manually. From 0638bcc95ae74cd33e9f0fd9ed079d480b3b1274 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 13 Oct 2023 15:37:33 +0300 Subject: [PATCH 36/60] split active and next relayer sets --- modules/relayers/src/extension/priority.rs | 37 +- modules/relayers/src/lib.rs | 680 ++++++++++----------- modules/relayers/src/payment_adapter.rs | 35 +- primitives/relayers/src/lane_relayers.rs | 440 +++++++++---- primitives/relayers/src/lib.rs | 4 +- 5 files changed, 672 insertions(+), 524 deletions(-) diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index b5ed5c486d..eaf1374aa7 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -30,6 +30,7 @@ use frame_system::{pallet_prelude::BlockNumberFor, Pallet as SystemPallet}; use sp_runtime::{ traits::{One, Zero}, transaction_validity::TransactionPriority, + Saturating, }; // reexport everything from `integrity_tests` module @@ -68,11 +69,8 @@ where { // if there are no relayers, explicitly registered at this lane, noone gets additional // priority boost - let lane_relayers = match RelayersPallet::::lane_relayers(&lane_id) { - Some(lane_relayers) => lane_relayers, - None => return 0, - }; - let active_lane_relayers = lane_relayers.active_relayers(); + let lane_relayers = RelayersPallet::::active_lane_relayers(&lane_id); + let active_lane_relayers = lane_relayers.relayers(); let lane_relayers_len: BlockNumberFor = (active_lane_relayers.len() as u32).into(); if lane_relayers_len.is_zero() { return 0 @@ -86,7 +84,7 @@ where // let's compute current slot number let current_block_number = SystemPallet::::block_number(); - let slot = current_block_number / slot_length; + let slot = current_block_number.saturating_sub(*lane_relayers.enacted_at()) / slot_length; // and then get the relayer for that slot let slot_relayer = match usize::try_from(slot % lane_relayers_len) { @@ -267,8 +265,9 @@ mod integrity_tests { #[cfg(test)] mod tests { use super::*; - use crate::{mock::*, LaneRelayers}; - use bp_relayers::LaneRelayersSet; + use crate::{mock::*, ActiveLaneRelayers}; + use bp_relayers::{ActiveLaneRelayersSet, NextLaneRelayersSet}; + use sp_runtime::traits::ConstU32; #[test] fn compute_per_lane_priority_boost_works() { @@ -278,16 +277,18 @@ mod tests { let relayer1 = 100; let relayer2 = 200; let relayer3 = 300; - let mut relayers_set = LaneRelayersSet::empty(0); - assert!(relayers_set.next_set_try_push(relayer1, 0)); - assert!(relayers_set.next_set_try_push(relayer2, 0)); - assert!(relayers_set.next_set_try_push(relayer3, 0)); - relayers_set.activate_next_set(0); - LaneRelayers::::insert(lane_id, relayers_set); - - // at blocks 1..=SlotLength relayer1 gets the boost - System::set_block_number(0); - for _ in 1..SlotLength::get() { + let mut next_set: NextLaneRelayersSet<_, _, ConstU32<3>> = + NextLaneRelayersSet::empty(5); + assert!(next_set.try_push(relayer1, 0)); + assert!(next_set.try_push(relayer2, 0)); + assert!(next_set.try_push(relayer3, 0)); + let mut active_set = ActiveLaneRelayersSet::default(); + active_set.activate_next_set(7, next_set, |_| true); + ActiveLaneRelayers::::insert(lane_id, active_set); + + // at blocks 7..=7+SlotLength relayer1 gets the boost + System::set_block_number(6); + for _ in 7..SlotLength::get() + 7 { System::set_block_number(System::block_number() + 1); assert_eq!( compute_per_lane_priority_boost::(lane_id, &relayer1), diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index c81e36eea0..fb06611e8a 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -22,8 +22,8 @@ use bp_messages::LaneId; use bp_relayers::{ - LaneRelayersSet, PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardAtSource, - RewardsAccountParams, StakeAndSlash, + ActiveLaneRelayersSet, NextLaneRelayersSet, PaymentProcedure, Registration, + RelayerRewardsKeyProvider, RewardAtSource, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; @@ -283,33 +283,33 @@ pub mod pallet { Error::::InvalidRegistrationLease ); - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - // registration must have been created by the `increase_stake` before - let mut registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; + // registration must have been created by the `increase_stake` before + let mut registration = match Self::registered_relayer(&relayer) { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; - // ensure that the stake is enough - ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); + // ensure that the stake is enough + ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); - // new `valid_till` must be larger (or equal) than the old one - ensure!( - valid_till >= registration.valid_till_ignore_lanes(), - Error::::CannotReduceRegistrationLease, - ); - registration.set_valid_till(valid_till); + // new `valid_till` must be larger (or equal) than the old one + ensure!( + valid_till >= registration.valid_till_ignore_lanes(), + Error::::CannotReduceRegistrationLease, + ); + registration.set_valid_till(valid_till); - log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); - Self::deposit_event(Event::::RegistrationUpdated { - relayer: relayer.clone(), - registration: registration.clone(), - }); + // deposit event + log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); + Self::deposit_event(Event::::RegistrationUpdated { + relayer: relayer.clone(), + registration: registration.clone(), + }); - *maybe_registration = Some(registration); + // update registration in the runtime storage + RegisteredRelayers::::insert(relayer, registration); - Ok(()) - }) + Ok(()) } /// `Deregister` relayer. @@ -325,32 +325,33 @@ pub mod pallet { pub fn deregister(origin: OriginFor) -> DispatchResult { let relayer = ensure_signed(origin)?; - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; + // only registered relayers can deregister + let registration = match Self::registered_relayer(&relayer) { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; - // we can't deregister until `valid_till + 1` block and while relayer has active - // lane registerations - ensure!( - registration - .valid_till() - .map(|valid_till| valid_till < frame_system::Pallet::::block_number()) - .unwrap_or(false), - Error::::RegistrationIsStillActive, - ); + // we can't deregister until `valid_till + 1` block and while relayer has active + // lane registerations + ensure!( + registration + .valid_till() + .map(|valid_till| valid_till < frame_system::Pallet::::block_number()) + .unwrap_or(false), + Error::::RegistrationIsStillActive, + ); - // if stake is non-zero, we should do unreserve - Self::update_relayer_stake(&relayer, registration.current_stake(), Zero::zero())?; + // if stake is non-zero, we should do unreserve + Self::update_relayer_stake(&relayer, registration.current_stake(), Zero::zero())?; - log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); - Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); + // deposit event + log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); + Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); - *maybe_registration = None; + // update runtime storage + RegisteredRelayers::::remove(&relayer); - Ok(()) - }) + Ok(()) } /// Register relayer intention to deliver inbound messages at given messages lane. @@ -382,57 +383,44 @@ pub mod pallet { ) -> DispatchResult { let relayer = ensure_signed(origin)?; - RegisteredRelayers::::try_mutate( - &relayer.clone(), - move |maybe_registration| -> DispatchResult { - // we only allow registered relayers to have priority boosts - let mut registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; - - // cannot add another lane registration if relayer has already max allowed - // lane registrations - ensure!( - registration.register_at_lane(lane), - Error::::FailedToRegisterAtLane - ); - - // ensure that the relayer stake is enough - ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); - - // let's try to claim a slot in the next set - LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { - let mut lane_relayers = match maybe_lane_relayers.take() { - Some(lane_relayers) => lane_relayers, - None => LaneRelayersSet::empty( - SystemPallet::::block_number() - .saturating_add(T::InitialElectionLength::get()), - ), - }; + // we only allow registered relayers to have priority boosts + let mut registration = match Self::registered_relayer(&relayer) { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; - ensure!( - lane_relayers.next_set_try_push(relayer.clone(), expected_reward), - Error::::TooLargeRewardToOccupyAnEntry, - ); + // cannot add another lane registration if relayer has already max allowed + // lane registrations + ensure!(registration.register_at_lane(lane), Error::::FailedToRegisterAtLane); - *maybe_lane_relayers = Some(lane_relayers); + // ensure that the relayer stake is enough + ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); - Ok::<_, Error>(()) - })?; + // read or create next lane relayers + let mut next_lane_relayers = match NextLaneRelayers::::get(lane) { + Some(lane_relayers) => lane_relayers, + None => NextLaneRelayersSet::empty( + SystemPallet::::block_number() + .saturating_add(T::InitialElectionLength::get()), + ), + }; - // the relayer need to stake additional amount for every additional lane - registration.set_stake(Self::update_relayer_stake( - &relayer, - registration.current_stake(), - registration.required_stake(Self::base_stake(), Self::stake_per_lane()), - )?); + // try to push relayer to the next set + ensure!( + next_lane_relayers.try_push(relayer.clone(), expected_reward), + Error::::TooLargeRewardToOccupyAnEntry, + ); - *maybe_registration = Some(registration); + // the relayer need to stake additional amount for every additional lane + registration.set_stake(Self::update_relayer_stake( + &relayer, + registration.current_stake(), + registration.required_stake(Self::base_stake(), Self::stake_per_lane()), + )?); - Ok(()) - }, - )?; + // update basic and lane registration in the runtime storage + RegisteredRelayers::::insert(&relayer, registration); + NextLaneRelayers::::insert(lane, next_lane_relayers); Ok(()) } @@ -452,46 +440,29 @@ pub mod pallet { pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { let relayer = ensure_signed(origin)?; - RegisteredRelayers::::try_mutate( - &relayer.clone(), - move |maybe_registration| -> DispatchResult { - // if relayer doesn't have a basic registration, we know that he is not - // registered at the lane as well - let mut registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; - - // remove relayer from the `next_set` of lane relayers. Relayer still remains - // in the active set until current epoch ends - LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { - let mut lane_relayers = match maybe_lane_relayers.take() { - Some(lane_relayers) => lane_relayers, - None => fail!(Error::::NotRegisteredAtLane), - }; - - // remove relayer from the next set if it is there - lane_relayers.next_set_try_remove(&relayer); - - // if relayer is still in the active set, we can't simply remove this lane - // registration - it needs to keep additional stake until current epoch is - // finished. So we will make it later, in the `advance_lane_epoch` call - let is_in_active_set = - lane_relayers.relayer_from_active_set(&relayer).is_some(); - if !is_in_active_set { - Self::remove_lane_from_relayer_registration(&mut registration, lane); - } - - *maybe_lane_relayers = Some(lane_relayers); - - Ok::<_, Error>(()) - })?; - - *maybe_registration = Some(registration); + // if relayer is in the active set, we can not simply remove lane registration and let + // him unreserve some portion of his stake. Instead, we remember the fact that he has + // removed lane registration recently and once lane epoch advances + let is_in_active_set = Self::active_lane_relayers(lane).relayer(&relayer).is_some(); + if !is_in_active_set { + // if relayer doesn't have a basic registration, we know that he is not + // registered at the lane as well + let mut registration = match Self::registered_relayer(&relayer) { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; - Ok(()) - }, - )?; + // forget lane registration + Self::remove_lane_from_relayer_registration(&mut registration, lane, false); + + // update registration in the runtime storage + RegisteredRelayers::::insert(&relayer, registration); + } + + // remove relayer from the `next_set` of lane relayers + NextLaneRelayers::::mutate_extant(lane, |next_lane_relayers| { + next_lane_relayers.try_remove(&relayer); + }); Ok(()) } @@ -510,54 +481,57 @@ pub mod pallet { pub fn advance_lane_epoch(origin: OriginFor, lane: LaneId) -> DispatchResult { let _ = ensure_signed(origin)?; - // remove relayer from the `next_set` of lane relayers. So relayer is still - LaneRelayers::::try_mutate(lane, |maybe_lane_relayers| { - let mut lane_relayers = match maybe_lane_relayers.take() { - Some(lane_relayers) => lane_relayers, - None => fail!(Error::::NoRelayersAtLane), - }; + let current_block_number = SystemPallet::::block_number(); + let mut active_lane_relayers = Self::active_lane_relayers(lane); + let mut next_lane_relayers = match Self::next_lane_relayers(lane) { + Some(lane_relayers) => lane_relayers, + None => fail!(Error::::NoRelayersAtLane), + }; - // ensure that the current block number allows us to enact next set - let current_block_number = SystemPallet::::block_number(); - ensure!( - lane_relayers.next_set_may_enact_at() <= current_block_number, - Error::::TooEarlyToActivateNextRelayersSet, - ); - - // activate next set of relayers - let old_active_set = lane_relayers - .active_relayers() - .iter() - .map(|r| r.relayer().clone()) - .collect::>(); - let new_next_set_may_enact_at = - current_block_number.saturating_add(T::EpochLength::get()); - lane_relayers.activate_next_set(new_next_set_may_enact_at); - - // for every relayer, who was in the active set, but is missing from the new active - // set, check if we need to remove lane registration - for old_relayer in old_active_set { - if lane_relayers.relayer_from_active_set(&old_relayer).is_some() { - continue - } - - let _ = RegisteredRelayers::::try_mutate(old_relayer, |registration| { - if let Some(registration) = registration { - if Self::remove_lane_from_relayer_registration(registration, lane) { - Ok(()) - } else { - Err(()) - } - } else { - Err(()) - } - }); + // TODO: the same `Self::registered_relayer(relayer).map(|reg| + // reg.lanes().contains(&lane))` is called in `activate_next_set` and later in `for + // old_relayer in old_active_set`. May dedup to decrease weight + + // activate next set of relayers + let old_active_set = active_lane_relayers + .relayers() + .iter() + .map(|r| r.relayer().clone()) + .collect::>(); + ensure!( + active_lane_relayers.activate_next_set( + current_block_number, + next_lane_relayers.clone(), + |relayer| Self::registered_relayer(relayer) + .map(|reg| reg.lanes().contains(&lane)) + .unwrap_or(false), + ), + Error::::TooEarlyToActivateNextRelayersSet, + ); + + // update new epoch end in the next set + next_lane_relayers + .set_may_enact_at(current_block_number.saturating_add(T::EpochLength::get())); + + // for every relayer, who was in the active set, but is missing from the next + // set, remove lane registration + // + // technically, this is incorrect, because relaye may have wanted to keep lane + // registration. But there's no difference between such state and state when the relayer + // has deregistered + for old_relayer in old_active_set { + if next_lane_relayers.relayer(&old_relayer).is_some() { + continue } - *maybe_lane_relayers = Some(lane_relayers); + RegisteredRelayers::::mutate_extant(&old_relayer, |registration| { + Self::remove_lane_from_relayer_registration(registration, lane, true); + }); + } - Ok::<_, Error>(()) - })?; + // update relayer sets in the storage + ActiveLaneRelayers::::insert(lane, active_lane_relayers); + NextLaneRelayers::::insert(lane, next_lane_relayers); Ok(()) } @@ -721,12 +695,18 @@ pub mod pallet { fn remove_lane_from_relayer_registration( registration: &mut Registration, T::Reward, T::MaxLanesPerRelayer>, lane: LaneId, + was_in_active_set: bool, ) -> bool { // if we are already removed from the if !registration.deregister_at_lane(lane) { return false } + // if relayer was not in the active set, we don't need to prolong his registration + if !was_in_active_set { + return true + } + // since relayer may get priority boost for transactions, verified at **this** block, we // require its registration for aty least another `required_registration_lease` blocks. // We do not need to do that if relayer still have other lane registrations - the valid @@ -846,8 +826,6 @@ pub mod pallet { /// The expected reward, specified by relayer during `register_at_lane` call is too large /// to occupy an entry in the next relayer set. TooLargeRewardToOccupyAnEntry, - /// The relayer is not registered at given lane. - NotRegisteredAtLane, /// Lane has no relayers set. NoRelayersAtLane, /// Next set of lane relayers cannot be activated now. It can be activated later, once @@ -886,28 +864,33 @@ pub mod pallet { OptionQuery, >; - // TODO: split active set and `LaneRelayers`! Active set is read at every delivery transaction - // and other fields we need only in pallet calls. - - // TODO: make it ValueQuery? After it is created, it is never removed. But it is not default - - /// A set of relayers that have explicitly registered themselves at a given lane. + /// An active set of relayers that have explicitly registered themselves at a given lane. /// /// Every relayer inside this set receives additional priority boost when it submits /// message delivers messages at given lane. The boost only happens inside the slot, /// assigned to relayer. #[pallet::storage] - #[pallet::getter(fn lane_relayers)] - pub type LaneRelayers = StorageMap< + #[pallet::getter(fn active_lane_relayers)] + pub type ActiveLaneRelayers = StorageMap< _, Identity, LaneId, - LaneRelayersSet< - T::AccountId, - BlockNumberFor, - T::MaxActiveRelayersPerLane, - T::MaxNextRelayersPerLane, - >, + ActiveLaneRelayersSet, T::MaxActiveRelayersPerLane>, + ValueQuery, + >; + + // TODO: make it ValueQuery? After it is created, it is never removed. But it is not default + + /// A next set of relayers that have explicitly registered themselves at a given lane. + /// + /// This set may replace the [`ActiveLaneRelayers`] after current epoch ends. + #[pallet::storage] + #[pallet::getter(fn next_lane_relayers)] + pub type NextLaneRelayers = StorageMap< + _, + Identity, + LaneId, + NextLaneRelayersSet, T::MaxNextRelayersPerLane>, OptionQuery, >; } @@ -925,7 +908,7 @@ mod tests { traits::fungible::{Inspect, Mutate}, }; use frame_system::{EventRecord, Pallet as System, Phase}; - use sp_runtime::DispatchError; + use sp_runtime::{traits::ConstU32, DispatchError}; fn get_ready_for_events() { System::::set_block_number(1); @@ -1503,15 +1486,15 @@ mod tests { #[test] fn register_at_lane_fails_if_relayer_requests_too_large_reward_to_claim_the_slot() { run_test(|| { - let mut lane_relayers = LaneRelayersSet::empty(100); + let mut lane_relayers = NextLaneRelayersSet::empty(100); for i in 1..=MAX_NEXT_RELAYERS_PER_LANE as u64 { - assert!(lane_relayers.next_set_try_push(REGISTER_RELAYER + i, 0)); + assert!(lane_relayers.try_push(REGISTER_RELAYER + i, 0)); } RegisteredRelayers::::insert( REGISTER_RELAYER, registration(151, Stake::get() + LaneStake::get()), ); - LaneRelayers::::insert(test_lane_id(), lane_relayers); + NextLaneRelayers::::insert(test_lane_id(), lane_relayers); assert_noop!( BridgeRelayers::register_at_lane( @@ -1562,11 +1545,12 @@ mod tests { test_lane_id(), 1 )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); - assert_eq!(lane_relayers.active_relayers(), &[]); + let active_lane_relayers = BridgeRelayers::active_lane_relayers(test_lane_id()); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(active_lane_relayers.relayers(), &[]); + assert_eq!(next_lane_relayers.may_enact_at(), InitialElectionLength::get()); assert_eq!( - lane_relayers.next_relayers(), + next_lane_relayers.relayers(), &[RelayerAndReward::new(REGISTER_RELAYER, 1)] ); @@ -1576,11 +1560,12 @@ mod tests { test_lane_id(), 0 )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); - assert_eq!(lane_relayers.active_relayers(), &[]); + let active_lane_relayers = BridgeRelayers::active_lane_relayers(test_lane_id()); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(active_lane_relayers.relayers(), &[]); + assert_eq!(next_lane_relayers.may_enact_at(), InitialElectionLength::get()); assert_eq!( - lane_relayers.next_relayers(), + next_lane_relayers.relayers(), &[ RelayerAndReward::new(REGISTER_RELAYER_2, 0), RelayerAndReward::new(REGISTER_RELAYER, 1) @@ -1603,9 +1588,9 @@ mod tests { test_lane_id(), 1 )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); assert_eq!( - lane_relayers.next_relayers(), + next_lane_relayers.relayers(), &[RelayerAndReward::new(REGISTER_RELAYER, 1)] ); @@ -1615,9 +1600,9 @@ mod tests { test_lane_id(), 0 )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); assert_eq!( - lane_relayers.next_relayers(), + next_lane_relayers.relayers(), &[RelayerAndReward::new(REGISTER_RELAYER, 0)] ); }); @@ -1627,9 +1612,9 @@ mod tests { fn relayer_still_has_lane_registration_after_he_is_pushed_out_of_next_set() { run_test(|| { // leave one free entry in next set by relayers with bid = 10 - let mut lane_relayers = LaneRelayersSet::empty(100); + let mut lane_relayers = NextLaneRelayersSet::empty(100); for i in 1..MAX_NEXT_RELAYERS_PER_LANE as u64 { - assert!(lane_relayers.next_set_try_push(REGISTER_RELAYER + 100 + i, 10)); + assert!(lane_relayers.try_push(REGISTER_RELAYER + 100 + i, 10)); } RegisteredRelayers::::insert( REGISTER_RELAYER, @@ -1639,7 +1624,7 @@ mod tests { REGISTER_RELAYER_2, registration(151, Stake::get() + LaneStake::get()), ); - LaneRelayers::::insert(test_lane_id(), lane_relayers); + NextLaneRelayers::::insert(test_lane_id(), lane_relayers); // occupy last entry by `REGISTER_RELAYER` with bid = 15 assert_ok!(BridgeRelayers::register_at_lane( @@ -1647,10 +1632,10 @@ mod tests { test_lane_id(), 15 ),); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); assert_eq!( - lane_relayers.next_relayers().last(), + next_lane_relayers.relayers().last(), Some(&RelayerAndReward::new(REGISTER_RELAYER, 15)) ); @@ -1660,10 +1645,10 @@ mod tests { test_lane_id(), 14 ),); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); assert_eq!( - lane_relayers.next_relayers().last(), + next_lane_relayers.relayers().last(), Some(&RelayerAndReward::new(REGISTER_RELAYER_2, 14)) ); @@ -1676,10 +1661,10 @@ mod tests { 13 ),); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.relayers().len() as u32, MAX_NEXT_RELAYERS_PER_LANE); assert_eq!( - lane_relayers.next_relayers().last(), + next_lane_relayers.relayers().last(), Some(&RelayerAndReward::new(REGISTER_RELAYER, 13)) ); }); @@ -1699,181 +1684,136 @@ mod tests { } #[test] - fn deregister_at_lane_fails_if_theres_no_lane_registration() { - run_test(|| { - RegisteredRelayers::::insert( - REGISTER_RELAYER, - registration(151, Stake::get() + LaneStake::get()), - ); - - assert_noop!( - BridgeRelayers::deregister_at_lane( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id(), - ), - Error::::NotRegisteredAtLane, - ); - }) - } - - #[test] - fn deregister_at_lane_fails_if_lane_relayers_are_missing() { + fn deregister_at_lane_does_not_fail_if_next_lane_relayers_are_missing() { run_test(|| { let mut registration = registration(151, Stake::get() + LaneStake::get()); registration.register_at_lane(test_lane_id()); RegisteredRelayers::::insert(REGISTER_RELAYER, registration); - assert_noop!( - BridgeRelayers::deregister_at_lane( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id(), - ), - Error::::NotRegisteredAtLane, - ); - }) - } - - #[test] - fn deregister_at_lane_works() { - run_test(|| { - let initial_valid_till = 151 + EpochLength::get(); - let registration = registration(initial_valid_till, Stake::get() + LaneStake::get()); - System::::set_block_number(100); - RegisteredRelayers::::insert(REGISTER_RELAYER, registration); - - // when the relayer is not in the active set, `valid_till` is not changed - assert_ok!(BridgeRelayers::register_at_lane( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id(), - 0 - )); + // when relayer is not in the active set assert_ok!(BridgeRelayers::deregister_at_lane( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() - )); - assert!(BridgeRelayers::registered_relayer(®ISTER_RELAYER) - .unwrap() - .lanes() - .is_empty()); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_relayers().len(), 0); - - // when the relayer is in the active set BUT current block + required lease is lte than - // the `valid_till`, `valid_till` is not changed - assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), - 0 - )); - System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(BridgeRelayers::advance_lane_epoch( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.active_relayers().len(), 1); - assert_eq!(lane_relayers.next_relayers().len(), 0); - System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(BridgeRelayers::advance_lane_epoch( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() + + // when relayer is in the active set + let mut active_lane_relayers = ActiveLaneRelayersSet::default(); + assert!(active_lane_relayers.activate_next_set( + 0, + { + let mut next_lane_relayers: NextLaneRelayersSet<_, _, ConstU32<1>> = + NextLaneRelayersSet::empty(0); + assert!(next_lane_relayers.try_push(REGISTER_RELAYER, 0)); + next_lane_relayers + }, + |_| true )); + ActiveLaneRelayers::::insert(test_lane_id(), active_lane_relayers); assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() + test_lane_id(), )); - assert!(BridgeRelayers::registered_relayer(®ISTER_RELAYER) - .unwrap() - .lanes() - .is_empty()); - assert_eq!( - BridgeRelayers::registered_relayer(®ISTER_RELAYER).unwrap().valid_till(), - Some(initial_valid_till) + }) + } + + #[test] + fn deregister_at_lane_works_when_relayer_is_not_in_active_set() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get() + LaneStake::get()), ); - // when the relayers is in the active set AND current block + required lease is gt than - // the `valid_till`, `valid_till` is changed + // register at lane assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(BridgeRelayers::advance_lane_epoch( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() - )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.active_relayers().len(), 1); - assert_eq!(lane_relayers.next_relayers().len(), 0); - System::::set_block_number(lane_relayers.next_set_may_enact_at()); - assert_ok!(BridgeRelayers::advance_lane_epoch( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() - )); + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().valid_till(), + None + ); + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER) + .unwrap() + .valid_till_ignore_lanes(), + 150 + ); + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().lanes(), + &[test_lane_id()] + ); + assert_eq!(BridgeRelayers::active_lane_relayers(test_lane_id()).relayers(), &[]); + assert_eq!( + BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().relayers(), + &[RelayerAndReward::new(REGISTER_RELAYER, 0)] + ); + + // and then deregister at lane before going into active set + System::::set_block_number(150); assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); - assert!(BridgeRelayers::registered_relayer(®ISTER_RELAYER) - .unwrap() - .lanes() - .is_empty()); - assert!( - BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().valid_till() > - Some(initial_valid_till) + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().valid_till(), + Some(150) ); + assert_eq!(BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().lanes(), &[]); + assert_eq!(BridgeRelayers::active_lane_relayers(test_lane_id()).relayers(), &[]); + assert_eq!(BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().relayers(), &[]); }); } #[test] - fn deregister_at_lane_does_not_remove_lane_from_registration_if_relayer_is_in_active_set() { + fn deregister_at_lane_works_when_relayer_is_in_active_set() { run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get() + LaneStake::get()), + ); + // register at lane - assert_ok!(BridgeRelayers::increase_stake( - RuntimeOrigin::signed(REGISTER_RELAYER), - Stake::get() + LaneStake::get() - )); - assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 100)); assert_ok!(BridgeRelayers::register_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id(), 0 )); - // advance lane epoch => relayer is in the active set + // activate next lane epoch System::::set_block_number( - BridgeRelayers::lane_relayers(test_lane_id()).unwrap().next_set_may_enact_at(), + BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().may_enact_at(), ); assert_ok!(BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() + test_lane_id(), )); - // deregister at lane => lane registration is not removed immeditely, it will be - // removed later, during next `advance_lane_epoch` call + // and then deregister assert_ok!(BridgeRelayers::deregister_at_lane( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); assert_eq!( - BridgeRelayers::registered_relayer(®ISTER_RELAYER).unwrap().lanes(), - &[test_lane_id()] + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().valid_till(), + None ); - - // call `advance_lane_epoch` and ensure that the lane registraion is actually removed - System::::set_block_number( - BridgeRelayers::lane_relayers(test_lane_id()).unwrap().next_set_may_enact_at(), + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER) + .unwrap() + .valid_till_ignore_lanes(), + 150 ); - assert_ok!(BridgeRelayers::advance_lane_epoch( - RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() - )); assert_eq!( - BridgeRelayers::registered_relayer(®ISTER_RELAYER).unwrap().lanes(), + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().lanes(), &[test_lane_id()] ); + assert_eq!( + BridgeRelayers::active_lane_relayers(test_lane_id()).relayers(), + &[RelayerAndReward::new(REGISTER_RELAYER, 0)] + ); + assert_eq!(BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().relayers(), &[]); }); } @@ -1938,22 +1878,74 @@ mod tests { 0, )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); - assert_eq!(lane_relayers.next_set_may_enact_at(), InitialElectionLength::get()); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.may_enact_at(), InitialElectionLength::get()); // when active epoch is advanced, new epoch starts at the block, where it has been // actually started, not the epoch where previous epoch was supposed to end - System::::set_block_number(lane_relayers.next_set_may_enact_at() + 77); + System::::set_block_number(next_lane_relayers.may_enact_at() + 77); assert_ok!(BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), test_lane_id() )); - let lane_relayers = BridgeRelayers::lane_relayers(test_lane_id()).unwrap(); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); assert_eq!( - lane_relayers.next_set_may_enact_at(), + next_lane_relayers.may_enact_at(), InitialElectionLength::get() + 77 + EpochLength::get() ); }); } + + #[test] + fn advance_lane_epoch_removes_dangling_lane_registrations() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get() + LaneStake::get()), + ); + + // register at lane + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + + // activate next lane epoch + System::::set_block_number( + BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().may_enact_at(), + ); + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + )); + + // and then deregister + assert_ok!(BridgeRelayers::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id() + )); + + // when the next epoch is activated, the lane registration is removed + registration is + // prolonged + let current_block_number = + BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().may_enact_at(); + System::::set_block_number(current_block_number); + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + )); + + // since relayer registration should have ended before, it is prolonged by + // `required_registration_lease` blocks + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().valid_till(), + Some(current_block_number + Lease::get()) + ); + assert_eq!(BridgeRelayers::registered_relayer(REGISTER_RELAYER).unwrap().lanes(), &[]); + assert_eq!(BridgeRelayers::active_lane_relayers(test_lane_id()).relayers(), &[]); + assert_eq!(BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().relayers(), &[]); + }); + } } diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 4a6464aa61..7bc91ce166 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -16,7 +16,7 @@ //! Code that allows relayers pallet to be used as a payment mechanism for the messages pallet. -use crate::{Config, LaneRelayers, Pallet}; +use crate::{ActiveLaneRelayers, Config, Pallet}; use bp_messages::{ source_chain::{DeliveryConfirmationPayments, RelayersRewardsAtSource}, @@ -105,36 +105,9 @@ where return } - let _ = LaneRelayers::::try_mutate(lane_id, |maybe_lane_relayers| { - if let Some(lane_relayers) = maybe_lane_relayers { - // if relayer is NOT in the active set, we don't want to do anything here - let relayer_in_active_set = lane_relayers.relayer_from_active_set(&relayer); - let relayer_in_active_set = match relayer_in_active_set { - Some(relayer_in_active_set) => relayer_in_active_set, - None => return Err(()), - }; - - // if relayer is already in the active set, we don't want to do anything here - let is_in_next_set = lane_relayers.relayer_from_next_set(&relayer).is_some(); - if is_in_next_set { - return Err(()) - } - - // if relayer is not willing to work on that lane anymore, we don't want to do - // anything here - let wants_to_work_on_lane = Pallet::::registered_relayer(&relayer) - .map(|registration| registration.lanes().contains(&lane_id)) - .unwrap_or(false); - if wants_to_work_on_lane { - return Err(()) - } - - if !lane_relayers.next_set_try_push(relayer, relayer_in_active_set.reward()) { - return Err(()) - } - } - - Ok(()) + // remember that the relayer has delivered messages + ActiveLaneRelayers::::mutate_extant(lane_id, |active_lane_relayers| { + active_lane_relayers.note_delivered_message(&relayer); }); } } diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 32e9c171ea..f8e89e09c8 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -19,10 +19,11 @@ pub use bp_messages::RewardAtSource; use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::CloneNoBound; use scale_info::TypeInfo; use sp_runtime::{ traits::{Get, Zero}, - BoundedVec, RuntimeDebug, + BoundedBTreeSet, BoundedVec, RuntimeDebug, }; /// A relayer and the reward that it wants to receive for delivering a single message. @@ -57,9 +58,143 @@ impl RelayerAndReward { /// message delivers messages at given lane. The boost only happens inside the slot, /// assigned to relayer. /// -/// The set is required to change periodically (at `next_set_may_enact_at`). An interval, when -/// the same relayers set is active is called epoch. Every relayer in the epoch is guaranteed -/// to have at least one slot, but epochs may have differrent lengths. +/// The active set will eventually be replaced with the [`NextLaneRelayersSet`]. Before +/// replacing, all relayers from the active set, who have delivered at least one messsage +/// at passed epoch, are reinserted into the next set. So if lane is active and relayers +/// area actually delivering messages, they can only be replaced by the relayers, offering +/// lower expected reward. +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxActiveRelayersPerLane))] +pub struct ActiveLaneRelayersSet> { + /// Number of block, where the active set has been enacted. + enacted_at: BlockNumber, + /// An active set of lane relayers. + /// + /// It is a circular queue. Every relayer in the queue is assigned the slot (fixed number + /// of blocks), starting from [`Self::enacted_at`]. Once the slot of last relayer ends, + /// next slot will be assigned to the first relayer and so on. + active_set: BoundedVec, MaxActiveRelayersPerLane>, + /// Relayers that have delivered at least one message in current epoch. + /// + /// This subset of the [`Self::active_set`] will be merged with the next set right before + /// lane epoch is advanced. Relayers that have deregistered from lane at current epoch, won't + /// be merged, though. + mergeable_set: BoundedBTreeSet, +} + +impl> Default + for ActiveLaneRelayersSet +{ + fn default() -> Self { + ActiveLaneRelayersSet { + enacted_at: Zero::zero(), + active_set: BoundedVec::new(), + mergeable_set: BoundedBTreeSet::new(), + } + } +} + +impl + ActiveLaneRelayersSet +where + AccountId: Clone + Ord, + BlockNumber: Copy, + MaxActiveRelayersPerLane: Get, +{ + /// Returns block, where this set has been enacted. + pub fn enacted_at(&self) -> &BlockNumber { + &self.enacted_at + } + + /// Returns relayer entry from the active set. + pub fn relayer(&self, relayer: &AccountId) -> Option<&RelayerAndReward> { + self.active_set.iter().filter(|r| r.relayer() == relayer).next() + } + + /// Returns relayers from the active set. + pub fn relayers(&self) -> &[RelayerAndReward] { + self.active_set.as_slice() + } + + /// Note message, delivered by given relayer. + /// + /// Returns true if we have updated anything in the structure. + pub fn note_delivered_message(&mut self, relayer: &AccountId) -> bool { + // TODO: add tests for that + + if !self.relayer(relayer).is_some() { + return false + } + + self.mergeable_set.try_insert(relayer.clone()).unwrap_or(false) + } + + /// Activate next set of relayers. + /// + /// The [`Self::active_set`] is replaced with the [`next_set`]. + /// + /// Returns false if `current_block` is lesser than the block where [`next_set`] may be enacted + pub fn activate_next_set>( + &mut self, + current_block: BlockNumber, + mut next_set: NextLaneRelayersSet, + is_lane_registration_active: impl Fn(&AccountId) -> bool, + ) -> bool + where + BlockNumber: Ord, + { + // ensure that we can enact the next set + if next_set.may_enact_at > current_block { + return false + } + + // merge mergeable relayers into next set + for relayer in &self.active_set { + // relayer has not delivered any new messages, do not merge + if !self.mergeable_set.contains(relayer.relayer()) { + continue + } + + // if relayer lane registration is no longer active, do not merge + if !is_lane_registration_active(relayer.relayer()) { + continue + } + + // TODO: add tests for that + + // else only push it to the next set if it is not yet there to avoid overwriting + // expected reward + let is_in_next_set = next_set.relayer(relayer.relayer()).is_some(); + if !is_in_next_set { + // we do not care if relayer stays in the set - we only need to try + let _ = next_set.try_push(relayer.relayer().clone(), relayer.reward()); + } + } + // clear active sets + self.active_set.clear(); + self.mergeable_set.clear(); + // ...and finally fill the active set with new best relayers + let relayers_in_active_set = + sp_std::cmp::min(MaxActiveRelayersPerLane::get(), next_set.relayers().len() as u32); + for _ in 0..relayers_in_active_set { + // we know that the next set has at least `relayers_in_active_set` + // => so calling `remove(0)` is safe + // we know that the active set is empty and we select at most `MaxActiveRelayersPerLane` + // relayers => ignoring `try_push` result is safe + let _ = self.active_set.try_push(next_set.next_set.remove(0)); + } + // finally - remember block where we have activated the set + self.enacted_at = current_block; + + true + } +} + +/// A set of relayers that will become active at next lane epoch. +/// +/// The active set of lane relayers is required to change periodically (at `next_set_may_enact_at`). +/// An interval, when the same relayers set is active is called epoch. Every relayer in the epoch +/// is guaranteed to have at least one slot, but epochs may have different lengths. /// /// We change the set to guarantee that inactive relayers are removed from the set eventually /// and are replaced by active relayers. The relayer will be scheduled for autoremoval if it @@ -68,27 +203,18 @@ impl RelayerAndReward { /// Relayers are bargaining for the place in the set by offering lower reward for delivering /// messages. Relayer, which agress to get a lower reward will likely to replace a "more greedy" /// relayer in the [`Self::next_set`]. -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[scale_info(skip_type_params(MaxActiveRelayersPerLane, MaxNextRelayersPerLane))] -pub struct LaneRelayersSet< - AccountId, - BlockNumber, - MaxActiveRelayersPerLane: Get, +#[derive(CloneNoBound, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxNextRelayersPerLane))] +pub struct NextLaneRelayersSet< + AccountId: Clone, + BlockNumber: Clone, MaxNextRelayersPerLane: Get, > { - /// Number of block, where the active set has been enacted. - enacted_at: BlockNumber, /// Number of block, where the active set may be replaced with the [`Self::next_set`]. /// - /// We do not allow immediate changes of the [`Self::next_set`], because relayers - /// may change it so that they are always assigned the current slot. - next_set_may_enact_at: BlockNumber, - /// An active set of lane relayers. - /// - /// It is a circular queue. Every relayer in the queue is assigned the slot (fixed number - /// of blocks), starting from [`Self::enacted_at`]. Once the slot of last relayer ends, - /// next slot will be assigned to the first relayer and so on. - active_set: BoundedVec, MaxActiveRelayersPerLane>, + /// We do not allow immediate changes of the active set, because relayers + /// may change it so that they are always assigned at the current slot. + may_enact_at: BlockNumber, /// Next set of lane relayers. /// /// It is a bounded priority queue. Relayers that are working for larger reward are replaced @@ -96,61 +222,44 @@ pub struct LaneRelayersSet< next_set: BoundedVec, MaxNextRelayersPerLane>, } -impl - LaneRelayersSet +impl + NextLaneRelayersSet where AccountId: Clone + PartialOrd, - BlockNumber: Copy + Zero, - MaxActiveRelayersPerLane: Get, + BlockNumber: Copy, MaxNextRelayersPerLane: Get, { /// Creates new empty relayers set, where next sets enacts at given block. - pub fn empty(next_set_may_enact_at: BlockNumber) -> Self { - LaneRelayersSet { - enacted_at: Zero::zero(), - next_set_may_enact_at, - active_set: BoundedVec::new(), - next_set: BoundedVec::new(), - } + pub fn empty(may_enact_at: BlockNumber) -> Self { + NextLaneRelayersSet { may_enact_at, next_set: BoundedVec::new() } } /// Returns block, starting from which the [`Self::next_set`] may be enacted. - pub fn next_set_may_enact_at(&self) -> BlockNumber { - self.next_set_may_enact_at + pub fn may_enact_at(&self) -> BlockNumber { + self.may_enact_at } - /// Returns relayer entry from the active set. - pub fn relayer_from_active_set( - &self, - relayer: &AccountId, - ) -> Option<&RelayerAndReward> { - self.active_set.iter().filter(|r| r.relayer() == relayer).next() + /// Set block, starting from which the [`Self::next_set`] may be enacted. + pub fn set_may_enact_at(&mut self, may_enact_at: BlockNumber) { + self.may_enact_at = may_enact_at; } /// Returns relayer entry from the next set. - pub fn relayer_from_next_set( - &self, - relayer: &AccountId, - ) -> Option<&RelayerAndReward> { + pub fn relayer(&self, relayer: &AccountId) -> Option<&RelayerAndReward> { self.next_set.iter().filter(|r| r.relayer() == relayer).next() } - /// Returns relayers from the active set. - pub fn active_relayers(&self) -> &[RelayerAndReward] { - self.active_set.as_slice() - } - /// Returns relayers from the next set. - pub fn next_relayers(&self) -> &[RelayerAndReward] { + pub fn relayers(&self) -> &[RelayerAndReward] { self.next_set.as_slice() } /// Try insert relayer to the next set. /// /// Returns `true` if relayer has been added to the set and false otherwise. - pub fn next_set_try_push(&mut self, relayer: AccountId, reward: RewardAtSource) -> bool { + pub fn try_push(&mut self, relayer: AccountId, reward: RewardAtSource) -> bool { // first, remove existing entry for the same relayer from the set - self.next_set_try_remove(&relayer); + self.try_remove(&relayer); // now try to insert new entry into the queue self.next_set .force_insert_keep_left( @@ -163,42 +272,15 @@ where /// Try remove relayer from the next set. /// /// Returns `true` if relayer has been removed from the set. - pub fn next_set_try_remove(&mut self, relayer: &AccountId) -> bool { + pub fn try_remove(&mut self, relayer: &AccountId) -> bool { let len_before = self.next_set.len(); self.next_set.retain(|entry| entry.relayer != *relayer); self.next_set.len() != len_before } - /// Activate next set of relayers. - /// - /// The [`Self::active_set`] is replaced with the [`Self::next_set`]. - pub fn activate_next_set(&mut self, new_next_set_may_enact_at: BlockNumber) { - // move relayers from the next set to the active set - self.active_set.clear(); - let relayers_in_active_set = - sp_std::cmp::min(MaxActiveRelayersPerLane::get(), self.next_set.len() as u32); - for _ in 0..relayers_in_active_set { - // we know that the next set has at least `relayers_in_active_set` - // => so calling `remove(0)` is safe - // we know that the active set is empty and we select at most `MaxActiveRelayersPerLane` - // relayers => ignoring `try_push` result is safe - let _ = self.active_set.try_push(self.next_set.remove(0)); - } - - // since capacity of the `next_set` is larger than the capacity of the `active_set`, we may - // or may not remove relayers, that have not been advanced to the `active_set` from the - // new `next_set`. Current implementation removes them, which may be a bit harsh towards - // such relayers. But since we assume that most likely active relayers will reappear in the - // next set later (when they relay at least one message), there's no much point in keeping - // them here. - - // we clear next set here. Relayers from the active set will be readded here if - // they deliver at least one message in epoch and their reward will be concurrent. - // Or else, they'll need to reregister manually. - self.next_set.clear(); - self.next_set_may_enact_at = new_next_set_may_enact_at; - } - + /// Selects position to insert relayer, wanting to receive `reward` for every delivered + /// message. If there are multiple relayers with that reward, relayers that are already + /// in the set are prioritized above the new relayer. fn select_position_in_next_set(&self, reward: RewardAtSource) -> usize { // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it // may be used to push relayers our of the queue @@ -225,26 +307,145 @@ mod tests { const MAX_ACTIVE_LANE_RELAYERS: u32 = 2; const MAX_NEXT_LANE_RELAYERS: u32 = 4; - type TestLaneRelayersSet = LaneRelayersSet< - u64, - u64, - ConstU32, - ConstU32, - >; + type TestActiveLaneRelayersSet = + ActiveLaneRelayersSet>; + type TestNextLaneRelayersSet = NextLaneRelayersSet>; #[test] - fn next_set_try_push_works() { - let mut relayers: TestLaneRelayersSet = LaneRelayersSet { + fn active_set_activate_next_set_works() { + let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { enacted_at: 0, - next_set_may_enact_at: 100, active_set: vec![].try_into().unwrap(), - next_set: vec![].try_into().unwrap(), + mergeable_set: vec![].try_into().unwrap(), }; + let mut next_set: TestNextLaneRelayersSet = NextLaneRelayersSet { + may_enact_at: 100, + next_set: vec![ + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + RelayerAndReward::new(300, 12), + RelayerAndReward::new(400, 13), + ] + .try_into() + .unwrap(), + }; + + // when we can't yet activate next set, it returns false + assert!(!active_set.activate_next_set(0, next_set.clone(), |_| true)); + + // only two relayers are selected from the next set when the active set is empty + assert!(!active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!(active_set.enacted_at, 100); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + ]) + .unwrap(), + ); + assert_eq!( + active_set.mergeable_set, + BoundedVec::<_, ConstU32>::try_from(vec![]).unwrap() + ); + + // spam relayers are occupying the whole next set and then they leave in favor of some + // expensive relayers. At the same time, both relayers from the active set were delivering + // messages => active set is not changed + active_set.mergeable_set = active_set.active_set.clone(); + next_set.next_set = vec![ + RelayerAndReward::new(300, 1000), + RelayerAndReward::new(400, 1100), + RelayerAndReward::new(500, 1200), + RelayerAndReward::new(600, 1300), + ] + .try_into() + .unwrap(); + assert!(!active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + ]) + .unwrap(), + ); + + // better relayers appear in the next set + // => even if active relayers were delivering messages, they lose their slots + active_set.mergeable_set = active_set.active_set.clone(); + next_set.next_set = vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(800, 5), + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + ] + .try_into() + .unwrap(); + assert!(!active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(800, 5), + ]) + .unwrap(), + ); + + // one of active relayers deregisters => next epoch will start without it + active_set.mergeable_set = active_set.active_set.clone(); + next_set.next_set = vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + RelayerAndReward::new(300, 1000), + ] + .try_into() + .unwrap(); + assert!(!active_set.activate_next_set(100, next_set.clone(), |relayer| *relayer != 800)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(100, 10), + ]) + .unwrap(), + ); + + // if relayer is in the next set already, we do not remerge it + active_set.mergeable_set = active_set.active_set.clone(); + next_set.next_set = vec![RelayerAndReward::new(700, 100), RelayerAndReward::new(100, 200)] + .try_into() + .unwrap(); + assert!(!active_set.activate_next_set(100, next_set.clone(), |relayer| *relayer != 800)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(700, 100), + RelayerAndReward::new(100, 200), + ]) + .unwrap(), + ); + + // all relayers deregister themselves and no relayers have submitted any messages => new + // active set will be empty + next_set.next_set = vec![].try_into().unwrap(); + assert!(!active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![]).unwrap() + ); + } + + #[test] + fn next_set_try_push_works() { + let mut relayers: TestNextLaneRelayersSet = + NextLaneRelayersSet { may_enact_at: 100, next_set: vec![].try_into().unwrap() }; // first `MAX_NEXT_LANE_RELAYERS` are simply filling the set let max_next_lane_relayers: u64 = MAX_NEXT_LANE_RELAYERS as _; for i in 0..max_next_lane_relayers { - assert!(relayers.next_set_try_push(i, (max_next_lane_relayers - i) * 10)); + assert!(relayers.try_push(i, (max_next_lane_relayers - i) * 10)); } assert_eq!( relayers.next_set.as_slice(), @@ -258,7 +459,7 @@ mod tests { // try to insert relayer who wants reward, that is larger than anyone in the set // => the set is not changed - assert!(!relayers.next_set_try_push(4, 50)); + assert!(!relayers.try_push(4, 50)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -270,7 +471,7 @@ mod tests { ); // replace worst relayer in the set - assert!(relayers.next_set_try_push(5, 35)); + assert!(relayers.try_push(5, 35)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -282,7 +483,7 @@ mod tests { ); // insert best relayer to the set, pushing worst relayer out of set - assert!(relayers.next_set_try_push(6, 5)); + assert!(relayers.try_push(6, 5)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -294,7 +495,7 @@ mod tests { ); // insert best relayer to the set, pushing worst relayer out of set - assert!(relayers.next_set_try_push(6, 5)); + assert!(relayers.try_push(6, 5)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -306,7 +507,7 @@ mod tests { ); // insert relayer to the middle of the set, pushing worst relayer out of set - assert!(relayers.next_set_try_push(7, 15)); + assert!(relayers.try_push(7, 15)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -319,8 +520,8 @@ mod tests { // insert couple of relayer that want the same reward as some relayer in the middle of the // queue => they are inserted **after** existing relayers - assert!(relayers.next_set_try_push(8, 10)); - assert!(relayers.next_set_try_push(9, 10)); + assert!(relayers.try_push(8, 10)); + assert!(relayers.try_push(9, 10)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -332,7 +533,7 @@ mod tests { ); // insert next relayer, similar to previous => it isn't inserted - assert!(!relayers.next_set_try_push(10, 10)); + assert!(!relayers.try_push(10, 10)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -344,7 +545,7 @@ mod tests { ); // update expected reward of existing relayer => the set order is changed - assert!(relayers.next_set_try_push(8, 2)); + assert!(relayers.try_push(8, 2)); assert_eq!( relayers.next_set.as_slice(), &[ @@ -357,33 +558,12 @@ mod tests { } #[test] - fn next_set_try_push_works_edge_case_1() { - // all relayers have the same reward = 10 - let mut relayers: TestLaneRelayersSet = LaneRelayersSet { - enacted_at: 0, - next_set_may_enact_at: 100, - active_set: vec![].try_into().unwrap(), - next_set: (0..MAX_NEXT_LANE_RELAYERS - 1) - .map(|i| RelayerAndReward::new(i as u64, 10)) - .collect::>() - .try_into() - .unwrap(), - }; + fn next_set_try_remove_works() { + let mut relayers: TestNextLaneRelayersSet = + NextLaneRelayersSet { may_enact_at: 100, next_set: vec![].try_into().unwrap() }; - // then comes the next relayer with reward = 15 - assert!(relayers.next_set_try_push((MAX_NEXT_LANE_RELAYERS - 1) as u64, 15)); - assert_eq!(relayers.next_set.len(), MAX_NEXT_LANE_RELAYERS as usize); - assert_eq!( - relayers.next_set.last(), - Some(&RelayerAndReward::new((MAX_NEXT_LANE_RELAYERS - 1) as u64, 15)) - ); - - // then comes the next relayer with reward = 14 - assert!(relayers.next_set_try_push(MAX_NEXT_LANE_RELAYERS as u64, 14)); - assert_eq!(relayers.next_set.len(), MAX_NEXT_LANE_RELAYERS as usize); - assert_eq!( - relayers.next_set.last(), - Some(&RelayerAndReward::new(MAX_NEXT_LANE_RELAYERS as u64, 14)) - ); + assert!(relayers.try_push(1, 0)); + assert!(relayers.try_remove(&1)); + assert!(!relayers.try_remove(&1)); } } diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index cbddcabde9..d2bf92d8c0 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -23,7 +23,9 @@ pub use extension::{ BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig, RuntimeWithUtilityPallet, }; -pub use lane_relayers::{LaneRelayersSet, RelayerAndReward, RewardAtSource}; +pub use lane_relayers::{ + ActiveLaneRelayersSet, NextLaneRelayersSet, RelayerAndReward, RewardAtSource, +}; pub use registration::{Registration, StakeAndSlash}; use bp_messages::LaneId; From ce46ebc8e7f1033bd8e301d58263d34229bf048f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 11:24:04 +0300 Subject: [PATCH 37/60] removed TODO + added comment --- modules/relayers/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 9c1022d228..e615c0290b 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -462,6 +462,9 @@ pub mod pallet { // remove relayer from the `next_set` of lane relayers NextLaneRelayers::::mutate_extant(lane, |next_lane_relayers| { next_lane_relayers.try_remove(&relayer); + + // we can't remove `NextLaneRelayers` entry here (if there are no more relayers + // in the set), bnecause the `may_enact_at` is important too }); Ok(()) @@ -879,8 +882,6 @@ pub mod pallet { ValueQuery, >; - // TODO: make it ValueQuery? After it is created, it is never removed. But it is not default - /// A next set of relayers that have explicitly registered themselves at a given lane. /// /// This set may replace the [`ActiveLaneRelayers`] after current epoch ends. From 187fc1978b82a13dcad8334127a04abbb622dc18 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 11:33:47 +0300 Subject: [PATCH 38/60] intreoduce ActiveLaneRelayersSet and NextLaneRelayersSet --- modules/relayers/src/extension/priority.rs | 42 +- modules/relayers/src/lib.rs | 47 +- modules/relayers/src/mock.rs | 5 +- primitives/relayers/src/lane_relayers.rs | 601 +++++++++++++++++++++ primitives/relayers/src/lib.rs | 4 + 5 files changed, 674 insertions(+), 25 deletions(-) create mode 100644 primitives/relayers/src/lane_relayers.rs diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index 03e4849637..eaf1374aa7 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -30,6 +30,7 @@ use frame_system::{pallet_prelude::BlockNumberFor, Pallet as SystemPallet}; use sp_runtime::{ traits::{One, Zero}, transaction_validity::TransactionPriority, + Saturating, }; // reexport everything from `integrity_tests` module @@ -68,8 +69,9 @@ where { // if there are no relayers, explicitly registered at this lane, noone gets additional // priority boost - let lane_relayers = RelayersPallet::::lane_relayers(lane_id); - let lane_relayers_len: BlockNumberFor = (lane_relayers.len() as u32).into(); + let lane_relayers = RelayersPallet::::active_lane_relayers(&lane_id); + let active_lane_relayers = lane_relayers.relayers(); + let lane_relayers_len: BlockNumberFor = (active_lane_relayers.len() as u32).into(); if lane_relayers_len.is_zero() { return 0 } @@ -82,17 +84,17 @@ where // let's compute current slot number let current_block_number = SystemPallet::::block_number(); - let slot = current_block_number / slot_length; + let slot = current_block_number.saturating_sub(*lane_relayers.enacted_at()) / slot_length; // and then get the relayer for that slot let slot_relayer = match usize::try_from(slot % lane_relayers_len) { - Ok(slot_relayer_index) => &lane_relayers[slot_relayer_index], + Ok(slot_relayer_index) => &active_lane_relayers[slot_relayer_index], Err(_) => return 0, }; // if message delivery transaction is submitted by the relayer, assigned to the current // slot, let's boost the transaction priority - if relayer != slot_relayer { + if relayer != slot_relayer.relayer() { return 0 } @@ -263,24 +265,30 @@ mod integrity_tests { #[cfg(test)] mod tests { use super::*; - use crate::{mock::*, LaneRelayers}; + use crate::{mock::*, ActiveLaneRelayers}; + use bp_relayers::{ActiveLaneRelayersSet, NextLaneRelayersSet}; + use sp_runtime::traits::ConstU32; #[test] fn compute_per_lane_priority_boost_works() { run_test(|| { // insert 3 relayers to the queue let lane_id = LaneId::new(1, 2); - let relayer1 = 1_000; - let relayer2 = 2_000; - let relayer3 = 3_000; - LaneRelayers::::insert( - lane_id, - sp_runtime::BoundedVec::try_from(vec![relayer1, relayer2, relayer3]).unwrap(), - ); - - // at blocks 1..=SlotLength relayer1 gets the boost - System::set_block_number(0); - for _ in 1..SlotLength::get() { + let relayer1 = 100; + let relayer2 = 200; + let relayer3 = 300; + let mut next_set: NextLaneRelayersSet<_, _, ConstU32<3>> = + NextLaneRelayersSet::empty(5); + assert!(next_set.try_push(relayer1, 0)); + assert!(next_set.try_push(relayer2, 0)); + assert!(next_set.try_push(relayer3, 0)); + let mut active_set = ActiveLaneRelayersSet::default(); + active_set.activate_next_set(7, next_set, |_| true); + ActiveLaneRelayers::::insert(lane_id, active_set); + + // at blocks 7..=7+SlotLength relayer1 gets the boost + System::set_block_number(6); + for _ in 7..SlotLength::get() + 7 { System::set_block_number(System::block_number() + 1); assert_eq!( compute_per_lane_priority_boost::(lane_id, &relayer1), diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index c733c592e9..74396ade9a 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -22,7 +22,8 @@ use bp_messages::LaneId; use bp_relayers::{ - PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + ActiveLaneRelayersSet, NextLaneRelayersSet, PaymentProcedure, Registration, + RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; @@ -70,9 +71,28 @@ pub mod pallet { /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; - /// Maximal number of relayers that can register themselves on a single lane. + /// Maximal number of relayers that can reside in the active lane relayers set on a single + /// lane. + /// + /// Lowering this value leads to additional concurrency between relayers, potentially + /// making messages cheaper. So it shall not be too large. + #[pallet::constant] + type MaxActiveRelayersPerLane: Get; + /// Maximal number of relayers that can reside in the next lane relayers set on a single + /// lane. + /// + /// Relayers set is a bounded priority queue, where relayers with lower expected reward are + /// prioritized over greedier relayers. At the end of epoch, we select top + /// `MaxActiveRelayersPerLane` relayers from the next set and move them to the next set. To + /// alleviate possible spam attacks, where relayers are registering at lane with zero reward + /// (pushing out actual relayers with larger expected reward) and then deregistering + /// themselves right before epoch end, we make the next relayers set larger than the active + /// set. It would make it more expensive for attackers to fill the whole next set. + /// + /// This value must be larger than or equal to the [`Self::MaxActiveRelayersPerLane`]. #[pallet::constant] - type MaxRelayersPerLane: Get; + type MaxNextRelayersPerLane: Get; + /// Length of slots in chain blocks. /// /// Registered relayer may explicitly register himself at some lane to get priority boost @@ -483,20 +503,33 @@ pub mod pallet { OptionQuery, >; - /// A set of relayers that have explicitly registered themselves at a given lane. + /// An active set of relayers that have explicitly registered themselves at a given lane. /// /// Every relayer inside this set receives additional priority boost when it submits /// message delivers messages at given lane. The boost only happens inside the slot, /// assigned to relayer. #[pallet::storage] - #[pallet::getter(fn lane_relayers)] - pub type LaneRelayers = StorageMap< + #[pallet::getter(fn active_lane_relayers)] + pub type ActiveLaneRelayers = StorageMap< _, Identity, LaneId, - BoundedVec, + ActiveLaneRelayersSet, T::MaxActiveRelayersPerLane>, ValueQuery, >; + + /// A next set of relayers that have explicitly registered themselves at a given lane. + /// + /// This set may replace the [`ActiveLaneRelayers`] after current epoch ends. + #[pallet::storage] + #[pallet::getter(fn next_lane_relayers)] + pub type NextLaneRelayers = StorageMap< + _, + Identity, + LaneId, + NextLaneRelayersSet, T::MaxNextRelayersPerLane>, + OptionQuery, + >; } #[cfg(test)] diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 53028bb3d7..45c6642f09 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -194,6 +194,8 @@ parameter_types! { pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); + pub MaxActiveRelayersPerLane: u32 = 4; + pub MaxNextRelayersPerLane: u32 = 16; pub SlotLength: u32 = 16; pub PriorityBoostForActiveLaneRelayer: TransactionPriority = 1; } @@ -310,7 +312,8 @@ impl pallet_bridge_relayers::Config for TestRuntime { type Reward = ThisChainBalance; type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = MaxActiveRelayersPerLane; + type MaxNextRelayersPerLane = MaxNextRelayersPerLane; type SlotLength = SlotLength; type PriorityBoostPerMessage = ConstU64<1>; type PriorityBoostForActiveLaneRelayer = PriorityBoostForActiveLaneRelayer; diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs new file mode 100644 index 0000000000..2cc618542f --- /dev/null +++ b/primitives/relayers/src/lane_relayers.rs @@ -0,0 +1,601 @@ +// Copyright (C) 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 . + +//! Bridge lane relayers. + +pub use bp_messages::RelayerRewardAtSource; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::CloneNoBound; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Get, Zero}, + BoundedBTreeSet, BoundedVec, RuntimeDebug, +}; + +/// A relayer and the reward that it wants to receive for delivering a single message. +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct RelayerAndReward { + /// A relayer account identifier. + relayer: AccountId, + /// A reward that is paid to relayer for delivering a single message. + relayer_reward_per_message: RelayerRewardAtSource, +} + +impl RelayerAndReward { + /// Create new instance. + pub fn new(relayer: AccountId, relayer_reward_per_message: RelayerRewardAtSource) -> Self { + RelayerAndReward { relayer, relayer_reward_per_message } + } + + /// Return relayer account identifier. + pub fn relayer(&self) -> &AccountId { + &self.relayer + } + + /// Return expected relayer reward. + pub fn relayer_reward_per_message(&self) -> RelayerRewardAtSource { + self.relayer_reward_per_message + } +} + +/// A set of relayers that have explicitly registered themselves at a given lane. +/// +/// Every relayer inside this set receives additional priority boost when it submits +/// message delivers messages at given lane. The boost only happens inside the slot, +/// assigned to relayer. +/// +/// The active set will eventually be replaced with the [`NextLaneRelayersSet`]. Before +/// replacing, all relayers from the active set, who have delivered at least one messsage +/// at passed epoch, are reinserted into the next set. So if lane is active and relayers +/// area actually delivering messages, they can only be replaced by the relayers, offering +/// lower expected reward. +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxActiveRelayersPerLane))] +pub struct ActiveLaneRelayersSet> { + /// Number of block, where the active set has been enacted. + enacted_at: BlockNumber, + /// An active set of lane relayers. + /// + /// It is a circular queue. Every relayer in the queue is assigned the slot (fixed number + /// of blocks), starting from [`Self::enacted_at`]. Once the slot of last relayer ends, + /// next slot will be assigned to the first relayer and so on. + active_set: BoundedVec, MaxActiveRelayersPerLane>, + /// Relayers that have delivered at least one message in current epoch. + /// + /// This subset of the [`Self::active_set`] will be merged with the next set right before + /// lane epoch is advanced. Relayers that have deregistered from lane at current epoch, won't + /// be merged, though. + mergeable_set: BoundedBTreeSet, +} + +impl> Default + for ActiveLaneRelayersSet +{ + fn default() -> Self { + ActiveLaneRelayersSet { + enacted_at: Zero::zero(), + active_set: BoundedVec::new(), + mergeable_set: BoundedBTreeSet::new(), + } + } +} + +impl + ActiveLaneRelayersSet +where + AccountId: Clone + Ord, + BlockNumber: Copy, + MaxActiveRelayersPerLane: Get, +{ + /// Returns block, where this set has been enacted. + pub fn enacted_at(&self) -> &BlockNumber { + &self.enacted_at + } + + /// Returns relayer entry from the active set. + pub fn relayer(&self, relayer: &AccountId) -> Option<&RelayerAndReward> { + self.active_set.iter().filter(|r| r.relayer() == relayer).next() + } + + /// Returns relayers from the active set. + pub fn relayers(&self) -> &[RelayerAndReward] { + self.active_set.as_slice() + } + + /// Note message, delivered by given relayer. + /// + /// Returns true if we have updated anything in the structure. + pub fn note_delivered_message(&mut self, relayer: &AccountId) -> bool { + // TODO: add tests for that + + if !self.relayer(relayer).is_some() { + return false + } + + self.mergeable_set.try_insert(relayer.clone()).unwrap_or(false) + } + + /// Activate next set of relayers. + /// + /// The [`Self::active_set`] is replaced with the [`next_set`]. + /// + /// Returns false if `current_block` is lesser than the block where [`next_set`] may be enacted + pub fn activate_next_set>( + &mut self, + current_block: BlockNumber, + mut next_set: NextLaneRelayersSet, + is_lane_registration_active: impl Fn(&AccountId) -> bool, + ) -> bool + where + BlockNumber: Ord, + { + // ensure that we can enact the next set + if next_set.may_enact_at > current_block { + return false + } + + // merge mergeable relayers into next set + for relayer in &self.active_set { + // relayer has not delivered any new messages, do not merge + if !self.mergeable_set.contains(relayer.relayer()) { + continue + } + + // if relayer lane registration is no longer active, do not merge + if !is_lane_registration_active(relayer.relayer()) { + continue + } + + // TODO: add tests for that + + // else only push it to the next set if it is not yet there to avoid overwriting + // expected reward + let is_in_next_set = next_set.relayer(relayer.relayer()).is_some(); + if !is_in_next_set { + // we do not care if relayer stays in the set - we only need to try + let _ = next_set + .try_push(relayer.relayer().clone(), relayer.relayer_reward_per_message()); + } + } + // clear active sets + self.active_set.clear(); + self.mergeable_set.clear(); + // ...and finally fill the active set with new best relayers + let relayers_in_active_set = + sp_std::cmp::min(MaxActiveRelayersPerLane::get(), next_set.relayers().len() as u32); + for _ in 0..relayers_in_active_set { + // we know that the next set has at least `relayers_in_active_set` + // => so calling `remove(0)` is safe + // we know that the active set is empty and we select at most `MaxActiveRelayersPerLane` + // relayers => ignoring `try_push` result is safe + let _ = self.active_set.try_push(next_set.next_set.remove(0)); + } + // finally - remember block where we have activated the set + self.enacted_at = current_block; + + true + } +} + +/// A set of relayers that will become active at next lane epoch. +/// +/// The active set of lane relayers is required to change periodically (at `next_set_may_enact_at`). +/// An interval, when the same relayers set is active is called epoch. Every relayer in the epoch +/// is guaranteed to have at least one slot, but epochs may have different lengths. +/// +/// We change the set to guarantee that inactive relayers are removed from the set eventually +/// and are replaced by active relayers. The relayer will be scheduled for autoremoval if it +/// has not delivered any messages during previous epoch. +/// +/// Relayers are bargaining for the place in the set by offering lower reward for delivering +/// messages. Relayer, which agress to get a lower reward will likely to replace a "more greedy" +/// relayer in the [`Self::next_set`]. +#[derive(CloneNoBound, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxNextRelayersPerLane))] +pub struct NextLaneRelayersSet< + AccountId: Clone, + BlockNumber: Clone, + MaxNextRelayersPerLane: Get, +> { + /// Number of block, where the active set may be replaced with the [`Self::next_set`]. + /// + /// We do not allow immediate changes of the active set, because relayers + /// may change it so that they are always assigned at the current slot. + may_enact_at: BlockNumber, + /// Next set of lane relayers. + /// + /// It is a bounded priority queue. Relayers that are working for larger reward are replaced + /// with relayers, that are working for smaller reward. + next_set: BoundedVec, MaxNextRelayersPerLane>, +} + +impl + NextLaneRelayersSet +where + AccountId: Clone + PartialOrd, + BlockNumber: Copy, + MaxNextRelayersPerLane: Get, +{ + /// Creates new empty relayers set, where next sets enacts at given block. + pub fn empty(may_enact_at: BlockNumber) -> Self { + NextLaneRelayersSet { may_enact_at, next_set: BoundedVec::new() } + } + + /// Returns block, starting from which the [`Self::next_set`] may be enacted. + pub fn may_enact_at(&self) -> BlockNumber { + self.may_enact_at + } + + /// Set block, starting from which the [`Self::next_set`] may be enacted. + pub fn set_may_enact_at(&mut self, may_enact_at: BlockNumber) { + self.may_enact_at = may_enact_at; + } + + /// Returns relayer entry from the next set. + pub fn relayer(&self, relayer: &AccountId) -> Option<&RelayerAndReward> { + self.next_set.iter().filter(|r| r.relayer() == relayer).next() + } + + /// Returns relayers from the next set. + pub fn relayers(&self) -> &[RelayerAndReward] { + self.next_set.as_slice() + } + + /// Try insert relayer to the next set. + /// + /// Returns `true` if relayer has been added to the set and false otherwise. + pub fn try_push( + &mut self, + relayer: AccountId, + relayer_reward_per_message: RelayerRewardAtSource, + ) -> bool { + // first, remove existing entry for the same relayer from the set + self.try_remove(&relayer); + // now try to insert new entry into the queue + self.next_set + .force_insert_keep_left( + self.select_position_in_next_set(relayer_reward_per_message), + RelayerAndReward { relayer, relayer_reward_per_message }, + ) + .is_ok() + } + + /// Try remove relayer from the next set. + /// + /// Returns `true` if relayer has been removed from the set. + pub fn try_remove(&mut self, relayer: &AccountId) -> bool { + let len_before = self.next_set.len(); + self.next_set.retain(|entry| entry.relayer != *relayer); + self.next_set.len() != len_before + } + + /// Selects position to insert relayer, wanting to receive `reward` for every delivered + /// message. If there are multiple relayers with that reward, relayers that are already + /// in the set are prioritized above the new relayer. + fn select_position_in_next_set( + &self, + relayer_reward_per_message: RelayerRewardAtSource, + ) -> usize { + // we need to insert new entry **after** the last entry with the same `reward`. Otherwise it + // may be used to push relayers our of the queue + let mut initial_position = self + .next_set + .binary_search_by_key(&relayer_reward_per_message, |entry| { + entry.relayer_reward_per_message + }) + .unwrap_or_else(|position| position); + while self + .next_set + .get(initial_position) + .map(|entry| entry.relayer_reward_per_message == relayer_reward_per_message) + .unwrap_or(false) + { + initial_position += 1; + } + initial_position + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_runtime::traits::ConstU32; + use std::collections::BTreeSet; + + const MAX_ACTIVE_LANE_RELAYERS: u32 = 2; + const MAX_NEXT_LANE_RELAYERS: u32 = 4; + type TestActiveLaneRelayersSet = + ActiveLaneRelayersSet>; + type TestNextLaneRelayersSet = NextLaneRelayersSet>; + + #[test] + fn active_set_activate_next_set_works() { + let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { + enacted_at: 0, + active_set: vec![].try_into().unwrap(), + mergeable_set: BTreeSet::new().try_into().unwrap(), + }; + let mut next_set: TestNextLaneRelayersSet = NextLaneRelayersSet { + may_enact_at: 100, + next_set: vec![ + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + RelayerAndReward::new(300, 12), + RelayerAndReward::new(400, 13), + ] + .try_into() + .unwrap(), + }; + + // when we can't yet activate next set, it returns false + assert!(!active_set.activate_next_set(0, next_set.clone(), |_| true)); + + // only two relayers are selected from the next set when the active set is empty + assert!(active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!(active_set.enacted_at, 100); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + ]) + .unwrap(), + ); + assert_eq!(active_set.mergeable_set.iter().cloned().collect::>(), Vec::::new(),); + + // spam relayers are occupying the whole next set and then they leave in favor of some + // expensive relayers. At the same time, both relayers from the active set were delivering + // messages => active set is not changed + active_set.mergeable_set = active_set + .active_set + .iter() + .map(|r| r.relayer().clone()) + .collect::>() + .try_into() + .unwrap(); + next_set.next_set = vec![ + RelayerAndReward::new(300, 1000), + RelayerAndReward::new(400, 1100), + RelayerAndReward::new(500, 1200), + RelayerAndReward::new(600, 1300), + ] + .try_into() + .unwrap(); + assert!(active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + ]) + .unwrap(), + ); + + // better relayers appear in the next set + // => even if active relayers were delivering messages, they lose their slots + active_set.mergeable_set = active_set + .active_set + .iter() + .map(|r| r.relayer().clone()) + .collect::>() + .try_into() + .unwrap(); + next_set.next_set = vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(800, 5), + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + ] + .try_into() + .unwrap(); + assert!(active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(800, 5), + ]) + .unwrap(), + ); + + // one of active relayers deregisters => next epoch will start without it + active_set.mergeable_set = active_set + .active_set + .iter() + .map(|r| r.relayer().clone()) + .collect::>() + .try_into() + .unwrap(); + next_set.next_set = vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(100, 10), + RelayerAndReward::new(200, 11), + RelayerAndReward::new(300, 1000), + ] + .try_into() + .unwrap(); + assert!(active_set.activate_next_set(100, next_set.clone(), |relayer| *relayer != 800)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(700, 5), + RelayerAndReward::new(100, 10), + ]) + .unwrap(), + ); + + // if relayer is in the next set already, we do not remerge it + active_set.mergeable_set = active_set + .active_set + .iter() + .map(|r| r.relayer().clone()) + .collect::>() + .try_into() + .unwrap(); + next_set.next_set = vec![RelayerAndReward::new(700, 100), RelayerAndReward::new(100, 200)] + .try_into() + .unwrap(); + assert!(active_set.activate_next_set(100, next_set.clone(), |relayer| *relayer != 800)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(700, 100), + RelayerAndReward::new(100, 200), + ]) + .unwrap(), + ); + + // all relayers deregister themselves and no relayers have submitted any messages => new + // active set will be empty + next_set.next_set = vec![].try_into().unwrap(); + assert!(active_set.activate_next_set(100, next_set.clone(), |_| true)); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![]).unwrap() + ); + } + + #[test] + fn next_set_try_push_works() { + let mut relayers: TestNextLaneRelayersSet = + NextLaneRelayersSet { may_enact_at: 100, next_set: vec![].try_into().unwrap() }; + + // first `MAX_NEXT_LANE_RELAYERS` are simply filling the set + let max_next_lane_relayers: u64 = MAX_NEXT_LANE_RELAYERS as _; + for i in 0..max_next_lane_relayers { + assert!(relayers.try_push(i, (max_next_lane_relayers - i) * 10)); + } + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 2, relayer_reward_per_message: 20 }, + RelayerAndReward { relayer: 1, relayer_reward_per_message: 30 }, + RelayerAndReward { relayer: 0, relayer_reward_per_message: 40 }, + ], + ); + + // try to insert relayer who wants reward, that is larger than anyone in the set + // => the set is not changed + assert!(!relayers.try_push(4, 50)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 2, relayer_reward_per_message: 20 }, + RelayerAndReward { relayer: 1, relayer_reward_per_message: 30 }, + RelayerAndReward { relayer: 0, relayer_reward_per_message: 40 }, + ], + ); + + // replace worst relayer in the set + assert!(relayers.try_push(5, 35)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 2, relayer_reward_per_message: 20 }, + RelayerAndReward { relayer: 1, relayer_reward_per_message: 30 }, + RelayerAndReward { relayer: 5, relayer_reward_per_message: 35 }, + ], + ); + + // insert best relayer to the set, pushing worst relayer out of set + assert!(relayers.try_push(6, 5)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, relayer_reward_per_message: 5 }, + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 2, relayer_reward_per_message: 20 }, + RelayerAndReward { relayer: 1, relayer_reward_per_message: 30 }, + ], + ); + + // insert best relayer to the set, pushing worst relayer out of set + assert!(relayers.try_push(6, 5)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, relayer_reward_per_message: 5 }, + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 2, relayer_reward_per_message: 20 }, + RelayerAndReward { relayer: 1, relayer_reward_per_message: 30 }, + ], + ); + + // insert relayer to the middle of the set, pushing worst relayer out of set + assert!(relayers.try_push(7, 15)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, relayer_reward_per_message: 5 }, + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 7, relayer_reward_per_message: 15 }, + RelayerAndReward { relayer: 2, relayer_reward_per_message: 20 }, + ], + ); + + // insert couple of relayer that want the same reward as some relayer in the middle of the + // queue => they are inserted **after** existing relayers + assert!(relayers.try_push(8, 10)); + assert!(relayers.try_push(9, 10)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, relayer_reward_per_message: 5 }, + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 8, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 9, relayer_reward_per_message: 10 }, + ], + ); + + // insert next relayer, similar to previous => it isn't inserted + assert!(!relayers.try_push(10, 10)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 6, relayer_reward_per_message: 5 }, + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 8, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 9, relayer_reward_per_message: 10 }, + ], + ); + + // update expected reward of existing relayer => the set order is changed + assert!(relayers.try_push(8, 2)); + assert_eq!( + relayers.next_set.as_slice(), + &[ + RelayerAndReward { relayer: 8, relayer_reward_per_message: 2 }, + RelayerAndReward { relayer: 6, relayer_reward_per_message: 5 }, + RelayerAndReward { relayer: 3, relayer_reward_per_message: 10 }, + RelayerAndReward { relayer: 9, relayer_reward_per_message: 10 }, + ], + ); + } + + #[test] + fn next_set_try_remove_works() { + let mut relayers: TestNextLaneRelayersSet = + NextLaneRelayersSet { may_enact_at: 100, next_set: vec![].try_into().unwrap() }; + + assert!(relayers.try_push(1, 0)); + assert!(relayers.try_remove(&1)); + assert!(!relayers.try_remove(&1)); + } +} diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index 6ca2d9d10a..e9d367fc30 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -23,6 +23,9 @@ pub use extension::{ BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig, RuntimeWithUtilityPallet, }; +pub use lane_relayers::{ + ActiveLaneRelayersSet, NextLaneRelayersSet, RelayerAndReward, RelayerRewardAtSource, +}; pub use registration::{Registration, StakeAndSlash}; use bp_messages::LaneId; @@ -37,6 +40,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, marker::PhantomData}; mod extension; +mod lane_relayers; mod registration; /// The owner of the sovereign account that should pay the rewards. From 392f93af9d8c87ea9539fb9ded8e60e4f76b2199 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 11:49:25 +0300 Subject: [PATCH 39/60] more tests --- primitives/relayers/src/lane_relayers.rs | 36 ++++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 2cc618542f..a313cbf46d 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -120,9 +120,7 @@ where /// /// Returns true if we have updated anything in the structure. pub fn note_delivered_message(&mut self, relayer: &AccountId) -> bool { - // TODO: add tests for that - - if !self.relayer(relayer).is_some() { + if self.relayer(relayer).is_none() { return false } @@ -160,8 +158,6 @@ where continue } - // TODO: add tests for that - // else only push it to the next set if it is not yet there to avoid overwriting // expected reward let is_in_next_set = next_set.relayer(relayer.relayer()).is_some(); @@ -322,6 +318,33 @@ mod tests { ActiveLaneRelayersSet>; type TestNextLaneRelayersSet = NextLaneRelayersSet>; + #[test] + fn note_delivered_message_works() { + let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { + enacted_at: 0, + active_set: vec![RelayerAndReward::new(100, 0), RelayerAndReward::new(200, 0)] + .try_into() + .unwrap(), + mergeable_set: BTreeSet::new().try_into().unwrap(), + }; + + // when registered relayer delivers first message + assert!(active_set.note_delivered_message(&100)); + assert_eq!(active_set.mergeable_set.iter().cloned().collect::>(), vec![100],); + + // when registered relayer delivers second message + assert!(!active_set.note_delivered_message(&100)); + assert_eq!(active_set.mergeable_set.iter().cloned().collect::>(), vec![100],); + + // when another registered relayer delivers a message + assert!(active_set.note_delivered_message(&200)); + assert_eq!(active_set.mergeable_set.iter().cloned().collect::>(), vec![100, 200],); + + // when unregistered relayer delivers a message + assert!(!active_set.note_delivered_message(&300)); + assert_eq!(active_set.mergeable_set.iter().cloned().collect::>(), vec![100, 200],); + } + #[test] fn active_set_activate_next_set_works() { let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { @@ -438,7 +461,8 @@ mod tests { .unwrap(), ); - // if relayer is in the next set already, we do not remerge it + // if relayer is in the next set already, we do not remerge it because we may rewrite its + // updated bid active_set.mergeable_set = active_set .active_set .iter() From b48a218501a726e7ee655d42b47d3e11c0136ca4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 11:55:34 +0300 Subject: [PATCH 40/60] fix runtimes --- bin/millau/runtime/src/lib.rs | 3 ++- bin/rialto-parachain/runtime/src/lib.rs | 3 ++- bin/rialto/runtime/src/lib.rs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index e510913a9d..75256ce4ff 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -432,7 +432,8 @@ impl pallet_bridge_relayers::Config for Runtime { ConstU64<1_000>, ConstU64<8>, >; - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = ConstU32<16>; + type MaxNextRelayersPerLane = ConstU32<1_024>; type SlotLength = ConstU64<16>; type PriorityBoostPerMessage = PriorityBoostPerMessage; type PriorityBoostForActiveLaneRelayer = ConstU64<0>; diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 7009747c7a..b0a7dd5b54 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -541,7 +541,8 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = ConstU32<16>; + type MaxNextRelayersPerLane = ConstU32<1_024>; type SlotLength = ConstU32<16>; type PriorityBoostPerMessage = ConstU64<0>; type PriorityBoostForActiveLaneRelayer = ConstU64<0>; diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index a20618a248..fafe2695ee 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -418,7 +418,8 @@ impl pallet_bridge_relayers::Config for Runtime { type PaymentProcedure = bp_relayers::PayRewardFromAccount, AccountId>; type StakeAndSlash = (); - type MaxRelayersPerLane = ConstU32<16>; + type MaxActiveRelayersPerLane = ConstU32<16>; + type MaxNextRelayersPerLane = ConstU32<1_024>; type SlotLength = ConstU32<16>; type PriorityBoostPerMessage = ConstU64<0>; type PriorityBoostForActiveLaneRelayer = ConstU64<0>; From 9f4e5cc8b710fbae9bb35f89aeb83ba7f7265cb2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 12:47:34 +0300 Subject: [PATCH 41/60] CI --- modules/relayers/src/lib.rs | 2 +- primitives/relayers/src/lane_relayers.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 74396ade9a..dec3a15a18 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -85,7 +85,7 @@ pub mod pallet { /// prioritized over greedier relayers. At the end of epoch, we select top /// `MaxActiveRelayersPerLane` relayers from the next set and move them to the next set. To /// alleviate possible spam attacks, where relayers are registering at lane with zero reward - /// (pushing out actual relayers with larger expected reward) and then deregistering + /// (pushing out actual relayers with larger expected reward) and then `deregistering` /// themselves right before epoch end, we make the next relayers set larger than the active /// set. It would make it more expensive for attackers to fill the whole next set. /// diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index a313cbf46d..779d133413 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -77,7 +77,7 @@ pub struct ActiveLaneRelayersSet, } @@ -108,7 +108,7 @@ where /// Returns relayer entry from the active set. pub fn relayer(&self, relayer: &AccountId) -> Option<&RelayerAndReward> { - self.active_set.iter().filter(|r| r.relayer() == relayer).next() + self.active_set.iter().find(|r| r.relayer() == relayer) } /// Returns relayers from the active set. @@ -129,9 +129,9 @@ where /// Activate next set of relayers. /// - /// The [`Self::active_set`] is replaced with the [`next_set`]. + /// This set is replaced with the `next_set` contents. /// - /// Returns false if `current_block` is lesser than the block where [`next_set`] may be enacted + /// Returns false if `current_block` is lesser than the block where `next_set` may be enacted pub fn activate_next_set>( &mut self, current_block: BlockNumber, @@ -198,8 +198,8 @@ where /// has not delivered any messages during previous epoch. /// /// Relayers are bargaining for the place in the set by offering lower reward for delivering -/// messages. Relayer, which agress to get a lower reward will likely to replace a "more greedy" -/// relayer in the [`Self::next_set`]. +/// messages. Relayer, which agrees to get a lower reward will likely to replace a "more greedy" +/// relayer in the `next_set`. #[derive(CloneNoBound, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxNextRelayersPerLane))] pub struct NextLaneRelayersSet< @@ -231,19 +231,19 @@ where NextLaneRelayersSet { may_enact_at, next_set: BoundedVec::new() } } - /// Returns block, starting from which the [`Self::next_set`] may be enacted. + /// Returns block, starting from which the `next_set` may be enacted. pub fn may_enact_at(&self) -> BlockNumber { self.may_enact_at } - /// Set block, starting from which the [`Self::next_set`] may be enacted. + /// Set block, starting from which the `next_set` may be enacted. pub fn set_may_enact_at(&mut self, may_enact_at: BlockNumber) { self.may_enact_at = may_enact_at; } /// Returns relayer entry from the next set. pub fn relayer(&self, relayer: &AccountId) -> Option<&RelayerAndReward> { - self.next_set.iter().filter(|r| r.relayer() == relayer).next() + self.next_set.iter().find(|r| r.relayer() == relayer) } /// Returns relayers from the next set. From 6957d6d1174eaf3f77b161b6b9ea8bcc157e7ce8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 12:59:04 +0300 Subject: [PATCH 42/60] CI 2 --- primitives/relayers/src/lane_relayers.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 779d133413..02e9e8c2e7 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -386,7 +386,7 @@ mod tests { active_set.mergeable_set = active_set .active_set .iter() - .map(|r| r.relayer().clone()) + .map(|r| *r.relayer()) .collect::>() .try_into() .unwrap(); @@ -413,7 +413,7 @@ mod tests { active_set.mergeable_set = active_set .active_set .iter() - .map(|r| r.relayer().clone()) + .map(|r| *r.relayer()) .collect::>() .try_into() .unwrap(); @@ -439,7 +439,7 @@ mod tests { active_set.mergeable_set = active_set .active_set .iter() - .map(|r| r.relayer().clone()) + .map(|r| *r.relayer()) .collect::>() .try_into() .unwrap(); @@ -466,7 +466,7 @@ mod tests { active_set.mergeable_set = active_set .active_set .iter() - .map(|r| r.relayer().clone()) + .map(|r| *r.relayer()) .collect::>() .try_into() .unwrap(); From 21f8caad237c3531dc9b1c55cfcc92b09264c6ae Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 13:26:36 +0300 Subject: [PATCH 43/60] CI 3 --- modules/relayers/src/extension/priority.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/relayers/src/extension/priority.rs b/modules/relayers/src/extension/priority.rs index eaf1374aa7..5139dae6c5 100644 --- a/modules/relayers/src/extension/priority.rs +++ b/modules/relayers/src/extension/priority.rs @@ -69,7 +69,7 @@ where { // if there are no relayers, explicitly registered at this lane, noone gets additional // priority boost - let lane_relayers = RelayersPallet::::active_lane_relayers(&lane_id); + let lane_relayers = RelayersPallet::::active_lane_relayers(lane_id); let active_lane_relayers = lane_relayers.relayers(); let lane_relayers_len: BlockNumberFor = (active_lane_relayers.len() as u32).into(); if lane_relayers_len.is_zero() { From 7e94f241a461483da834129bb9ccc49788a5c79e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 14:32:42 +0300 Subject: [PATCH 44/60] successful advance_lane_epoch is free for submitter --- modules/relayers/src/lib.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index f4ae1faf4d..78dc02cde2 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -26,7 +26,7 @@ use bp_relayers::{ RelayerRewardAtSource, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; -use frame_support::fail; +use frame_support::{dispatch::PostDispatchInfo, fail}; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_runtime::{traits::CheckedSub, Saturating}; @@ -470,7 +470,6 @@ pub mod pallet { Ok(()) } - // TODO: add another `obsolete` extension for this call of the relayers pallet? /// Enact next set of relayers at a given lane. /// /// This will replace the set of active relayers with the next scheduled set, for given @@ -481,7 +480,10 @@ pub mod pallet { /// be paid. We suggest the first relayer from the `next_set` to submit this transaction. #[pallet::call_index(7)] #[pallet::weight(Weight::zero())] // TODO - pub fn advance_lane_epoch(origin: OriginFor, lane: LaneId) -> DispatchResult { + pub fn advance_lane_epoch( + origin: OriginFor, + lane: LaneId, + ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; let current_block_number = SystemPallet::::block_number(); @@ -536,7 +538,7 @@ pub mod pallet { ActiveLaneRelayers::::insert(lane, active_lane_relayers); NextLaneRelayers::::insert(lane, next_lane_relayers); - Ok(()) + Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }) } } @@ -1885,10 +1887,12 @@ mod tests { // when active epoch is advanced, new epoch starts at the block, where it has been // actually started, not the epoch where previous epoch was supposed to end System::::set_block_number(next_lane_relayers.may_enact_at() + 77); - assert_ok!(BridgeRelayers::advance_lane_epoch( + let result = BridgeRelayers::advance_lane_epoch( RuntimeOrigin::signed(REGISTER_RELAYER), - test_lane_id() - )); + test_lane_id(), + ); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); assert_eq!( From 03bc70575c14fc7d133ef7c810d66c1657d98e1e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 14:41:15 +0300 Subject: [PATCH 45/60] small optimization --- modules/relayers/src/lib.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 78dc02cde2..16ff7b76e2 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -30,7 +30,7 @@ use frame_support::{dispatch::PostDispatchInfo, fail}; use frame_system::Pallet as SystemPallet; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_runtime::{traits::CheckedSub, Saturating}; -use sp_std::{marker::PhantomData, vec::Vec}; +use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData}; pub use pallet::*; pub use payment_adapter::DeliveryConfirmationPaymentsAdapter; @@ -497,17 +497,23 @@ pub mod pallet { // reg.lanes().contains(&lane))` is called in `activate_next_set` and later in `for // old_relayer in old_active_set`. May dedup to decrease weight + // read registrations of old relayers - we'll probably need to read it twice below, so + // better to cache it here + let mut old_active_set_registrations = BTreeMap::new(); + for old_relayer in active_lane_relayers.relayers() { + let old_relayer_id = old_relayer.relayer(); + let registration = Self::registered_relayer(old_relayer_id); + old_active_set_registrations.insert(old_relayer_id.clone(), registration); + } + // activate next set of relayers - let old_active_set = active_lane_relayers - .relayers() - .iter() - .map(|r| r.relayer().clone()) - .collect::>(); ensure!( active_lane_relayers.activate_next_set( current_block_number, next_lane_relayers.clone(), - |relayer| Self::registered_relayer(relayer) + |relayer| old_active_set_registrations + .get(relayer) + .and_then(|maybe_reg| maybe_reg.as_ref()) .map(|reg| reg.lanes().contains(&lane)) .unwrap_or(false), ), @@ -524,14 +530,20 @@ pub mod pallet { // technically, this is incorrect, because relaye may have wanted to keep lane // registration. But there's no difference between such state and state when the relayer // has deregistered - for old_relayer in old_active_set { + for (old_relayer, old_relayer_registration) in old_active_set_registrations { if next_lane_relayers.relayer(&old_relayer).is_some() { continue } - RegisteredRelayers::::mutate_extant(&old_relayer, |registration| { - Self::remove_lane_from_relayer_registration(registration, lane, true); - }); + if let Some(mut old_relayer_registration) = old_relayer_registration { + if Self::remove_lane_from_relayer_registration( + &mut old_relayer_registration, + lane, + true, + ) { + RegisteredRelayers::::insert(old_relayer, old_relayer_registration); + } + } } // update relayer sets in the storage From 772837f00e5c64b4a7e38ab614537d299ed4a8fd Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 15:41:15 +0300 Subject: [PATCH 46/60] remove lane registrations when slashing relayer --- modules/relayers/src/lib.rs | 162 +++++++++++++++++++++-- modules/relayers/src/mock.rs | 5 + modules/relayers/src/stake_adapter.rs | 5 +- primitives/relayers/src/lane_relayers.rs | 53 +++++++- 4 files changed, 211 insertions(+), 14 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 16ff7b76e2..a5af8235fd 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -411,13 +411,6 @@ pub mod pallet { Error::::TooLargeRewardToOccupyAnEntry, ); - // the relayer need to stake additional amount for every additional lane - registration.set_stake(Self::update_relayer_stake( - &relayer, - registration.current_stake(), - registration.required_stake(Self::base_stake(), Self::stake_per_lane()), - )?); - // update basic and lane registration in the runtime storage RegisteredRelayers::::insert(&relayer, registration); NextLaneRelayers::::insert(lane, next_lane_relayers); @@ -577,8 +570,7 @@ pub mod pallet { relayer: &T::AccountId, slash_destination: RewardsAccountParams, ) { - // TODO: also remove from all lanes? - + // remove base relayer registration let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, None => { @@ -592,6 +584,18 @@ pub mod pallet { }, }; + // since slashing is an extraordinary case, we can't allow relayer get priority boosts + // for his transaction on lanes anymore. It may break the active lane relayers set slot + // ordering but it is bettter than boosting priority of potentially invalid transactions + for lane in registration.lanes() { + ActiveLaneRelayers::::mutate_extant(lane, |active_lane_relayers| { + active_lane_relayers.try_remove(relayer); + }); + NextLaneRelayers::::mutate_extant(lane, |next_lane_relayers| { + next_lane_relayers.try_remove(relayer); + }); + } + match T::StakeAndSlash::repatriate_reserved( relayer, slash_destination, @@ -919,7 +923,7 @@ mod tests { use bp_messages::LaneId; use bp_relayers::{RelayerAndReward, RewardsAccountOwner}; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, assert_storage_noop, traits::fungible::{Inspect, Mutate}, }; use frame_system::{EventRecord, Pallet as System, Phase}; @@ -1965,4 +1969,142 @@ mod tests { assert_eq!(BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().relayers(), &[]); }); } + + #[test] + fn slash_and_deregister_does_nothing_if_relayer_is_not_registered() { + run_test(|| { + assert_storage_noop!(BridgeRelayers::slash_and_deregister( + ®ULAR_RELAYER, + test_reward_account_param() + )); + }) + } + + #[test] + fn slash_and_deregister_works() { + run_test(|| { + // let's add basic relayer registration and lane registrations + let lane_1_id = test_lane_id(); + let lane_2_id = LaneId::new(lane_1_id, lane_1_id); + assert_ok!(BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + Stake::get() + LaneStake::get() * 2 + 100, + )); + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + lane_1_id, + 0, + )); + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + lane_2_id, + 0, + )); + + // let's activate relayer at lane 1 and emulate message delivery + System::::set_block_number( + BridgeRelayers::next_lane_relayers(lane_1_id).unwrap().may_enact_at(), + ); + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + lane_1_id, + )); + ActiveLaneRelayers::::mutate_extant(lane_1_id, |lane_relayers| { + lane_relayers.note_delivered_message(®ISTER_RELAYER); + }); + BridgeRelayers::register_relayer_reward( + test_reward_account_param(), + ®ISTER_RELAYER, + 100, + ); + + // now slash relayer + let old_free_balance = Balances::free_balance(REGISTER_RELAYER); + BridgeRelayers::slash_and_deregister(®ISTER_RELAYER, test_reward_account_param()); + + // => all registrations are removed, but reward is still there and may be claimed + assert_eq!(BridgeRelayers::registered_relayer(REGISTER_RELAYER), None); + assert_eq!(BridgeRelayers::active_lane_relayers(lane_1_id).relayers(), &[]); + assert_eq!(BridgeRelayers::next_lane_relayers(lane_1_id).unwrap().relayers(), &[]); + assert_eq!(BridgeRelayers::active_lane_relayers(lane_2_id).relayers(), &[]); + assert_eq!(BridgeRelayers::next_lane_relayers(lane_2_id).unwrap().relayers(), &[]); + assert_eq!(Balances::free_balance(REGISTER_RELAYER), old_free_balance); + assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), 0); + assert_eq!( + BridgeRelayers::relayer_reward(REGISTER_RELAYER, test_reward_account_param()), + Some(100) + ); + }); + } } +/* + // remove base relayer registration + let registration = match RegisteredRelayers::::take(relayer) { + Some(registration) => registration, + None => { + log::trace!( + target: crate::LOG_TARGET, + "Cannot slash unregistered relayer {:?}", + relayer, + ); + + return + }, + }; + + // since slashing is an extraordinary case, we can't allow relayer get priority boosts + // for his transaction on lanes anymore. It may break the active lane relayers set slot + // ordering but it is bettter than boosting priority of potentially invalid transactions + for lane in registration.lanes() { + ActiveLaneRelayers::::mutate_extant(lane, |active_lane_relayers| { + active_lane_relayers.try_remove(relayer); + }); + NextLaneRelayers::::mutate_extant(lane, |next_lane_relayers| { + next_lane_relayers.try_remove(relayer); + }); + } + + match T::StakeAndSlash::repatriate_reserved( + relayer, + slash_destination, + registration.current_stake(), + ) { + Ok(failed_to_slash) if failed_to_slash.is_zero() => { + log::trace!( + target: crate::LOG_TARGET, + "Relayer account {:?} has been slashed for {:?}. Funds were deposited to {:?}", + relayer, + registration.current_stake(), + slash_destination, + ); + }, + Ok(failed_to_slash) => { + log::trace!( + target: crate::LOG_TARGET, + "Relayer account {:?} has been partially slashed for {:?}. Funds were deposited to {:?}. \ + Failed to slash: {:?}", + relayer, + registration.current_stake(), + slash_destination, + failed_to_slash, + ); + }, + Err(e) => { + // TODO: document this. Where? + + // it may fail if there's no beneficiary account. For us it means that this + // account must exists before we'll deploy the bridge + log::debug!( + target: crate::LOG_TARGET, + "Failed to slash relayer account {:?}: {:?}. Maybe beneficiary account doesn't exist? \ + Beneficiary: {:?}, amount: {:?}, failed to slash: {:?}", + relayer, + e, + slash_destination, + registration.current_stake(), + registration.current_stake(), + ); + }, + } +*/ diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 84d50e9b78..391426aad4 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -430,6 +430,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities { /// Run pallet test. pub fn run_test(test: impl FnOnce() -> T) -> T { new_test_ext().execute_with(|| { + Balances::mint_into( + &TestPaymentProcedure::rewards_account(test_reward_account_param()), + ExistentialDeposit::get(), + ) + .unwrap(); Balances::mint_into(®ISTER_RELAYER, ExistentialDeposit::get() + 10 * Stake::get()) .unwrap(); Balances::mint_into(®ISTER_RELAYER_2, ExistentialDeposit::get() + 10 * Stake::get()) diff --git a/modules/relayers/src/stake_adapter.rs b/modules/relayers/src/stake_adapter.rs index f96b26c80a..707a6efd83 100644 --- a/modules/relayers/src/stake_adapter.rs +++ b/modules/relayers/src/stake_adapter.rs @@ -131,9 +131,7 @@ mod tests { run_test(|| { let beneficiary = test_reward_account_param(); let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary); - - let mut expected_balance = ExistentialDeposit::get(); - Balances::mint_into(&beneficiary_account, expected_balance).unwrap(); + let mut expected_balance = Balances::free_balance(&beneficiary_account); assert_eq!( TestStakeAndSlash::repatriate_reserved(&1, beneficiary, test_stake()), @@ -175,6 +173,7 @@ mod tests { run_test(|| { let beneficiary = test_reward_account_param(); let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary); + Balances::set_balance(&beneficiary_account, 0); Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); diff --git a/primitives/relayers/src/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 02e9e8c2e7..61a1a5138c 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -127,6 +127,18 @@ where self.mergeable_set.try_insert(relayer.clone()).unwrap_or(false) } + /// Remove relayer from the active set. + /// + /// This function breaks the order of the active relayers set and may cause additional + /// contention between relayers. So it must only be used when relayer is being slashed and can + /// no longer be useful to the system. + pub fn try_remove(&mut self, relayer: &AccountId) -> bool { + let len_before = self.active_set.len(); + self.active_set.retain(|entry| entry.relayer != *relayer); + self.mergeable_set.remove(relayer); + self.active_set.len() != len_before + } + /// Activate next set of relayers. /// /// This set is replaced with the `next_set` contents. @@ -318,6 +330,45 @@ mod tests { ActiveLaneRelayersSet>; type TestNextLaneRelayersSet = NextLaneRelayersSet>; + #[test] + fn active_set_try_remove_works() { + let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { + enacted_at: 0, + active_set: vec![RelayerAndReward::new(100, 0), RelayerAndReward::new(200, 0)] + .try_into() + .unwrap(), + mergeable_set: BTreeSet::from([100]).try_into().unwrap(), + }; + + // remove relayer that is not in the set + assert!(!active_set.try_remove(&300)); + assert_eq!(active_set.mergeable_set.iter().cloned().collect::>(), vec![100],); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(100, 0), + RelayerAndReward::new(200, 0), + ]) + .unwrap(), + ); + + // remove relayer that is both in the active and mergeable sets + assert!(active_set.try_remove(&100)); + assert!(active_set.mergeable_set.is_empty()); + assert_eq!( + active_set.active_set, + BoundedVec::<_, ConstU32>::try_from(vec![ + RelayerAndReward::new(200, 0), + ]) + .unwrap(), + ); + + // remove relayer that is only in the active set + assert!(active_set.try_remove(&200)); + assert!(active_set.mergeable_set.is_empty()); + assert!(active_set.active_set.is_empty()); + } + #[test] fn note_delivered_message_works() { let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { @@ -346,7 +397,7 @@ mod tests { } #[test] - fn active_set_activate_next_set_works() { + fn activate_next_set_works() { let mut active_set: TestActiveLaneRelayersSet = ActiveLaneRelayersSet { enacted_at: 0, active_set: vec![].try_into().unwrap(), From 78a56edb55c6b03f1ead2aecbf166f5aae045420 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 15:51:46 +0300 Subject: [PATCH 47/60] remove obsolete TODO and dev comment --- modules/relayers/src/lib.rs | 74 ------------------------------------- 1 file changed, 74 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index a5af8235fd..7f0cbed2e3 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -486,10 +486,6 @@ pub mod pallet { None => fail!(Error::::NoRelayersAtLane), }; - // TODO: the same `Self::registered_relayer(relayer).map(|reg| - // reg.lanes().contains(&lane))` is called in `activate_next_set` and later in `for - // old_relayer in old_active_set`. May dedup to decrease weight - // read registrations of old relayers - we'll probably need to read it twice below, so // better to cache it here let mut old_active_set_registrations = BTreeMap::new(); @@ -2038,73 +2034,3 @@ mod tests { }); } } -/* - // remove base relayer registration - let registration = match RegisteredRelayers::::take(relayer) { - Some(registration) => registration, - None => { - log::trace!( - target: crate::LOG_TARGET, - "Cannot slash unregistered relayer {:?}", - relayer, - ); - - return - }, - }; - - // since slashing is an extraordinary case, we can't allow relayer get priority boosts - // for his transaction on lanes anymore. It may break the active lane relayers set slot - // ordering but it is bettter than boosting priority of potentially invalid transactions - for lane in registration.lanes() { - ActiveLaneRelayers::::mutate_extant(lane, |active_lane_relayers| { - active_lane_relayers.try_remove(relayer); - }); - NextLaneRelayers::::mutate_extant(lane, |next_lane_relayers| { - next_lane_relayers.try_remove(relayer); - }); - } - - match T::StakeAndSlash::repatriate_reserved( - relayer, - slash_destination, - registration.current_stake(), - ) { - Ok(failed_to_slash) if failed_to_slash.is_zero() => { - log::trace!( - target: crate::LOG_TARGET, - "Relayer account {:?} has been slashed for {:?}. Funds were deposited to {:?}", - relayer, - registration.current_stake(), - slash_destination, - ); - }, - Ok(failed_to_slash) => { - log::trace!( - target: crate::LOG_TARGET, - "Relayer account {:?} has been partially slashed for {:?}. Funds were deposited to {:?}. \ - Failed to slash: {:?}", - relayer, - registration.current_stake(), - slash_destination, - failed_to_slash, - ); - }, - Err(e) => { - // TODO: document this. Where? - - // it may fail if there's no beneficiary account. For us it means that this - // account must exists before we'll deploy the bridge - log::debug!( - target: crate::LOG_TARGET, - "Failed to slash relayer account {:?}: {:?}. Maybe beneficiary account doesn't exist? \ - Beneficiary: {:?}, amount: {:?}, failed to slash: {:?}", - relayer, - e, - slash_destination, - registration.current_stake(), - registration.current_stake(), - ); - }, - } -*/ From 1f70ac7e38ed332da74bb4a4aeae5d09eb76e2d9 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 17 Oct 2023 16:31:15 +0300 Subject: [PATCH 48/60] added benchmarks for increase_stake, decrease_stake and register_at_lane --- modules/relayers/src/benchmarking.rs | 78 ++++++- modules/relayers/src/lib.rs | 8 +- modules/relayers/src/weights.rs | 322 ++++++++++++++++++--------- 3 files changed, 290 insertions(+), 118 deletions(-) diff --git a/modules/relayers/src/benchmarking.rs b/modules/relayers/src/benchmarking.rs index fe72710975..b06bcfaf77 100644 --- a/modules/relayers/src/benchmarking.rs +++ b/modules/relayers/src/benchmarking.rs @@ -23,6 +23,7 @@ use crate::*; use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Get; use frame_system::RawOrigin; use sp_runtime::traits::One; @@ -58,15 +59,45 @@ benchmarks! { // also completed successfully } + // Benchmark `increase_stake` call. + increase_stake { + let relayer: T::AccountId = whitelisted_caller(); + let stake = crate::Pallet::::base_stake(); + T::deposit_account(relayer.clone(), stake); + }: _(RawOrigin::Signed(relayer.clone()), stake) + verify { + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.current_stake()), + Some(stake), + ); + } + + // Benchmark `decrease_stake` call. + decrease_stake { + let relayer: T::AccountId = whitelisted_caller(); + let base_stake = crate::Pallet::::base_stake(); + let stake = base_stake.saturating_add(100u32.into()); + T::deposit_account(relayer.clone(), stake); + crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), stake).unwrap(); + }: _(RawOrigin::Signed(relayer.clone()), 100u32.into()) + verify { + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.current_stake()), + Some(base_stake), + ); + } + // Benchmark `register` call. register { let relayer: T::AccountId = whitelisted_caller(); + let base_stake = crate::Pallet::::base_stake(); let valid_till = frame_system::Pallet::::block_number() .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + T::deposit_account(relayer.clone(), base_stake); + crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), base_stake).unwrap(); }: _(RawOrigin::Signed(relayer.clone()), valid_till) verify { assert!(crate::Pallet::::is_registration_active(&relayer)); @@ -75,11 +106,13 @@ benchmarks! { // Benchmark `deregister` call. deregister { let relayer: T::AccountId = whitelisted_caller(); + let base_stake = crate::Pallet::::base_stake(); let valid_till = frame_system::Pallet::::block_number() .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + T::deposit_account(relayer.clone(), base_stake); + crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), base_stake).unwrap(); crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); frame_system::Pallet::::set_block_number(valid_till.saturating_add(One::one())); @@ -88,19 +121,58 @@ benchmarks! { assert!(!crate::Pallet::::is_registration_active(&relayer)); } + // Benchmark `register_at_lane` call. The worst case for this call is when: + // + // - relayer has `T::MaxLanesPerRelayer::get() - 1` lane registrations; + // + // - there are no other relayers registered at that lane yet; + register_at_lane { + let relayer: T::AccountId = whitelisted_caller(); + let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); + let stake = crate::Pallet::::base_stake().saturating_add(crate::Pallet::::stake_per_lane().saturating_mul(max_lanes_per_relayer.into())); + let valid_till = frame_system::Pallet::::block_number() + .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(One::one()) + .saturating_add(One::one()); + T::deposit_account(relayer.clone(), stake); + crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), stake).unwrap(); + crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); + + for i in 0..max_lanes_per_relayer - 1 { + let lane_id = LaneId::new(i, i); + crate::Pallet::::register_at_lane(RawOrigin::Signed(relayer.clone()).into(), lane_id, 0).unwrap(); + } + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), + Some(max_lanes_per_relayer - 1), + ); + + let lane_id = LaneId::new(max_lanes_per_relayer - 1, max_lanes_per_relayer - 1); + }: _(RawOrigin::Signed(relayer.clone()), lane_id, 0) + verify { + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), + Some(max_lanes_per_relayer), + ); + } + // Benchmark `slash_and_deregister` method of the pallet. We are adding this weight to // the weight of message delivery call if `RefundBridgedParachainMessages` signed extension // is deployed at runtime level. slash_and_deregister { // prepare and register relayer account let relayer: T::AccountId = whitelisted_caller(); + let base_stake = crate::Pallet::::base_stake(); let valid_till = frame_system::Pallet::::block_number() .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + T::deposit_account(relayer.clone(), base_stake); + crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), base_stake).unwrap(); crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); + // TODO: add max number of lane registrations + // create slash destination account let lane = LaneId::new(1, 2); let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 7f0cbed2e3..5f853b85e8 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -200,7 +200,7 @@ pub mod pallet { /// This call must be followed by `register` and (optionally) `register_at_lane` calls to /// activate priority boosts. #[pallet::call_index(1)] - #[pallet::weight(Weight::zero())] // TODO + #[pallet::weight(T::WeightInfo::increase_stake())] pub fn increase_stake( origin: OriginFor, additional_amount: T::Reward, @@ -230,7 +230,7 @@ pub mod pallet { /// The reserved amount after this call must cover basic registration and all lane /// registrations that relayer has. #[pallet::call_index(2)] - #[pallet::weight(Weight::zero())] // TODO + #[pallet::weight(T::WeightInfo::decrease_stake())] pub fn decrease_stake(origin: OriginFor, to_unreserve: T::Reward) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -375,7 +375,7 @@ pub mod pallet { /// [`DeliveryConfirmationPaymentsAdapter`] is used to register rewards, the maximal reward /// per message is limited by the `MaxRewardPerMessage` parameter. #[pallet::call_index(5)] - #[pallet::weight(Weight::zero())] // TODO + #[pallet::weight(T::WeightInfo::register_at_lane())] pub fn register_at_lane( origin: OriginFor, lane: LaneId, @@ -692,7 +692,7 @@ pub mod pallet { } /// Returns total stake that the relayer must hold reserved on his account. - fn required_stake( + pub(crate) fn required_stake( registration: &Registration, T::Reward, T::MaxLanesPerRelayer>, ) -> T::Reward { registration.required_stake(Self::base_stake(), Self::stake_per_lane()) diff --git a/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs index 2e064a3936..e59f3620c7 100644 --- a/modules/relayers/src/weights.rs +++ b/modules/relayers/src/weights.rs @@ -1,4 +1,4 @@ -// Copyright (C) Parity Technologies (UK) Ltd. +// 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 @@ -17,10 +17,10 @@ //! Autogenerated weights for pallet_bridge_relayers //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-04-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! HOSTNAME: `MusXroom`, CPU: `13th Gen Intel(R) Core(TM) i7-13650HX` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // target/release/millau-bridge-node @@ -31,7 +31,6 @@ // --repeat=20 // --pallet=pallet_bridge_relayers // --extrinsic=* -// --execution=wasm // --wasm-execution=Compiled // --heap-pages=4096 // --output=./modules/relayers/src/weights.rs @@ -51,8 +50,11 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_bridge_relayers. pub trait WeightInfo { fn claim_rewards() -> Weight; + fn increase_stake() -> Weight; + fn decrease_stake() -> Weight; fn register() -> Weight; fn deregister() -> Weight; + fn register_at_lane() -> Weight; fn slash_and_deregister() -> Weight; fn register_relayer_reward() -> Weight; } @@ -62,98 +64,147 @@ pub trait WeightInfo { /// Those weights are test only and must never be used in production. pub struct BridgeWeight(PhantomData); impl WeightInfo for BridgeWeight { - /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) + /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// - /// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(93), added: + /// 2568, mode: `MaxEncodedLen`) /// - /// Storage: Balances TotalIssuance (r:1 w:0) + /// Storage: `Balances::TotalIssuance` (r:1 w:0) /// - /// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode: - /// MaxEncodedLen) + /// Proof: `Balances::TotalIssuance` (`max_values`: Some(1), `max_size`: Some(8), added: 503, + /// mode: `MaxEncodedLen`) /// - /// Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) /// - /// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode: - /// MaxEncodedLen) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: + /// `MaxEncodedLen`) fn claim_rewards() -> Weight { // Proof Size summary in bytes: - // Measured: `294` - // Estimated: `8592` - // Minimum execution time: 77_614 nanoseconds. - Weight::from_parts(79_987_000, 8592) + // Measured: `388` + // Estimated: `3569` + // Minimum execution time: 56_947 nanoseconds. + Weight::from_parts(59_061_000, 3569) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Storage: `Balances::Reserves` (r:1 w:1) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) - fn register() -> Weight { + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) + fn increase_stake() -> Weight { // Proof Size summary in bytes: - // Measured: `87` - // Estimated: `7843` - // Minimum execution time: 39_590 nanoseconds. - Weight::from_parts(40_546_000, 7843) + // Measured: `153` + // Estimated: `4314` + // Minimum execution time: 23_794 nanoseconds. + Weight::from_parts(24_585_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// Storage: `Balances::Reserves` (r:1 w:1) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) + fn decrease_stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `331` + // Estimated: `4314` + // Minimum execution time: 27_209 nanoseconds. + Weight::from_parts(28_170_000, 4314) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + fn register() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `4042` + // Minimum execution time: 14_691 nanoseconds. + Weight::from_parts(15_254_000, 4042) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + /// + /// Storage: `Balances::Reserves` (r:1 w:1) + /// + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) fn deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `264` - // Estimated: `7843` - // Minimum execution time: 43_332 nanoseconds. - Weight::from_parts(45_087_000, 7843) + // Measured: `331` + // Estimated: `4314` + // Minimum execution time: 33_636 nanoseconds. + Weight::from_parts(35_039_000, 4314) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + fn register_at_lane() -> Weight { + // Proof Size summary in bytes: + // Measured: `1268` + // Estimated: `44467` + // Minimum execution time: 17_632 nanoseconds. + Weight::from_parts(18_289_000, 44467) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Storage: `Balances::Reserves` (r:1 w:1) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) /// - /// Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) /// - /// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode: - /// MaxEncodedLen) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: + /// `MaxEncodedLen`) fn slash_and_deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `380` - // Estimated: `11412` - // Minimum execution time: 42_358 nanoseconds. - Weight::from_parts(43_539_000, 11412) + // Measured: `447` + // Estimated: `4314` + // Minimum execution time: 34_143 nanoseconds. + Weight::from_parts(34_940_000, 4314) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) + /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// - /// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(93), added: + /// 2568, mode: `MaxEncodedLen`) fn register_relayer_reward() -> Weight { // Proof Size summary in bytes: // Measured: `12` - // Estimated: `3530` - // Minimum execution time: 6_338 nanoseconds. - Weight::from_parts(6_526_000, 3530) + // Estimated: `3558` + // Minimum execution time: 4_418 nanoseconds. + Weight::from_parts(4_667_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -161,98 +212,147 @@ impl WeightInfo for BridgeWeight { // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) + /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// - /// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(93), added: + /// 2568, mode: `MaxEncodedLen`) /// - /// Storage: Balances TotalIssuance (r:1 w:0) + /// Storage: `Balances::TotalIssuance` (r:1 w:0) /// - /// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode: - /// MaxEncodedLen) + /// Proof: `Balances::TotalIssuance` (`max_values`: Some(1), `max_size`: Some(8), added: 503, + /// mode: `MaxEncodedLen`) /// - /// Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) /// - /// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode: - /// MaxEncodedLen) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: + /// `MaxEncodedLen`) fn claim_rewards() -> Weight { // Proof Size summary in bytes: - // Measured: `294` - // Estimated: `8592` - // Minimum execution time: 77_614 nanoseconds. - Weight::from_parts(79_987_000, 8592) + // Measured: `388` + // Estimated: `3569` + // Minimum execution time: 56_947 nanoseconds. + Weight::from_parts(59_061_000, 3569) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Storage: `Balances::Reserves` (r:1 w:1) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) - fn register() -> Weight { + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) + fn increase_stake() -> Weight { // Proof Size summary in bytes: - // Measured: `87` - // Estimated: `7843` - // Minimum execution time: 39_590 nanoseconds. - Weight::from_parts(40_546_000, 7843) + // Measured: `153` + // Estimated: `4314` + // Minimum execution time: 23_794 nanoseconds. + Weight::from_parts(24_585_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// Storage: `Balances::Reserves` (r:1 w:1) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) + fn decrease_stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `331` + // Estimated: `4314` + // Minimum execution time: 27_209 nanoseconds. + Weight::from_parts(28_170_000, 4314) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + fn register() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `4042` + // Minimum execution time: 14_691 nanoseconds. + Weight::from_parts(15_254_000, 4042) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + /// + /// Storage: `Balances::Reserves` (r:1 w:1) + /// + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) fn deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `264` - // Estimated: `7843` - // Minimum execution time: 43_332 nanoseconds. - Weight::from_parts(45_087_000, 7843) + // Measured: `331` + // Estimated: `4314` + // Minimum execution time: 33_636 nanoseconds. + Weight::from_parts(35_039_000, 4314) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + fn register_at_lane() -> Weight { + // Proof Size summary in bytes: + // Measured: `1268` + // Estimated: `44467` + // Minimum execution time: 17_632 nanoseconds. + Weight::from_parts(18_289_000, 44467) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Storage: `Balances::Reserves` (r:1 w:1) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: + /// `MaxEncodedLen`) /// - /// Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) /// - /// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode: - /// MaxEncodedLen) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(104), added: 2579, mode: + /// `MaxEncodedLen`) fn slash_and_deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `380` - // Estimated: `11412` - // Minimum execution time: 42_358 nanoseconds. - Weight::from_parts(43_539_000, 11412) + // Measured: `447` + // Estimated: `4314` + // Minimum execution time: 34_143 nanoseconds. + Weight::from_parts(34_940_000, 4314) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) + /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// - /// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540, - /// mode: MaxEncodedLen) + /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(93), added: + /// 2568, mode: `MaxEncodedLen`) fn register_relayer_reward() -> Weight { // Proof Size summary in bytes: // Measured: `12` - // Estimated: `3530` - // Minimum execution time: 6_338 nanoseconds. - Weight::from_parts(6_526_000, 3530) + // Estimated: `3558` + // Minimum execution time: 4_418 nanoseconds. + Weight::from_parts(4_667_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From 94018e2936bdcfbf50ec8810965e6a2f39c41079 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 09:31:19 +0300 Subject: [PATCH 49/60] deregister_at_lane benchmark --- modules/relayers/src/benchmarking.rs | 81 ++++++++++++------- modules/relayers/src/lib.rs | 2 +- modules/relayers/src/weights.rs | 115 +++++++++++++++++++-------- 3 files changed, 135 insertions(+), 63 deletions(-) diff --git a/modules/relayers/src/benchmarking.rs b/modules/relayers/src/benchmarking.rs index b06bcfaf77..f0f8197e8b 100644 --- a/modules/relayers/src/benchmarking.rs +++ b/modules/relayers/src/benchmarking.rs @@ -24,7 +24,7 @@ use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_support::traits::Get; -use frame_system::RawOrigin; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_runtime::traits::One; /// Reward amount that is (hopefully) is larger than existential deposit across all chains. @@ -41,6 +41,43 @@ pub trait Config: crate::Config { fn deposit_account(account: Self::AccountId, balance: Self::Reward); } +/// Return lane id that we use in tests. +fn lane_id(i: u32) -> LaneId { + LaneId::new(i, i) +} + +/// Return block number until which our test registration is considered valid. +fn valid_till() -> BlockNumberFor { + frame_system::Pallet::::block_number() + .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(One::one()) + .saturating_add(One::one()) +} + +/// Add basic relayer registration and optionally lane registrations. +fn register_relayer(relayer: &T::AccountId, lanes_reg_count: u32) { + let stake = crate::Pallet::::base_stake().saturating_add( + crate::Pallet::::stake_per_lane().saturating_mul((lanes_reg_count + 1).into()), + ); + T::deposit_account(relayer.clone(), stake); + crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), stake).unwrap(); + crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till::()) + .unwrap(); + + for i in 0..lanes_reg_count { + crate::Pallet::::register_at_lane( + RawOrigin::Signed(relayer.clone()).into(), + lane_id(i), + 0, + ) + .unwrap(); + } + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), + Some(lanes_reg_count), + ); +} + benchmarks! { // Benchmark `claim_rewards` call. claim_rewards { @@ -106,16 +143,8 @@ benchmarks! { // Benchmark `deregister` call. deregister { let relayer: T::AccountId = whitelisted_caller(); - let base_stake = crate::Pallet::::base_stake(); - let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) - .saturating_add(One::one()) - .saturating_add(One::one()); - T::deposit_account(relayer.clone(), base_stake); - crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), base_stake).unwrap(); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); - - frame_system::Pallet::::set_block_number(valid_till.saturating_add(One::one())); + register_relayer::(&relayer, 0); + frame_system::Pallet::::set_block_number(valid_till::().saturating_add(One::one())); }: _(RawOrigin::Signed(relayer.clone())) verify { assert!(!crate::Pallet::::is_registration_active(&relayer)); @@ -129,30 +158,26 @@ benchmarks! { register_at_lane { let relayer: T::AccountId = whitelisted_caller(); let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); - let stake = crate::Pallet::::base_stake().saturating_add(crate::Pallet::::stake_per_lane().saturating_mul(max_lanes_per_relayer.into())); - let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) - .saturating_add(One::one()) - .saturating_add(One::one()); - T::deposit_account(relayer.clone(), stake); - crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), stake).unwrap(); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); - - for i in 0..max_lanes_per_relayer - 1 { - let lane_id = LaneId::new(i, i); - crate::Pallet::::register_at_lane(RawOrigin::Signed(relayer.clone()).into(), lane_id, 0).unwrap(); - } + register_relayer::(&relayer, max_lanes_per_relayer - 1); + }: _(RawOrigin::Signed(relayer.clone()), lane_id(max_lanes_per_relayer), 0) + verify { assert_eq!( crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), - Some(max_lanes_per_relayer - 1), + Some(max_lanes_per_relayer), ); + } - let lane_id = LaneId::new(max_lanes_per_relayer - 1, max_lanes_per_relayer - 1); - }: _(RawOrigin::Signed(relayer.clone()), lane_id, 0) + // Benchmark `deregister_at_lane` call. The worst case for this call is when relayer is not in + // the active relayers set. + deregister_at_lane { + let relayer: T::AccountId = whitelisted_caller(); + let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); + register_relayer::(&relayer, max_lanes_per_relayer); + }: _(RawOrigin::Signed(relayer.clone()), lane_id(0)) verify { assert_eq!( crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), - Some(max_lanes_per_relayer), + Some(max_lanes_per_relayer - 1), ); } diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 5f853b85e8..16a3b927e2 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -429,7 +429,7 @@ pub mod pallet { /// priority boost for his message delivery transaction at that lane. The relayer will be /// able to claim his lane stake back when it is removed from both active and the next set. #[pallet::call_index(6)] - #[pallet::weight(Weight::zero())] // TODO + #[pallet::weight(T::WeightInfo::deregister_at_lane())] pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { let relayer = ensure_signed(origin)?; diff --git a/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs index e59f3620c7..ae5228e186 100644 --- a/modules/relayers/src/weights.rs +++ b/modules/relayers/src/weights.rs @@ -55,6 +55,7 @@ pub trait WeightInfo { fn register() -> Weight; fn deregister() -> Weight; fn register_at_lane() -> Weight; + fn deregister_at_lane() -> Weight; fn slash_and_deregister() -> Weight; fn register_relayer_reward() -> Weight; } @@ -82,8 +83,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_947 nanoseconds. - Weight::from_parts(59_061_000, 3569) + // Minimum execution time: 56_459 nanoseconds. + Weight::from_parts(57_754_000, 3569) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -100,8 +101,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_794 nanoseconds. - Weight::from_parts(24_585_000, 4314) + // Minimum execution time: 23_586 nanoseconds. + Weight::from_parts(24_587_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -118,8 +119,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 27_209 nanoseconds. - Weight::from_parts(28_170_000, 4314) + // Minimum execution time: 25_939 nanoseconds. + Weight::from_parts(26_935_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -131,8 +132,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_691 nanoseconds. - Weight::from_parts(15_254_000, 4042) + // Minimum execution time: 14_369 nanoseconds. + Weight::from_parts(14_874_000, 4042) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -149,8 +150,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 33_636 nanoseconds. - Weight::from_parts(35_039_000, 4314) + // Minimum execution time: 32_461 nanoseconds. + Weight::from_parts(34_086_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -165,13 +166,36 @@ impl WeightInfo for BridgeWeight { /// added: 43477, mode: `MaxEncodedLen`) fn register_at_lane() -> Weight { // Proof Size summary in bytes: - // Measured: `1268` + // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 17_632 nanoseconds. - Weight::from_parts(18_289_000, 44467) + // Minimum execution time: 18_336 nanoseconds. + Weight::from_parts(18_818_000, 44467) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:0) + /// + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), + /// added: 3669, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + fn deregister_at_lane() -> Weight { + // Proof Size summary in bytes: + // Measured: `1596` + // Estimated: `44467` + // Minimum execution time: 19_207 nanoseconds. + Weight::from_parts(19_880_000, 44467) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), @@ -190,8 +214,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `447` // Estimated: `4314` - // Minimum execution time: 34_143 nanoseconds. - Weight::from_parts(34_940_000, 4314) + // Minimum execution time: 33_131 nanoseconds. + Weight::from_parts(34_359_000, 4314) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -203,8 +227,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_418 nanoseconds. - Weight::from_parts(4_667_000, 3558) + // Minimum execution time: 4_335 nanoseconds. + Weight::from_parts(4_591_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -230,8 +254,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_947 nanoseconds. - Weight::from_parts(59_061_000, 3569) + // Minimum execution time: 56_459 nanoseconds. + Weight::from_parts(57_754_000, 3569) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -248,8 +272,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_794 nanoseconds. - Weight::from_parts(24_585_000, 4314) + // Minimum execution time: 23_586 nanoseconds. + Weight::from_parts(24_587_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -266,8 +290,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 27_209 nanoseconds. - Weight::from_parts(28_170_000, 4314) + // Minimum execution time: 25_939 nanoseconds. + Weight::from_parts(26_935_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -279,8 +303,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_691 nanoseconds. - Weight::from_parts(15_254_000, 4042) + // Minimum execution time: 14_369 nanoseconds. + Weight::from_parts(14_874_000, 4042) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -297,8 +321,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 33_636 nanoseconds. - Weight::from_parts(35_039_000, 4314) + // Minimum execution time: 32_461 nanoseconds. + Weight::from_parts(34_086_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -313,13 +337,36 @@ impl WeightInfo for () { /// added: 43477, mode: `MaxEncodedLen`) fn register_at_lane() -> Weight { // Proof Size summary in bytes: - // Measured: `1268` + // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 17_632 nanoseconds. - Weight::from_parts(18_289_000, 44467) + // Minimum execution time: 18_336 nanoseconds. + Weight::from_parts(18_818_000, 44467) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:0) + /// + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), + /// added: 3669, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + fn deregister_at_lane() -> Weight { + // Proof Size summary in bytes: + // Measured: `1596` + // Estimated: `44467` + // Minimum execution time: 19_207 nanoseconds. + Weight::from_parts(19_880_000, 44467) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), @@ -338,8 +385,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `447` // Estimated: `4314` - // Minimum execution time: 34_143 nanoseconds. - Weight::from_parts(34_940_000, 4314) + // Minimum execution time: 33_131 nanoseconds. + Weight::from_parts(34_359_000, 4314) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -351,8 +398,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_418 nanoseconds. - Weight::from_parts(4_667_000, 3558) + // Minimum execution time: 4_335 nanoseconds. + Weight::from_parts(4_591_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From 48504d9c6178c58fd1a6280377d9cbdb2dbed9af Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 10:12:55 +0300 Subject: [PATCH 50/60] advance_lane_epoch benchmark --- modules/relayers/src/benchmarking.rs | 70 ++++++++++++++-- modules/relayers/src/lib.rs | 2 +- modules/relayers/src/weights.rs | 121 +++++++++++++++++++-------- 3 files changed, 149 insertions(+), 44 deletions(-) diff --git a/modules/relayers/src/benchmarking.rs b/modules/relayers/src/benchmarking.rs index f0f8197e8b..11b7f4a0c8 100644 --- a/modules/relayers/src/benchmarking.rs +++ b/modules/relayers/src/benchmarking.rs @@ -22,10 +22,11 @@ use crate::*; use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_support::traits::Get; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_runtime::traits::One; +use sp_std::vec::Vec; /// Reward amount that is (hopefully) is larger than existential deposit across all chains. const REWARD_AMOUNT: u32 = u32::MAX; @@ -55,7 +56,11 @@ fn valid_till() -> BlockNumberFor { } /// Add basic relayer registration and optionally lane registrations. -fn register_relayer(relayer: &T::AccountId, lanes_reg_count: u32) { +fn register_relayer( + relayer: &T::AccountId, + lanes_reg_count: u32, + expected_reward: RelayerRewardAtSource, +) { let stake = crate::Pallet::::base_stake().saturating_add( crate::Pallet::::stake_per_lane().saturating_mul((lanes_reg_count + 1).into()), ); @@ -68,7 +73,7 @@ fn register_relayer(relayer: &T::AccountId, lanes_reg_count: u32) { crate::Pallet::::register_at_lane( RawOrigin::Signed(relayer.clone()).into(), lane_id(i), - 0, + expected_reward, ) .unwrap(); } @@ -143,7 +148,7 @@ benchmarks! { // Benchmark `deregister` call. deregister { let relayer: T::AccountId = whitelisted_caller(); - register_relayer::(&relayer, 0); + register_relayer::(&relayer, 0, 0); frame_system::Pallet::::set_block_number(valid_till::().saturating_add(One::one())); }: _(RawOrigin::Signed(relayer.clone())) verify { @@ -158,7 +163,7 @@ benchmarks! { register_at_lane { let relayer: T::AccountId = whitelisted_caller(); let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); - register_relayer::(&relayer, max_lanes_per_relayer - 1); + register_relayer::(&relayer, max_lanes_per_relayer - 1, 0); }: _(RawOrigin::Signed(relayer.clone()), lane_id(max_lanes_per_relayer), 0) verify { assert_eq!( @@ -172,7 +177,7 @@ benchmarks! { deregister_at_lane { let relayer: T::AccountId = whitelisted_caller(); let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); - register_relayer::(&relayer, max_lanes_per_relayer); + register_relayer::(&relayer, max_lanes_per_relayer, 0); }: _(RawOrigin::Signed(relayer.clone()), lane_id(0)) verify { assert_eq!( @@ -181,6 +186,59 @@ benchmarks! { ); } + // Benchmark `advance_lane_epoch` call. The worst case for this call is when active set is completely + // replaced with a next set. + advance_lane_epoch { + let current_block_number = frame_system::Pallet::::block_number(); + + // prepare active relayers set with max possible relayers count + let max_active_relayers_per_lane = T::MaxActiveRelayersPerLane::get(); + let active_relayers = (0..max_active_relayers_per_lane) + .map(|i| account("relayer", i, 0)) + .collect::>(); + let mut active_relayers_set = ActiveLaneRelayersSet::<_, BlockNumberFor, T::MaxActiveRelayersPerLane>::default(); + let mut next_relayers_set = NextLaneRelayersSet::<_, BlockNumberFor, T::MaxNextRelayersPerLane>::empty( + current_block_number, + ); + for active_relayer in &active_relayers { + register_relayer::(active_relayer, 1, 1); + assert!(next_relayers_set.try_push(active_relayer.clone(), 0)); + } + active_relayers_set.activate_next_set(current_block_number, next_relayers_set, |_| true); + ActiveLaneRelayers::::insert(lane_id(0), active_relayers_set); + + // prepare next relayers set with max possible relayers count + let max_next_relayers_per_lane = T::MaxNextRelayersPerLane::get(); + let next_relayers = (0..max_next_relayers_per_lane) + .map(|i| account::("relayer", max_active_relayers_per_lane + i, 0)) + .collect::>(); + for next_relayer in &next_relayers { + register_relayer::(next_relayer, 1, 0); + } + + // set next block to block where next set can be activated + frame_system::Pallet::::set_block_number( + NextLaneRelayers::::get(lane_id(0)).unwrap().may_enact_at(), + ); + }: _(RawOrigin::Signed(whitelisted_caller()), lane_id(0)) + verify { + // active relayers are replaced with next relayers + assert_eq!( + crate::Pallet::::active_lane_relayers(&lane_id(0)) + .relayers() + .iter() + .map(|r| r.relayer()) + .collect::>(), + next_relayers.iter().take(max_active_relayers_per_lane as _).collect::>(), + ); + + // all (previous) active relayers have no lane registration + active_relayers.into_iter().all(|r| crate::Pallet::::registered_relayer(&r).unwrap().lanes().len() == 0); + + // all (previous) next relayers have no lane registration + next_relayers.into_iter().all(|r| crate::Pallet::::registered_relayer(&r).unwrap().lanes().len() == 1); + } + // Benchmark `slash_and_deregister` method of the pallet. We are adding this weight to // the weight of message delivery call if `RefundBridgedParachainMessages` signed extension // is deployed at runtime level. diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 16a3b927e2..0890621a33 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -472,7 +472,7 @@ pub mod pallet { /// anyone will be accepted and it will have the zero cost. All subsequent transactions will /// be paid. We suggest the first relayer from the `next_set` to submit this transaction. #[pallet::call_index(7)] - #[pallet::weight(Weight::zero())] // TODO + #[pallet::weight(T::WeightInfo::advance_lane_epoch())] pub fn advance_lane_epoch( origin: OriginFor, lane: LaneId, diff --git a/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs index ae5228e186..ce69f7463b 100644 --- a/modules/relayers/src/weights.rs +++ b/modules/relayers/src/weights.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for pallet_bridge_relayers //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `MusXroom`, CPU: `13th Gen Intel(R) Core(TM) i7-13650HX` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -56,6 +56,7 @@ pub trait WeightInfo { fn deregister() -> Weight; fn register_at_lane() -> Weight; fn deregister_at_lane() -> Weight; + fn advance_lane_epoch() -> Weight; fn slash_and_deregister() -> Weight; fn register_relayer_reward() -> Weight; } @@ -83,8 +84,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_459 nanoseconds. - Weight::from_parts(57_754_000, 3569) + // Minimum execution time: 56_090 nanoseconds. + Weight::from_parts(58_064_000, 3569) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -101,8 +102,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_586 nanoseconds. - Weight::from_parts(24_587_000, 4314) + // Minimum execution time: 23_930 nanoseconds. + Weight::from_parts(24_853_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -119,8 +120,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 25_939 nanoseconds. - Weight::from_parts(26_935_000, 4314) + // Minimum execution time: 26_571 nanoseconds. + Weight::from_parts(27_640_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -132,8 +133,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_369 nanoseconds. - Weight::from_parts(14_874_000, 4042) + // Minimum execution time: 14_196 nanoseconds. + Weight::from_parts(15_060_000, 4042) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -150,8 +151,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 32_461 nanoseconds. - Weight::from_parts(34_086_000, 4314) + // Minimum execution time: 33_606 nanoseconds. + Weight::from_parts(34_687_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -168,8 +169,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 18_336 nanoseconds. - Weight::from_parts(18_818_000, 44467) + // Minimum execution time: 18_000 nanoseconds. + Weight::from_parts(18_890_000, 44467) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -191,11 +192,34 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `44467` - // Minimum execution time: 19_207 nanoseconds. - Weight::from_parts(19_880_000, 44467) + // Minimum execution time: 19_446 nanoseconds. + Weight::from_parts(20_114_000, 44467) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), + /// added: 3669, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:16 w:16) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + fn advance_lane_epoch() -> Weight { + // Proof Size summary in bytes: + // Measured: `56725` + // Estimated: `49822` + // Minimum execution time: 214_569 nanoseconds. + Weight::from_parts(216_164_000, 49822) + .saturating_add(T::DbWeight::get().reads(18_u64)) + .saturating_add(T::DbWeight::get().writes(18_u64)) + } /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), @@ -214,8 +238,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `447` // Estimated: `4314` - // Minimum execution time: 33_131 nanoseconds. - Weight::from_parts(34_359_000, 4314) + // Minimum execution time: 33_346 nanoseconds. + Weight::from_parts(34_066_000, 4314) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -227,8 +251,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_335 nanoseconds. - Weight::from_parts(4_591_000, 3558) + // Minimum execution time: 4_227 nanoseconds. + Weight::from_parts(4_413_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -254,8 +278,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_459 nanoseconds. - Weight::from_parts(57_754_000, 3569) + // Minimum execution time: 56_090 nanoseconds. + Weight::from_parts(58_064_000, 3569) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -272,8 +296,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_586 nanoseconds. - Weight::from_parts(24_587_000, 4314) + // Minimum execution time: 23_930 nanoseconds. + Weight::from_parts(24_853_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -290,8 +314,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 25_939 nanoseconds. - Weight::from_parts(26_935_000, 4314) + // Minimum execution time: 26_571 nanoseconds. + Weight::from_parts(27_640_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -303,8 +327,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_369 nanoseconds. - Weight::from_parts(14_874_000, 4042) + // Minimum execution time: 14_196 nanoseconds. + Weight::from_parts(15_060_000, 4042) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -321,8 +345,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 32_461 nanoseconds. - Weight::from_parts(34_086_000, 4314) + // Minimum execution time: 33_606 nanoseconds. + Weight::from_parts(34_687_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -339,8 +363,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 18_336 nanoseconds. - Weight::from_parts(18_818_000, 44467) + // Minimum execution time: 18_000 nanoseconds. + Weight::from_parts(18_890_000, 44467) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -362,11 +386,34 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `44467` - // Minimum execution time: 19_207 nanoseconds. - Weight::from_parts(19_880_000, 44467) + // Minimum execution time: 19_446 nanoseconds. + Weight::from_parts(20_114_000, 44467) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), + /// added: 3669, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:16 w:16) + /// + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) + fn advance_lane_epoch() -> Weight { + // Proof Size summary in bytes: + // Measured: `56725` + // Estimated: `49822` + // Minimum execution time: 214_569 nanoseconds. + Weight::from_parts(216_164_000, 49822) + .saturating_add(RocksDbWeight::get().reads(18_u64)) + .saturating_add(RocksDbWeight::get().writes(18_u64)) + } /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), @@ -385,8 +432,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `447` // Estimated: `4314` - // Minimum execution time: 33_131 nanoseconds. - Weight::from_parts(34_359_000, 4314) + // Minimum execution time: 33_346 nanoseconds. + Weight::from_parts(34_066_000, 4314) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -398,8 +445,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_335 nanoseconds. - Weight::from_parts(4_591_000, 3558) + // Minimum execution time: 4_227 nanoseconds. + Weight::from_parts(4_413_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From e23557424e74f9130f59eca2de929fe121d8abbc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 10:19:52 +0300 Subject: [PATCH 51/60] fixed slash_and_deregister benchmark --- modules/relayers/src/benchmarking.rs | 35 +++++--- modules/relayers/src/weights.rs | 116 ++++++++++++++++----------- 2 files changed, 93 insertions(+), 58 deletions(-) diff --git a/modules/relayers/src/benchmarking.rs b/modules/relayers/src/benchmarking.rs index 11b7f4a0c8..f6847a3bee 100644 --- a/modules/relayers/src/benchmarking.rs +++ b/modules/relayers/src/benchmarking.rs @@ -245,16 +245,18 @@ benchmarks! { slash_and_deregister { // prepare and register relayer account let relayer: T::AccountId = whitelisted_caller(); - let base_stake = crate::Pallet::::base_stake(); - let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) - .saturating_add(One::one()) - .saturating_add(One::one()); - T::deposit_account(relayer.clone(), base_stake); - crate::Pallet::::increase_stake(RawOrigin::Signed(relayer.clone()).into(), base_stake).unwrap(); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); - - // TODO: add max number of lane registrations + let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); + register_relayer::(&relayer, max_lanes_per_relayer, 1); + + // also register relayer in next lane relayers set (with better bid) + for i in 0..max_lanes_per_relayer { + crate::Pallet::::register_at_lane( + RawOrigin::Signed(relayer.clone()).into(), + lane_id(i), + 0, + ) + .unwrap(); + } // create slash destination account let lane = LaneId::new(1, 2); @@ -265,6 +267,19 @@ benchmarks! { } verify { assert!(!crate::Pallet::::is_registration_active(&relayer)); + for i in 0..max_lanes_per_relayer { + assert!( + crate::Pallet::::active_lane_relayers(lane_id(i)) + .relayer(&relayer) + .is_none(), + ); + assert!( + crate::Pallet::::next_lane_relayers(lane_id(i)) + .unwrap_or_else(|| NextLaneRelayersSet::empty(Zero::zero())) + .relayer(&relayer) + .is_none(), + ); + } } // Benchmark `register_relayer_reward` method of the pallet. We are adding this weight to diff --git a/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs index ce69f7463b..fff32d3aa6 100644 --- a/modules/relayers/src/weights.rs +++ b/modules/relayers/src/weights.rs @@ -84,8 +84,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_090 nanoseconds. - Weight::from_parts(58_064_000, 3569) + // Minimum execution time: 56_449 nanoseconds. + Weight::from_parts(57_453_000, 3569) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -102,8 +102,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_930 nanoseconds. - Weight::from_parts(24_853_000, 4314) + // Minimum execution time: 23_818 nanoseconds. + Weight::from_parts(24_830_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -120,8 +120,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 26_571 nanoseconds. - Weight::from_parts(27_640_000, 4314) + // Minimum execution time: 26_879 nanoseconds. + Weight::from_parts(27_588_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -133,8 +133,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_196 nanoseconds. - Weight::from_parts(15_060_000, 4042) + // Minimum execution time: 14_367 nanoseconds. + Weight::from_parts(14_873_000, 4042) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -151,8 +151,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 33_606 nanoseconds. - Weight::from_parts(34_687_000, 4314) + // Minimum execution time: 33_479 nanoseconds. + Weight::from_parts(34_648_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -169,8 +169,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 18_000 nanoseconds. - Weight::from_parts(18_890_000, 44467) + // Minimum execution time: 17_836 nanoseconds. + Weight::from_parts(18_517_000, 44467) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -192,8 +192,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `44467` - // Minimum execution time: 19_446 nanoseconds. - Weight::from_parts(20_114_000, 44467) + // Minimum execution time: 19_315 nanoseconds. + Weight::from_parts(19_951_000, 44467) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -215,8 +215,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `56725` // Estimated: `49822` - // Minimum execution time: 214_569 nanoseconds. - Weight::from_parts(216_164_000, 49822) + // Minimum execution time: 211_226 nanoseconds. + Weight::from_parts(219_707_000, 49822) .saturating_add(T::DbWeight::get().reads(18_u64)) .saturating_add(T::DbWeight::get().writes(18_u64)) } @@ -225,6 +225,16 @@ impl WeightInfo for BridgeWeight { /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), /// added: 3052, mode: `MaxEncodedLen`) /// + /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:16 w:16) + /// + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), + /// added: 3669, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:16 w:16) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + /// /// Storage: `Balances::Reserves` (r:1 w:1) /// /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: @@ -236,12 +246,12 @@ impl WeightInfo for BridgeWeight { /// `MaxEncodedLen`) fn slash_and_deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `4314` - // Minimum execution time: 33_346 nanoseconds. - Weight::from_parts(34_066_000, 4314) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + // Measured: `2395` + // Estimated: `696622` + // Minimum execution time: 101_108 nanoseconds. + Weight::from_parts(102_963_000, 696622) + .saturating_add(T::DbWeight::get().reads(35_u64)) + .saturating_add(T::DbWeight::get().writes(35_u64)) } /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// @@ -251,8 +261,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_227 nanoseconds. - Weight::from_parts(4_413_000, 3558) + // Minimum execution time: 4_310 nanoseconds. + Weight::from_parts(4_589_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -278,8 +288,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_090 nanoseconds. - Weight::from_parts(58_064_000, 3569) + // Minimum execution time: 56_449 nanoseconds. + Weight::from_parts(57_453_000, 3569) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -296,8 +306,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_930 nanoseconds. - Weight::from_parts(24_853_000, 4314) + // Minimum execution time: 23_818 nanoseconds. + Weight::from_parts(24_830_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -314,8 +324,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 26_571 nanoseconds. - Weight::from_parts(27_640_000, 4314) + // Minimum execution time: 26_879 nanoseconds. + Weight::from_parts(27_588_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -327,8 +337,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_196 nanoseconds. - Weight::from_parts(15_060_000, 4042) + // Minimum execution time: 14_367 nanoseconds. + Weight::from_parts(14_873_000, 4042) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -345,8 +355,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 33_606 nanoseconds. - Weight::from_parts(34_687_000, 4314) + // Minimum execution time: 33_479 nanoseconds. + Weight::from_parts(34_648_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -363,8 +373,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 18_000 nanoseconds. - Weight::from_parts(18_890_000, 44467) + // Minimum execution time: 17_836 nanoseconds. + Weight::from_parts(18_517_000, 44467) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -386,8 +396,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `44467` - // Minimum execution time: 19_446 nanoseconds. - Weight::from_parts(20_114_000, 44467) + // Minimum execution time: 19_315 nanoseconds. + Weight::from_parts(19_951_000, 44467) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -409,8 +419,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56725` // Estimated: `49822` - // Minimum execution time: 214_569 nanoseconds. - Weight::from_parts(216_164_000, 49822) + // Minimum execution time: 211_226 nanoseconds. + Weight::from_parts(219_707_000, 49822) .saturating_add(RocksDbWeight::get().reads(18_u64)) .saturating_add(RocksDbWeight::get().writes(18_u64)) } @@ -419,6 +429,16 @@ impl WeightInfo for () { /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), /// added: 3052, mode: `MaxEncodedLen`) /// + /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:16 w:16) + /// + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), + /// added: 3669, mode: `MaxEncodedLen`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:16 w:16) + /// + /// Proof: `BridgeRelayers::NextLaneRelayers` (`max_values`: None, `max_size`: Some(41002), + /// added: 43477, mode: `MaxEncodedLen`) + /// /// Storage: `Balances::Reserves` (r:1 w:1) /// /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(849), added: 3324, mode: @@ -430,12 +450,12 @@ impl WeightInfo for () { /// `MaxEncodedLen`) fn slash_and_deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `4314` - // Minimum execution time: 33_346 nanoseconds. - Weight::from_parts(34_066_000, 4314) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + // Measured: `2395` + // Estimated: `696622` + // Minimum execution time: 101_108 nanoseconds. + Weight::from_parts(102_963_000, 696622) + .saturating_add(RocksDbWeight::get().reads(35_u64)) + .saturating_add(RocksDbWeight::get().writes(35_u64)) } /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// @@ -445,8 +465,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_227 nanoseconds. - Weight::from_parts(4_413_000, 3558) + // Minimum execution time: 4_310 nanoseconds. + Weight::from_parts(4_589_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From 32186727fb83b9f6db1faa015090b9d3b5469eb3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 10:23:18 +0300 Subject: [PATCH 52/60] added small comment regarding MaxLanesPerRelayer --- modules/relayers/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 0890621a33..7519567ed8 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -74,7 +74,10 @@ pub mod pallet { /// Maximal number of lanes, where relayer may be registered. /// - /// This is an artificial limit that only exists to make PoV size predictable. + /// This is an artificial limit that only exists to make PoV size predictable. Large values + /// will make `slash_and_deregister` weight too large. Since message delivery/confirmation + /// transactions also include this weight, their weight is increased too. So it shall not be + /// too large. #[pallet::constant] type MaxLanesPerRelayer: Get; /// Maximal number of relayers that can reside in the active lane relayers set on a single From 00dc6b85abd7cba650a54657bce24c4032639aa7 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 11:01:20 +0300 Subject: [PATCH 53/60] removed already fixed TODO --- primitives/relayers/src/registration.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 8e1881d80f..4a0734f117 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -106,9 +106,6 @@ impl< /// `None` is returned if current block number does not affect registration and /// it shall be considered active regardless of it. pub fn valid_till(&self) -> Option { - // TODO: could be used by attacker to bump their current transaction priority while lease - // ends. We need to prolong valid_till at least by - // [`StakeAndSlash::RequiredRegistrationLease`] if lane is removed if self.lanes.is_empty() { Some(self.valid_till) } else { From 0d6d8a865d9339c60093a6c61ccee02d384b8ca6 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 12:24:17 +0300 Subject: [PATCH 54/60] relayer_reward_per_message is now Option --- bin/millau/runtime/src/lib.rs | 4 +- bin/rialto-parachain/runtime/src/lib.rs | 1 + bin/rialto/runtime/src/lib.rs | 1 + modules/messages/src/call_ext.rs | 4 +- modules/messages/src/inbound_lane.rs | 38 ++++++++-------- modules/messages/src/tests/mock.rs | 13 ++++-- modules/messages/src/tests/pallet_tests.rs | 18 +++++--- modules/relayers/src/extension/mod.rs | 2 +- modules/relayers/src/mock.rs | 3 ++ modules/relayers/src/payment_adapter.rs | 52 +++++++++++++++++----- primitives/messages/src/lib.rs | 47 +++++++++++-------- primitives/messages/src/target_chain.rs | 16 +++++-- 12 files changed, 132 insertions(+), 67 deletions(-) diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs index c389e4b086..6f67412038 100644 --- a/bin/millau/runtime/src/lib.rs +++ b/bin/millau/runtime/src/lib.rs @@ -487,6 +487,7 @@ impl pallet_bridge_messages::Config for Runtime { type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithRialtoMessagesInstance, + frame_support::traits::ConstU64<0>, frame_support::traits::ConstU64<100_000>, >; type OnMessagesDelivered = XcmRialtoBridgeHub; @@ -516,6 +517,7 @@ impl pallet_bridge_messages::Config for Run type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithRialtoParachainMessagesInstance, + frame_support::traits::ConstU64<0>, frame_support::traits::ConstU64<100_000>, >; type OnMessagesDelivered = XcmRialtoParachainBridgeHub; @@ -696,7 +698,7 @@ generate_bridge_reject_obsolete_headers_and_messages! { bp_runtime::generate_static_str_provider!(BridgeRefundRialtoPara2000Msgs); /// Signed extension that refunds relayers that are delivering messages from the Rialto parachain. -pub type PriorityBoostPerMessage = ConstU64<351_343_108>; +pub type PriorityBoostPerMessage = ConstU64<51_049_853>; pub type BridgeRefundRialtoParachainMessages = pallet_bridge_relayers::extension::BridgeRelayersSignedExtension< Runtime, diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs index 5d1ff4c97d..3161baf560 100644 --- a/bin/rialto-parachain/runtime/src/lib.rs +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -584,6 +584,7 @@ impl pallet_bridge_messages::Config for Runtime { type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithMillauMessagesInstance, + frame_support::traits::ConstU128<0>, frame_support::traits::ConstU128<100_000>, >; type OnMessagesDelivered = XcmMillauBridgeHub; diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs index 9b53970382..8b570f93cf 100644 --- a/bin/rialto/runtime/src/lib.rs +++ b/bin/rialto/runtime/src/lib.rs @@ -463,6 +463,7 @@ impl pallet_bridge_messages::Config for Runtime { type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithMillauMessagesInstance, + frame_support::traits::ConstU128<0>, frame_support::traits::ConstU128<100_000>, >; type OnMessagesDelivered = XcmMillauBridgeHub; diff --git a/modules/messages/src/call_ext.rs b/modules/messages/src/call_ext.rs index c1e3240e19..a71cdb6a18 100644 --- a/modules/messages/src/call_ext.rs +++ b/modules/messages/src/call_ext.rs @@ -264,7 +264,7 @@ mod tests { messages: DeliveredMessages { begin: n + 1, end: n + 1, - relayer_reward_per_message: 0, + relayer_reward_per_message: None, }, }); } @@ -278,7 +278,7 @@ mod tests { messages: DeliveredMessages { begin: 1, end: BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, - relayer_reward_per_message: 0, + relayer_reward_per_message: None, }, }); InboundLanes::::insert(test_lane_id(), inbound_lane_state); diff --git a/modules/messages/src/inbound_lane.rs b/modules/messages/src/inbound_lane.rs index c990fc7415..7610cfd4af 100644 --- a/modules/messages/src/inbound_lane.rs +++ b/modules/messages/src/inbound_lane.rs @@ -186,7 +186,7 @@ impl InboundLane { relayer_at_bridged_chain: &S::Relayer, nonce: MessageNonce, message_data: DispatchMessageData, - relayer_reward_per_message: RelayerRewardAtSource, + relayer_reward_per_message: Option, ) -> ReceivalResult { let mut data = self.storage.data(); if Some(nonce) != data.last_delivered_nonce().checked_add(1) { @@ -251,7 +251,7 @@ mod tests { &TEST_RELAYER_A, nonce, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -379,7 +379,7 @@ mod tests { &TEST_RELAYER_A, 10, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::InvalidNonce ); @@ -398,7 +398,7 @@ mod tests { &(TEST_RELAYER_A + current_nonce), current_nonce, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -409,7 +409,7 @@ mod tests { &(TEST_RELAYER_A + max_nonce + 1), max_nonce + 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::TooManyUnrewardedRelayers, ); @@ -419,7 +419,7 @@ mod tests { &(TEST_RELAYER_A + max_nonce), max_nonce + 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::TooManyUnrewardedRelayers, ); @@ -437,7 +437,7 @@ mod tests { &TEST_RELAYER_A, current_nonce, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -448,7 +448,7 @@ mod tests { &TEST_RELAYER_B, max_nonce + 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::TooManyUnconfirmedMessages, ); @@ -458,7 +458,7 @@ mod tests { &TEST_RELAYER_A, max_nonce + 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::TooManyUnconfirmedMessages, ); @@ -474,7 +474,7 @@ mod tests { &TEST_RELAYER_A, 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -483,7 +483,7 @@ mod tests { &TEST_RELAYER_B, 2, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -492,7 +492,7 @@ mod tests { &TEST_RELAYER_A, 3, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -516,7 +516,7 @@ mod tests { &TEST_RELAYER_A, 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -525,7 +525,7 @@ mod tests { &TEST_RELAYER_A, 2, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE + 1, + Some(RELAYER_REWARD_PER_MESSAGE + 1), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -534,7 +534,7 @@ mod tests { &TEST_RELAYER_A, 3, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE + 1, + Some(RELAYER_REWARD_PER_MESSAGE + 1), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -542,7 +542,7 @@ mod tests { let mut unrewarded_relayer_with_larger_reward = unrewarded_relayer(2, 3, TEST_RELAYER_A); unrewarded_relayer_with_larger_reward.messages.relayer_reward_per_message = - RELAYER_REWARD_PER_MESSAGE + 1; + Some(RELAYER_REWARD_PER_MESSAGE + 1); assert_eq!( lane.storage.data().relayers, vec![ @@ -562,7 +562,7 @@ mod tests { &TEST_RELAYER_A, 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(0)) ); @@ -571,7 +571,7 @@ mod tests { &TEST_RELAYER_B, 1, inbound_message_data(REGULAR_PAYLOAD), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::InvalidNonce, ); @@ -598,7 +598,7 @@ mod tests { &TEST_RELAYER_A, 1, inbound_message_data(payload), - RELAYER_REWARD_PER_MESSAGE, + Some(RELAYER_REWARD_PER_MESSAGE), ), ReceivalResult::Dispatched(dispatch_result(1)) ); diff --git a/modules/messages/src/tests/mock.rs b/modules/messages/src/tests/mock.rs index 4e48061d95..1392039656 100644 --- a/modules/messages/src/tests/mock.rs +++ b/modules/messages/src/tests/mock.rs @@ -332,8 +332,11 @@ impl TestDeliveryPayments { impl DeliveryPayments for TestDeliveryPayments { type Error = &'static str; - fn relayer_reward_per_message(_lane_id: LaneId, _relayer: &AccountId) -> RelayerRewardAtSource { - RELAYER_REWARD_PER_MESSAGE + fn relayer_reward_per_message( + _lane_id: LaneId, + _relayer: &AccountId, + ) -> Option { + Some(RELAYER_REWARD_PER_MESSAGE) } fn pay_reward( @@ -373,7 +376,9 @@ impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayment let relayers_rewards = calc_relayers_rewards_at_source::( messages_relayers, received_range, - |messages, relayer_reward_per_message| messages * relayer_reward_per_message, + |messages, relayer_reward_per_message| { + messages * relayer_reward_per_message.unwrap_or(0) + }, ); let rewarded_relayers = relayers_rewards.len(); for (relayer, reward) in &relayers_rewards { @@ -481,7 +486,7 @@ pub fn unrewarded_relayer( messages: DeliveredMessages { begin, end, - relayer_reward_per_message: RELAYER_REWARD_PER_MESSAGE, + relayer_reward_per_message: Some(RELAYER_REWARD_PER_MESSAGE), }, } } diff --git a/modules/messages/src/tests/pallet_tests.rs b/modules/messages/src/tests/pallet_tests.rs index 7c01dca5fe..da81b9380f 100644 --- a/modules/messages/src/tests/pallet_tests.rs +++ b/modules/messages/src/tests/pallet_tests.rs @@ -87,7 +87,7 @@ fn receive_messages_delivery_proof() { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, - messages: DeliveredMessages::new(1, 0), + messages: DeliveredMessages::new(1, None), }] .into(), }, @@ -263,7 +263,7 @@ fn receive_messages_proof_works() { .0 .relayers .front() - .map(|r| r.messages.relayer_reward_per_message), + .and_then(|r| r.messages.relayer_reward_per_message), Some(RELAYER_REWARD_PER_MESSAGE), ); @@ -855,7 +855,7 @@ fn proof_size_refund_from_receive_messages_proof_works() { messages: DeliveredMessages { begin: 0, end: 100, - relayer_reward_per_message: 0 + relayer_reward_per_message: None, } }; max_entries @@ -888,7 +888,7 @@ fn proof_size_refund_from_receive_messages_proof_works() { messages: DeliveredMessages { begin: 0, end: 100, - relayer_reward_per_message: 0 + relayer_reward_per_message: None, } }; max_entries - 1 @@ -1002,7 +1002,7 @@ fn test_bridge_messages_call_is_correctly_defined() { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, - messages: DeliveredMessages::new(1, 0), + messages: DeliveredMessages::new(1, None), }] .into(), }, @@ -1065,7 +1065,11 @@ fn inbound_storage_extra_proof_size_bytes_works() { fn relayer_entry() -> UnrewardedRelayer { UnrewardedRelayer { relayer: 42u64, - messages: DeliveredMessages { begin: 0, end: 100, relayer_reward_per_message: 0 }, + messages: DeliveredMessages { + begin: 0, + end: 100, + relayer_reward_per_message: Some(42), + }, } } @@ -1161,7 +1165,7 @@ fn receive_messages_delivery_proof_fails_if_outbound_lane_is_unknown() { last_confirmed_nonce: 1, relayers: vec![UnrewardedRelayer { relayer: 0, - messages: DeliveredMessages::new(1, 0), + messages: DeliveredMessages::new(1, None), }] .into(), }, diff --git a/modules/relayers/src/extension/mod.rs b/modules/relayers/src/extension/mod.rs index 1ed7dcf450..9d48e94956 100644 --- a/modules/relayers/src/extension/mod.rs +++ b/modules/relayers/src/extension/mod.rs @@ -1930,7 +1930,7 @@ mod tests { messages: DeliveredMessages { begin: 1, end: best_delivered_message, - relayer_reward_per_message: 0, + relayer_reward_per_message: None, }, }] .into(), diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index 391426aad4..3ab27c0624 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -79,6 +79,8 @@ pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg"; /// Maximal extrinsic size at the `BridgedChain`. pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; +/// Default reward that is paid to relayer for delivering a single message. +pub const DEFAULT_REWARD_PER_MESSAGE: ThisChainBalance = 100_000; /// Maximal reward that may be paid to relayer for delivering a single message. pub const MAX_REWARD_PER_MESSAGE: ThisChainBalance = 100_000; @@ -291,6 +293,7 @@ pub type TestDeliveryConfirmationPaymentsAdapter = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), + ConstU64, ConstU64, >; diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 3c99d233db..7f102cb58e 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -37,19 +37,21 @@ use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeIn /// for the messages pallet. /// /// This adapter assumes 1:1 mapping of `RelayerRewardAtSource` to `T::Reward`. The reward for -/// delivering a single message, will never be larger than the `MaxRewardPerMessage`. +/// delivering a single message, will never be larger than the `MaxRewardPerMessage`. If relayer +/// has not specified expected reward, it gets the `DefaultRewardPerMessage` for every message. /// /// We assume that the confirmation transaction cost is refunded by the signed extension, /// implemented by the pallet. So we do not reward confirmation relayer additionally here. -pub struct DeliveryConfirmationPaymentsAdapter( - PhantomData<(T, MI, MaxRewardPerMessage)>, +pub struct DeliveryConfirmationPaymentsAdapter( + PhantomData<(T, MI, DefaultRewardPerMessage, MaxRewardPerMessage)>, ); -impl DeliveryConfirmationPayments - for DeliveryConfirmationPaymentsAdapter +impl DeliveryConfirmationPayments + for DeliveryConfirmationPaymentsAdapter where T: Config + pallet_bridge_messages::Config, MI: 'static, + DefaultRewardPerMessage: Get, MaxRewardPerMessage: Get, { type Error = &'static str; @@ -67,7 +69,9 @@ where |messages, relayer_reward_per_message| { let relayer_reward_per_message = sp_std::cmp::min( MaxRewardPerMessage::get(), - relayer_reward_per_message.unique_saturated_into(), + relayer_reward_per_message + .map(|x| x.unique_saturated_into()) + .unwrap_or_else(|| DefaultRewardPerMessage::get()), ); T::Reward::unique_saturated_from(messages) @@ -89,16 +93,19 @@ where } } -impl DeliveryPayments - for DeliveryConfirmationPaymentsAdapter +impl DeliveryPayments + for DeliveryConfirmationPaymentsAdapter where T: Config + pallet_bridge_messages::Config, MI: 'static, { type Error = &'static str; - fn relayer_reward_per_message(_lane: LaneId, _relayer: &T::AccountId) -> RelayerRewardAtSource { - unimplemented!("TODO") + fn relayer_reward_per_message( + _lane: LaneId, + _relayer: &T::AccountId, + ) -> Option { + None // TODO: read from ActiveLaneRelayers } fn pay_reward( @@ -165,11 +172,34 @@ mod tests { }); } + #[test] + fn reward_per_message_is_default_if_not_specified() { + run_test(|| { + let mut delivered_messages = bp_messages::DeliveredMessages::new(1, None); + delivered_messages.note_dispatched_message(); + + >::pay_reward( + test_lane_id(), + vec![bp_messages::UnrewardedRelayer { relayer: 42, messages: delivered_messages }] + .into(), + &43, + &(1..=2), + ); + + assert_eq!( + RelayerRewards::::get(42, test_reward_account_param()), + Some(DEFAULT_REWARD_PER_MESSAGE * 2), + ); + }); + } + #[test] fn reward_per_message_is_never_larger_than_max_reward_per_message() { run_test(|| { let mut delivered_messages = - bp_messages::DeliveredMessages::new(1, MAX_REWARD_PER_MESSAGE + 1); + bp_messages::DeliveredMessages::new(1, Some(MAX_REWARD_PER_MESSAGE + 1)); delivered_messages.note_dispatched_message(); , } impl DeliveredMessages { /// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given /// dispatch result. - pub fn new(nonce: MessageNonce, relayer_reward_per_message: RelayerRewardAtSource) -> Self { + pub fn new( + nonce: MessageNonce, + relayer_reward_per_message: Option, + ) -> Self { DeliveredMessages { begin: nonce, end: nonce, relayer_reward_per_message } } @@ -617,7 +624,7 @@ impl Default for OutboundLaneData { pub fn calc_relayers_rewards_at_source( messages_relayers: VecDeque>, received_range: &RangeInclusive, - compute_reward: impl Fn(MessageNonce, RelayerRewardAtSource) -> Reward, + compute_reward: impl Fn(MessageNonce, Option) -> Reward, ) -> RelayersRewards where AccountId: sp_std::cmp::Ord, @@ -627,11 +634,6 @@ where // this loop is bounded by `T::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` on the bridged chain let mut relayers_rewards: RelayersRewards<_, Reward> = RelayersRewards::new(); for entry in messages_relayers { - // if relayer does not expect any reward, do nothing - if entry.messages.relayer_reward_per_message == 0 { - continue - } - // if we have already paid reward for delivering those messages, do nothing let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end()); @@ -644,6 +646,12 @@ where // compute and update reward in the rewards map let new_reward = compute_reward(new_confirmations_count, entry.messages.relayer_reward_per_message); + + // if relayer does not expect any reward, do nothing + if new_reward.is_zero() { + continue + } + let total_relayer_reward = relayers_rewards.entry(entry.relayer).or_insert_with(Zero::zero); *total_relayer_reward = total_relayer_reward.saturating_add(new_reward); } @@ -690,10 +698,10 @@ mod tests { let lane_data = InboundLaneData { state: LaneState::Opened, relayers: vec![ - UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0, 0) }, + UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0, None) }, UnrewardedRelayer { relayer: 2, - messages: DeliveredMessages::new(MessageNonce::MAX, 0), + messages: DeliveredMessages::new(MessageNonce::MAX, None), }, ] .into_iter() @@ -720,7 +728,7 @@ mod tests { relayers: (1u8..=relayer_entries) .map(|i| UnrewardedRelayer { relayer: i, - messages: DeliveredMessages::new(i as _, 0u64), + messages: DeliveredMessages::new(i as _, Some(42)), }) .collect(), last_confirmed_nonce: messages_count as _, @@ -738,7 +746,7 @@ mod tests { #[test] fn contains_result_works() { let delivered_messages = - DeliveredMessages { begin: 100, end: 150, relayer_reward_per_message: 0 }; + DeliveredMessages { begin: 100, end: 150, relayer_reward_per_message: None }; assert!(!delivered_messages.contains_message(99)); assert!(delivered_messages.contains_message(100)); @@ -800,20 +808,21 @@ mod tests { calc_relayers_rewards_at_source::( vec![ // relayer that wants zero reward => no payments expected - UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(1, 0) }, + UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(1, Some(0)) }, + UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(1, None) }, // relayer wants reward => payment is expected - UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(2, 77) }, + UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(2, Some(77)) }, // relayer that we met before and he wants reward => payment is expected - UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(3, 42) }, + UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(3, Some(42)) }, // relayer that we met before and he wants reward => payment is expected - UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(4, 33) }, + UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(4, Some(33)) }, // relayers that deliver messages out of range - UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(0, 33) }, - UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(5, 33) }, + UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(0, Some(33)) }, + UnrewardedRelayer { relayer: 2, messages: DeliveredMessages::new(5, Some(33)) }, ] .into(), &(1..=4), - |_, relayer_reward_per_message| relayer_reward_per_message, + |_, relayer_reward_per_message| relayer_reward_per_message.unwrap_or(0), ), vec![(1, 42), (2, 110)].into_iter().collect(), ); diff --git a/primitives/messages/src/target_chain.rs b/primitives/messages/src/target_chain.rs index c4cdd22a6f..fe10d284de 100644 --- a/primitives/messages/src/target_chain.rs +++ b/primitives/messages/src/target_chain.rs @@ -131,7 +131,14 @@ pub trait DeliveryPayments { /// /// Keep in mind that it is not necessary a real reward that will be paid. See /// [`crate::RelayerRewardAtSource`] for more details. - fn relayer_reward_per_message(lane: LaneId, relayer: &AccountId) -> RelayerRewardAtSource; + /// + /// If method returns `None`, it means that the relayer has not specified its expected reward + /// at the target chain and it is up to the source chain `DeliveryConfirmationPayments` to + /// compute the reward amount. + fn relayer_reward_per_message( + lane: LaneId, + relayer: &AccountId, + ) -> Option; /// Pay rewards for delivering messages to the given relayer. /// @@ -169,8 +176,11 @@ impl From for DispatchMessageData DeliveryPayments for () { type Error = &'static str; - fn relayer_reward_per_message(_lane: LaneId, _relayer: &AccountId) -> RelayerRewardAtSource { - 0 + fn relayer_reward_per_message( + _lane: LaneId, + _relayer: &AccountId, + ) -> Option { + None } fn pay_reward( From 113251edaccf7f970e81efefb1c2b62a397fab1d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 12:33:06 +0300 Subject: [PATCH 55/60] properly implement relayer_reward_per_message in DeliveryConfirmationPaymentsAdapter --- modules/relayers/src/payment_adapter.rs | 50 +++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 7f102cb58e..fd1b96edd0 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -102,10 +102,12 @@ where type Error = &'static str; fn relayer_reward_per_message( - _lane: LaneId, - _relayer: &T::AccountId, + lane: LaneId, + relayer: &T::AccountId, ) -> Option { - None // TODO: read from ActiveLaneRelayers + ActiveLaneRelayers::::get(lane) + .relayer(relayer) + .map(|r| r.relayer_reward_per_message()) } fn pay_reward( @@ -140,6 +142,7 @@ fn register_relayers_rewards( mod tests { use super::*; use crate::{mock::*, RelayerRewards}; + use frame_support::assert_ok; const RELAYER_1: ThisChainAccountId = 1; const RELAYER_2: ThisChainAccountId = 2; @@ -218,4 +221,45 @@ mod tests { ); }); } + + #[test] + fn relayer_reward_per_message_works() { + run_test(|| { + assert_ok!(BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + Stake::get() + LaneStake::get() + )); + assert_ok!(BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150)); + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 42, + )); + System::set_block_number( + BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().may_enact_at(), + ); + assert_ok!(BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + )); + + // for unregistered relayer it returns `None` + assert_eq!( + TestDeliveryConfirmationPaymentsAdapter::relayer_reward_per_message( + test_lane_id(), + ®ULAR_RELAYER + ), + None, + ); + + // for registered relayer it returns its expected reward + assert_eq!( + TestDeliveryConfirmationPaymentsAdapter::relayer_reward_per_message( + test_lane_id(), + ®ISTER_RELAYER + ), + Some(42), + ); + }) + } } From 325a99a0a8195787896221c41b2f69224dead823 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 12:40:42 +0300 Subject: [PATCH 56/60] clippy --- modules/relayers/src/lib.rs | 6 +++--- modules/relayers/src/stake_adapter.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 7519567ed8..2938793629 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -767,7 +767,7 @@ pub mod pallet { fail!(Error::::FailedToUnreserve) } } else if let Some(to_reserve) = required_stake.checked_sub(¤t_stake) { - let reserve_result = T::StakeAndSlash::reserve(&relayer, to_reserve); + let reserve_result = T::StakeAndSlash::reserve(relayer, to_reserve); if let Err(e) = reserve_result { log::trace!( target: LOG_TARGET, @@ -1075,7 +1075,7 @@ mod tests { Stake::get() + 42 )); assert_eq!( - BridgeRelayers::registered_relayer(®ISTER_RELAYER), + BridgeRelayers::registered_relayer(REGISTER_RELAYER), Some(registration(0, Stake::get() + 42)) ); assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 42); @@ -1092,7 +1092,7 @@ mod tests { assert_ok!(BridgeRelayers::increase_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 8)); assert_eq!( - BridgeRelayers::registered_relayer(®ISTER_RELAYER), + BridgeRelayers::registered_relayer(REGISTER_RELAYER), Some(registration(150, Stake::get() + 50)) ); assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), 8); diff --git a/modules/relayers/src/stake_adapter.rs b/modules/relayers/src/stake_adapter.rs index 707a6efd83..83bf5d84f9 100644 --- a/modules/relayers/src/stake_adapter.rs +++ b/modules/relayers/src/stake_adapter.rs @@ -131,7 +131,7 @@ mod tests { run_test(|| { let beneficiary = test_reward_account_param(); let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary); - let mut expected_balance = Balances::free_balance(&beneficiary_account); + let mut expected_balance = Balances::free_balance(beneficiary_account); assert_eq!( TestStakeAndSlash::repatriate_reserved(&1, beneficiary, test_stake()), From 17b678b09fc594401b852454980bf0bbff8c668c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 12:48:23 +0300 Subject: [PATCH 57/60] spelling --- modules/relayers/src/lib.rs | 12 ++++++------ primitives/relayers/src/registration.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 2938793629..5047d280b3 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -105,7 +105,7 @@ pub mod pallet { /// Length of initial relayer elections in chain blocks. /// /// When the first relayer registers itself on the lane, we give some time to other relayers - /// to register as well. Otherwise there'll be only one relayer in active set, for the whole + /// to register as well. Otherwise there will be only one relayer in active set, for the whole /// (first) epoch. type InitialElectionLength: Get>; /// Length of slots in chain blocks. @@ -228,7 +228,7 @@ pub mod pallet { }) } - /// Unreserve some or all funds, reserved previously by the `reserve_funds` call. + /// `Unreserve` some or all funds, reserved previously by the `reserve_funds` call. /// /// The reserved amount after this call must cover basic registration and all lane /// registrations that relayer has. @@ -318,7 +318,7 @@ pub mod pallet { /// `Deregister` relayer. /// /// After this call, message delivery transactions of the relayer won't get any priority - /// boost. Keep in mind that the relayer can't deregister until `valid_till` block, which + /// boost. Keep in mind that the relayer can't `deregister` until `valid_till` block, which /// he has specified in the registration call. The relayer is also unregistered from all /// lanes, where he has explicitly registered using `register_at_lane`. /// @@ -370,7 +370,7 @@ pub mod pallet { /// call. However, before that call, relayer may be pushed from the next set by relayers, /// offering lower `expected_reward`. If that happens, relayer may either try to re-register /// itself by repeating the `register_at_lane` call, offering lower reward. Or it may claim - /// his lane stake back, by updating his registration with `register` call or deregistering + /// his lane stake back, by updating his registration with `register` call or `deregistering` /// at all using `deregister` call. /// /// Relayer may request large reward here (using `expected_reward`), but in the end, the @@ -421,9 +421,9 @@ pub mod pallet { Ok(()) } - /// Deregister relayer intention to deliver inbound messages at given messages lane. + /// `Deregister` relayer intention to deliver inbound messages at given messages lane. /// - /// After deregistration, relayer won't get lane-specific boost for message delivery + /// After `deregistration`, relayer won't get lane-specific boost for message delivery /// transactions at that lane. It would still get the basic boost until the `deregister` /// call. /// diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index 4a0734f117..e56ebfca6b 100644 --- a/primitives/relayers/src/registration.rs +++ b/primitives/relayers/src/registration.rs @@ -70,7 +70,7 @@ pub struct Registration< /// registration ends (see [`StakeAndSlash::RequiredRegistrationLease`]). /// /// Please also keep in mind that while relayer is registered at least at one lane, - /// the regisration is always considered active. + /// the registration is always considered active. valid_till: BlockNumber, /// Active relayer stake, which is mapped to the relayer reserved balance. /// From bd000e2726087e64b8214b1696b1d18a13d4cc68 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 12:52:17 +0300 Subject: [PATCH 58/60] fmt --- modules/relayers/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs index 5047d280b3..f055102788 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -105,8 +105,8 @@ pub mod pallet { /// Length of initial relayer elections in chain blocks. /// /// When the first relayer registers itself on the lane, we give some time to other relayers - /// to register as well. Otherwise there will be only one relayer in active set, for the whole - /// (first) epoch. + /// to register as well. Otherwise there will be only one relayer in active set, for the + /// whole (first) epoch. type InitialElectionLength: Get>; /// Length of slots in chain blocks. /// @@ -370,8 +370,8 @@ pub mod pallet { /// call. However, before that call, relayer may be pushed from the next set by relayers, /// offering lower `expected_reward`. If that happens, relayer may either try to re-register /// itself by repeating the `register_at_lane` call, offering lower reward. Or it may claim - /// his lane stake back, by updating his registration with `register` call or `deregistering` - /// at all using `deregister` call. + /// his lane stake back, by updating his registration with `register` call or + /// `deregistering` at all using `deregister` call. /// /// Relayer may request large reward here (using `expected_reward`), but in the end, the /// reward amount is computed at the bridged (source chain). In the case if From b5146bb01240679f1e7b3f9963dda908da14485e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 18 Oct 2023 13:03:21 +0300 Subject: [PATCH 59/60] fixed benchmarks compilation --- modules/messages/src/benchmarking.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/messages/src/benchmarking.rs b/modules/messages/src/benchmarking.rs index 7a312dbb93..aead7d6032 100644 --- a/modules/messages/src/benchmarking.rs +++ b/modules/messages/src/benchmarking.rs @@ -131,7 +131,7 @@ fn receive_messages, I: 'static>(nonce: MessageNonce) { state: LaneState::Opened, relayers: vec![UnrewardedRelayer { relayer: T::bridged_relayer_id(), - messages: DeliveredMessages::new(nonce, 1), + messages: DeliveredMessages::new(nonce, Some(1)), }] .into(), last_confirmed_nonce: 0, @@ -368,7 +368,7 @@ mod benchmarks { state: LaneState::Opened, relayers: vec![UnrewardedRelayer { relayer: relayer_id.clone(), - messages: DeliveredMessages::new(1, 1), + messages: DeliveredMessages::new(1, Some(1)), }] .into_iter() .collect(), @@ -412,7 +412,7 @@ mod benchmarks { total_messages: 2, last_delivered_nonce: 2, }; - let mut delivered_messages = DeliveredMessages::new(1, 1); + let mut delivered_messages = DeliveredMessages::new(1, Some(1)); delivered_messages.note_dispatched_message(); let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { lane: T::bench_lane_id(), @@ -472,11 +472,11 @@ mod benchmarks { relayers: vec![ UnrewardedRelayer { relayer: relayer1_id.clone(), - messages: DeliveredMessages::new(1, 1), + messages: DeliveredMessages::new(1, Some(1)), }, UnrewardedRelayer { relayer: relayer2_id.clone(), - messages: DeliveredMessages::new(2, 1), + messages: DeliveredMessages::new(2, Some(1)), }, ] .into_iter() From c6b4f454862e7438d0d6aa75c717de5e5c33154c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 30 Oct 2023 14:27:33 +0300 Subject: [PATCH 60/60] fix benchmarks --- modules/relayers/src/benchmarking.rs | 2 +- modules/relayers/src/weights.rs | 110 +++++++++++++-------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/modules/relayers/src/benchmarking.rs b/modules/relayers/src/benchmarking.rs index f6847a3bee..00fb59d50f 100644 --- a/modules/relayers/src/benchmarking.rs +++ b/modules/relayers/src/benchmarking.rs @@ -202,7 +202,7 @@ benchmarks! { ); for active_relayer in &active_relayers { register_relayer::(active_relayer, 1, 1); - assert!(next_relayers_set.try_push(active_relayer.clone(), 0)); + assert!(next_relayers_set.try_insert(active_relayer.clone(), 0)); } active_relayers_set.activate_next_set(current_block_number, next_relayers_set, |_| true); ActiveLaneRelayers::::insert(lane_id(0), active_relayers_set); diff --git a/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs index fff32d3aa6..b3ed8ff0eb 100644 --- a/modules/relayers/src/weights.rs +++ b/modules/relayers/src/weights.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for pallet_bridge_relayers //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `MusXroom`, CPU: `13th Gen Intel(R) Core(TM) i7-13650HX` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -84,8 +84,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_449 nanoseconds. - Weight::from_parts(57_453_000, 3569) + // Minimum execution time: 50_439 nanoseconds. + Weight::from_parts(51_446_000, 3569) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -102,8 +102,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_818 nanoseconds. - Weight::from_parts(24_830_000, 4314) + // Minimum execution time: 21_916 nanoseconds. + Weight::from_parts(23_028_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -120,8 +120,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 26_879 nanoseconds. - Weight::from_parts(27_588_000, 4314) + // Minimum execution time: 25_145 nanoseconds. + Weight::from_parts(26_059_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -133,8 +133,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_367 nanoseconds. - Weight::from_parts(14_873_000, 4042) + // Minimum execution time: 12_568 nanoseconds. + Weight::from_parts(13_014_000, 4042) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -151,8 +151,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 33_479 nanoseconds. - Weight::from_parts(34_648_000, 4314) + // Minimum execution time: 29_748 nanoseconds. + Weight::from_parts(30_408_000, 4314) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -169,15 +169,15 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 17_836 nanoseconds. - Weight::from_parts(18_517_000, 44467) + // Minimum execution time: 17_866 nanoseconds. + Weight::from_parts(18_227_000, 44467) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:0) /// - /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), - /// added: 3669, mode: `MaxEncodedLen`) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// @@ -192,15 +192,15 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `44467` - // Minimum execution time: 19_315 nanoseconds. - Weight::from_parts(19_951_000, 44467) + // Minimum execution time: 19_639 nanoseconds. + Weight::from_parts(20_242_000, 44467) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:1) /// - /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), - /// added: 3669, mode: `MaxEncodedLen`) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) /// @@ -213,10 +213,10 @@ impl WeightInfo for BridgeWeight { /// added: 3052, mode: `MaxEncodedLen`) fn advance_lane_epoch() -> Weight { // Proof Size summary in bytes: - // Measured: `56725` + // Measured: `56740` // Estimated: `49822` - // Minimum execution time: 211_226 nanoseconds. - Weight::from_parts(219_707_000, 49822) + // Minimum execution time: 168_831 nanoseconds. + Weight::from_parts(182_371_000, 49822) .saturating_add(T::DbWeight::get().reads(18_u64)) .saturating_add(T::DbWeight::get().writes(18_u64)) } @@ -227,8 +227,8 @@ impl WeightInfo for BridgeWeight { /// /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:16 w:16) /// - /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), - /// added: 3669, mode: `MaxEncodedLen`) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// /// Storage: `BridgeRelayers::NextLaneRelayers` (r:16 w:16) /// @@ -248,8 +248,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `2395` // Estimated: `696622` - // Minimum execution time: 101_108 nanoseconds. - Weight::from_parts(102_963_000, 696622) + // Minimum execution time: 102_040 nanoseconds. + Weight::from_parts(104_512_000, 696622) .saturating_add(T::DbWeight::get().reads(35_u64)) .saturating_add(T::DbWeight::get().writes(35_u64)) } @@ -261,8 +261,8 @@ impl WeightInfo for BridgeWeight { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_310 nanoseconds. - Weight::from_parts(4_589_000, 3558) + // Minimum execution time: 6_682 nanoseconds. + Weight::from_parts(7_047_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -288,8 +288,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `388` // Estimated: `3569` - // Minimum execution time: 56_449 nanoseconds. - Weight::from_parts(57_453_000, 3569) + // Minimum execution time: 50_439 nanoseconds. + Weight::from_parts(51_446_000, 3569) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -306,8 +306,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `153` // Estimated: `4314` - // Minimum execution time: 23_818 nanoseconds. - Weight::from_parts(24_830_000, 4314) + // Minimum execution time: 21_916 nanoseconds. + Weight::from_parts(23_028_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -324,8 +324,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 26_879 nanoseconds. - Weight::from_parts(27_588_000, 4314) + // Minimum execution time: 25_145 nanoseconds. + Weight::from_parts(26_059_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -337,8 +337,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `103` // Estimated: `4042` - // Minimum execution time: 14_367 nanoseconds. - Weight::from_parts(14_873_000, 4042) + // Minimum execution time: 12_568 nanoseconds. + Weight::from_parts(13_014_000, 4042) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -355,8 +355,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `331` // Estimated: `4314` - // Minimum execution time: 33_479 nanoseconds. - Weight::from_parts(34_648_000, 4314) + // Minimum execution time: 29_748 nanoseconds. + Weight::from_parts(30_408_000, 4314) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -373,15 +373,15 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1340` // Estimated: `44467` - // Minimum execution time: 17_836 nanoseconds. - Weight::from_parts(18_517_000, 44467) + // Minimum execution time: 17_866 nanoseconds. + Weight::from_parts(18_227_000, 44467) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:0) /// - /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), - /// added: 3669, mode: `MaxEncodedLen`) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// @@ -396,15 +396,15 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1596` // Estimated: `44467` - // Minimum execution time: 19_315 nanoseconds. - Weight::from_parts(19_951_000, 44467) + // Minimum execution time: 19_639 nanoseconds. + Weight::from_parts(20_242_000, 44467) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:1 w:1) /// - /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), - /// added: 3669, mode: `MaxEncodedLen`) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) /// @@ -417,10 +417,10 @@ impl WeightInfo for () { /// added: 3052, mode: `MaxEncodedLen`) fn advance_lane_epoch() -> Weight { // Proof Size summary in bytes: - // Measured: `56725` + // Measured: `56740` // Estimated: `49822` - // Minimum execution time: 211_226 nanoseconds. - Weight::from_parts(219_707_000, 49822) + // Minimum execution time: 168_831 nanoseconds. + Weight::from_parts(182_371_000, 49822) .saturating_add(RocksDbWeight::get().reads(18_u64)) .saturating_add(RocksDbWeight::get().writes(18_u64)) } @@ -431,8 +431,8 @@ impl WeightInfo for () { /// /// Storage: `BridgeRelayers::ActiveLaneRelayers` (r:16 w:16) /// - /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(1194), - /// added: 3669, mode: `MaxEncodedLen`) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// /// Storage: `BridgeRelayers::NextLaneRelayers` (r:16 w:16) /// @@ -452,8 +452,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2395` // Estimated: `696622` - // Minimum execution time: 101_108 nanoseconds. - Weight::from_parts(102_963_000, 696622) + // Minimum execution time: 102_040 nanoseconds. + Weight::from_parts(104_512_000, 696622) .saturating_add(RocksDbWeight::get().reads(35_u64)) .saturating_add(RocksDbWeight::get().writes(35_u64)) } @@ -465,8 +465,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `12` // Estimated: `3558` - // Minimum execution time: 4_310 nanoseconds. - Weight::from_parts(4_589_000, 3558) + // Minimum execution time: 6_682 nanoseconds. + Weight::from_parts(7_047_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) }