diff --git a/Cargo.lock b/Cargo.lock index 27d52787ae..55e9ef8014 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1339,6 +1339,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/benchmarking.rs b/modules/relayers/src/benchmarking.rs index 50f1399489..7d946f497d 100644 --- a/modules/relayers/src/benchmarking.rs +++ b/modules/relayers/src/benchmarking.rs @@ -23,8 +23,10 @@ use crate::*; use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; use frame_benchmarking::v2::*; -use frame_system::RawOrigin; +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; @@ -40,6 +42,47 @@ 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, + expected_reward: RelayerRewardAtSource, +) { + 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), + expected_reward, + ) + .unwrap(); + } + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), + Some(lanes_reg_count), + ); +} + #[benchmarks] mod benchmarks { use super::*; @@ -64,16 +107,54 @@ mod benchmarks { // also completed successfully } + /// Benchmark `increase_stake` call. + #[benchmark] + fn increase_stake() { + let relayer: T::AccountId = whitelisted_caller(); + let stake = crate::Pallet::::base_stake(); + T::deposit_account(relayer.clone(), stake); + + #[extrinsic_call] + _(RawOrigin::Signed(relayer.clone()), stake); + + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.current_stake()), + Some(stake), + ); + } + + /// Benchmark `decrease_stake` call. + #[benchmark] + fn 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(); + + #[extrinsic_call] + _(RawOrigin::Signed(relayer.clone()), 100u32.into()); + + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.current_stake()), + Some(base_stake), + ); + } + /// Benchmark `register` call. #[benchmark] fn 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(); #[extrinsic_call] _(RawOrigin::Signed(relayer.clone()), valid_till); @@ -85,15 +166,8 @@ mod benchmarks { #[benchmark] fn deregister() { let relayer: T::AccountId = whitelisted_caller(); - 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()); - 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, 0); + frame_system::Pallet::::set_block_number(valid_till::().saturating_add(One::one())); #[extrinsic_call] _(RawOrigin::Signed(relayer.clone())); @@ -101,6 +175,105 @@ mod 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; + #[benchmark] + fn 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, 0); + + #[extrinsic_call] + _(RawOrigin::Signed(relayer.clone()), lane_id(max_lanes_per_relayer), 0); + + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), + Some(max_lanes_per_relayer), + ); + } + + /// Benchmark `deregister_at_lane` call. The worst case for this call is when relayer is not in + /// the active relayers set. + #[benchmark] + fn deregister_at_lane() { + let relayer: T::AccountId = whitelisted_caller(); + let max_lanes_per_relayer = T::MaxLanesPerRelayer::get(); + register_relayer::(&relayer, max_lanes_per_relayer, 0); + + #[extrinsic_call] + _(RawOrigin::Signed(relayer.clone()), lane_id(0)); + + assert_eq!( + crate::Pallet::::registered_relayer(&relayer).map(|reg| reg.lanes().len() as u32), + Some(max_lanes_per_relayer - 1), + ); + } + + // Benchmark `advance_lane_epoch` call. The worst case for this call is when active set is + // completely replaced with a next set. + #[benchmark] + fn 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_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); + + // 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(), + ); + + #[extrinsic_call] + _(RawOrigin::Signed(whitelisted_caller()), lane_id(0)); + + // 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. @@ -108,13 +281,18 @@ mod benchmarks { fn slash_and_deregister() { // prepare and register relayer account let relayer: T::AccountId = whitelisted_caller(); - 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()); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till) + 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); @@ -128,6 +306,15 @@ mod benchmarks { } 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/extension/mod.rs b/modules/relayers/src/extension/mod.rs index c84230e7ac..9d48e94956 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 738f8935e9..647659d59b 100644 --- a/modules/relayers/src/lib.rs +++ b/modules/relayers/src/lib.rs @@ -14,8 +14,8 @@ // 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. #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] @@ -23,13 +23,14 @@ use bp_messages::LaneId; use bp_relayers::{ ActiveLaneRelayersSet, NextLaneRelayersSet, PaymentProcedure, Registration, - RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + 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}; -use sp_std::marker::PhantomData; +use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData}; pub use pallet::*; pub use payment_adapter::DeliveryConfirmationPaymentsAdapter; @@ -63,7 +64,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; @@ -71,6 +72,14 @@ pub mod pallet { /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; + /// Maximal number of lanes, where relayer may be registered. + /// + /// 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 /// lane. /// @@ -93,6 +102,12 @@ pub mod pallet { #[pallet::constant] type MaxNextRelayersPerLane: 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 will 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 @@ -105,6 +120,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::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>; + /// Priority boost that the registered relayer gets for every additional message in the /// message delivery transaction. type PriorityBoostPerMessage: Get; @@ -166,10 +193,87 @@ 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(T::WeightInfo::increase_stake())] + 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(T::WeightInfo::decrease_stake())] + 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. - #[pallet::call_index(1)] + /// 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(3)] #[pallet::weight(T::WeightInfo::register())] pub fn register(origin: OriginFor, valid_till: BlockNumberFor) -> DispatchResult { let relayer = ensure_signed(origin)?; @@ -182,84 +286,263 @@ pub mod pallet { Error::::InvalidRegistrationLease ); - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let mut registration = maybe_registration - .unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() }); - - // new `valid_till` must be larger (or equal) than the old one - ensure!( - valid_till >= registration.valid_till, - Error::::CannotReduceRegistrationLease, - ); - 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; + // 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), + }; - log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); - Self::deposit_event(Event::::RegistrationUpdated { - relayer: relayer.clone(), - registration, - }); + // ensure that the stake is enough + ensure!(Self::is_stake_enough(®istration), Error::::StakeIsTooLow); - *maybe_registration = Some(registration); + // 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); - Ok(()) - }) + // deposit event + log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); + Self::deposit_event(Event::::RegistrationUpdated { + relayer: relayer.clone(), + registration: registration.clone(), + }); + + // update registration in the runtime storage + RegisteredRelayers::::insert(relayer, registration); + + Ok(()) } /// `Deregister` relayer. /// /// After this call, message delivery transactions of the relayer won't get any priority - /// boost. - #[pallet::call_index(2)] + /// 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`. + /// + /// 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)?; - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let registration = match maybe_registration.take() { + // 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, + ); + + // if stake is non-zero, we should do unreserve + Self::update_relayer_stake(&relayer, registration.current_stake(), Zero::zero())?; + + // deposit event + log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); + Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); + + // update runtime storage + RegisteredRelayers::::remove(&relayer); + + Ok(()) + } + + /// 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). + /// + /// 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 + /// per message is limited by the `MaxRewardPerMessage` parameter. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::register_at_lane())] + pub fn register_at_lane( + origin: OriginFor, + lane: LaneId, + expected_relayer_reward_per_message: RelayerRewardAtSource, + ) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + // we only allow registered relayers to have priority boosts + let mut registration = match Self::registered_relayer(&relayer) { + 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); + + // 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()), + ), + }; + + // try to push relayer to the next set + ensure!( + next_lane_relayers.try_insert(relayer.clone(), expected_relayer_reward_per_message), + Error::::TooLargeRewardToOccupyAnEntry, + ); + + // update basic and lane registration in the runtime storage + RegisteredRelayers::::insert(&relayer, registration); + NextLaneRelayers::::insert(lane, next_lane_relayers); + + Ok(()) + } + + /// `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(6)] + #[pallet::weight(T::WeightInfo::deregister_at_lane())] + pub fn deregister_at_lane(origin: OriginFor, lane: LaneId) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + // 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), }; - // we can't deregister until `valid_till + 1` - ensure!( - registration.valid_till < frame_system::Pallet::::block_number(), - Error::::RegistrationIsStillActive, - ); + // forget lane registration + Self::remove_lane_from_relayer_registration(&mut registration, lane, false); - // if stake is non-zero, we should do unreserve - if !registration.stake.is_zero() { - Self::do_unreserve(&relayer, registration.stake)?; + // 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); + + // 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(()) + } + + /// 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(7)] + #[pallet::weight(T::WeightInfo::advance_lane_epoch())] + pub fn advance_lane_epoch( + origin: OriginFor, + lane: LaneId, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + + 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), + }; + + // 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 + ensure!( + active_lane_relayers.activate_next_set( + current_block_number, + next_lane_relayers.clone(), + |relayer| old_active_set_registrations + .get(relayer) + .and_then(|maybe_reg| maybe_reg.as_ref()) + .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, old_relayer_registration) in old_active_set_registrations { + if next_lane_relayers.relayer(&old_relayer).is_some() { + continue } - log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); - Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); + 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); + } + } + } - *maybe_registration = None; + // update relayer sets in the storage + ActiveLaneRelayers::::insert(lane, active_lane_relayers); + NextLaneRelayers::::insert(lane, next_lane_relayers); - Ok(()) - }) + Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }) } } @@ -270,25 +553,13 @@ 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( + SystemPallet::::block_number(), + Self::required_registration_lease(), + ), + None => false, } - - true } /// Slash and `deregister` relayer. This function slashes all staked balance. @@ -298,6 +569,7 @@ pub mod pallet { relayer: &T::AccountId, slash_destination: RewardsAccountParams, ) { + // remove base relayer registration let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, None => { @@ -311,17 +583,29 @@ 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, - 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, ); }, @@ -331,7 +615,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, ); @@ -348,8 +632,8 @@ pub mod pallet { relayer, e, slash_destination, - registration.stake, - registration.stake, + registration.current_stake(), + registration.current_stake(), ); }, } @@ -398,8 +682,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 { , @@ -407,22 +691,103 @@ 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, - ); - - fail!(Error::::FailedToUnreserve) + /// Return required stake per lane. + pub(crate) fn stake_per_lane() -> T::Reward { + , + T::Reward, + >>::RequiredLaneStake::get() + } + + /// Returns total stake that the relayer must hold reserved on his account. + pub(crate) 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, + was_in_active_set: bool, + ) -> bool { + // if we are already removed from the + if !registration.deregister_at_lane(lane) { + return false } - Ok(()) + // 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 + // 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, + 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, + ); + + fail!(Error::::FailedToReserve) + } + } + + Ok(required_stake) } } @@ -452,7 +817,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 { @@ -464,7 +829,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>, }, } @@ -487,6 +852,22 @@ pub mod pallet { NotRegistered, /// Failed to `deregister` relayer, because lease is still active. RegistrationIsStillActive, + /// 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. + FailedToRegisterAtLane, + /// The expected reward, specified by relayer during `register_at_lane` call is too large + /// to occupy an entry in the next relayer set. + TooLargeRewardToOccupyAnEntry, + /// 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. @@ -514,7 +895,7 @@ pub mod pallet { _, Blake2_128Concat, T::AccountId, - Registration, T::Reward>, + Registration, T::Reward, T::MaxLanesPerRelayer>, OptionQuery, >; @@ -554,19 +935,28 @@ mod tests { use crate::Event::{RewardPaid, RewardRegistered}; use bp_messages::LaneId; - use bp_relayers::RewardsAccountOwner; + use bp_relayers::{ActiveLaneRegistration, LaneRegistration, 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}; - use sp_runtime::DispatchError; + use sp_runtime::{traits::ConstU32, DispatchError}; fn get_ready_for_events() { System::::set_block_number(1); 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 register_relayer_reward_emit_event() { run_test(|| { @@ -598,10 +988,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, ); }); @@ -611,7 +998,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() ), @@ -629,7 +1016,7 @@ mod tests { 100, ); assert_noop!( - Pallet::::claim_rewards( + BridgeRelayers::claim_rewards( RuntimeOrigin::signed(FAILING_RELAYER), test_reward_account_param() ), @@ -648,7 +1035,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() )); @@ -713,178 +1100,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_noop!( - Pallet::::register( - RuntimeOrigin::signed(REGISTER_RELAYER), - 99 + Lease::get() - ), - Error::::InvalidRegistrationLease, + assert_ok!(BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + Stake::get() + 42 + )); + assert_eq!( + BridgeRelayers::registered_relayer(REGISTER_RELAYER), + Some(registration(0, Stake::get() + 42)) ); + assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 42); }); } #[test] - fn register_works() { + fn increase_stake_works_for_existing_registration() { 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 { valid_till: 150, stake: Stake::get() }), + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get() + 42), ); + assert_ok!(BridgeRelayers::increase_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 8)); assert_eq!( - System::::events().last(), - Some(&EventRecord { - phase: Phase::Initialization, - event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { - relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get() }, - }), - topics: vec![], - }), + BridgeRelayers::registered_relayer(REGISTER_RELAYER), + Some(registration(150, Stake::get() + 50)) ); + assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), 8); }); } #[test] - fn register_fails_if_new_valid_till_is_lesser_than_previous() { + fn increase_stake_fails_if_it_fails_to_reserve_additional_stake() { run_test(|| { - assert_ok!(Pallet::::register( - RuntimeOrigin::signed(REGISTER_RELAYER), - 150 - )); - assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 125), - Error::::CannotReduceRegistrationLease, + BridgeRelayers::increase_stake( + RuntimeOrigin::signed(REGISTER_RELAYER), + ThisChainBalance::MAX + ), + Error::::FailedToReserve, ); }); } #[test] - fn register_fails_if_it_cant_unreserve_some_balance_if_required_stake_decreases() { + fn decrease_stake_requires_signed_origin() { run_test(|| { - RegisteredRelayers::::insert( - REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() + 1 }, + assert_noop!( + BridgeRelayers::decrease_stake(RuntimeOrigin::root(), 1), + DispatchError::BadOrigin ); + }); + } + #[test] + fn decrease_stake_fails_if_relayer_is_not_registered() { + run_test(|| { assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), - Error::::FailedToUnreserve, + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::NotRegistered ); }); } #[test] - fn register_unreserves_some_balance_if_required_stake_decreases() { + fn decrease_stake_fails_if_stake_after_call_is_too_low_to_cover_all_registrations() { 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); + + // 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 { valid_till: 150, stake: 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_eq!( - Pallet::::registered_relayer(REGISTER_RELAYER), - Some(Registration { valid_till: 150, stake: Stake::get() }), + registration(150, Stake::get()), ); - - assert_eq!( - System::::events().last(), - Some(&EventRecord { - phase: Phase::Initialization, - event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { - relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get() } - }), - topics: vec![], - }), + assert_noop!( + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::StakeIsTooLow ); }); } #[test] - fn register_fails_if_it_cant_reserve_some_balance() { + fn decrease_stake_fails_if_it_cant_unreserve_stake() { run_test(|| { - Balances::set_balance(®ISTER_RELAYER, 0); + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(150, Stake::get() + 1), + ); assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), - Error::::FailedToReserve, + BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1), + Error::::FailedToUnreserve ); }); } #[test] - fn register_fails_if_it_cant_reserve_some_balance_if_required_stake_increases() { + fn decrease_stake_works() { run_test(|| { + 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 { valid_till: 150, stake: Stake::get() - 1 }, + registration(150, Stake::get() + 1), ); - Balances::set_balance(®ISTER_RELAYER, 0); - - assert_noop!( - Pallet::::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), - Error::::FailedToReserve, + assert_ok!(BridgeRelayers::decrease_stake(RuntimeOrigin::signed(REGISTER_RELAYER), 1)); + assert_eq!( + Balances::reserved_balance(REGISTER_RELAYER), + Stake::get() + LaneStake::get() + LaneStake::get() - 1 ); }); } #[test] - fn register_reserves_some_balance_if_required_stake_increases() { + fn register_fails_if_valid_till_is_a_past_block() { run_test(|| { - get_ready_for_events(); + System::::set_block_number(100); + + assert_noop!( + BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 50), + Error::::InvalidRegistrationLease, + ); + }); + } + + #[test] + fn register_fails_if_valid_till_lease_is_less_than_required() { + run_test(|| { + System::::set_block_number(100); + + assert_noop!( + BridgeRelayers::register( + RuntimeOrigin::signed(REGISTER_RELAYER), + 99 + Lease::get() + ), + Error::::InvalidRegistrationLease, + ); + }); + } + #[test] + fn register_fails_if_stake_is_not_enough() { + run_test(|| { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() - 1 }, + registration(0, Stake::get() - 1), ); - TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() - 1).unwrap(); - let free_balance = Balances::free_balance(REGISTER_RELAYER); - assert_ok!(Pallet::::register( + assert_noop!( + BridgeRelayers::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150), + Error::::StakeIsTooLow, + ); + }); + } + + #[test] + fn register_works() { + run_test(|| { + get_ready_for_events(); + 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 { valid_till: 150, stake: 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(), @@ -892,7 +1316,7 @@ mod tests { phase: Phase::Initialization, event: TestEvent::BridgeRelayers(Event::RegistrationUpdated { relayer: REGISTER_RELAYER, - registration: Registration { valid_till: 150, stake: Stake::get() } + registration: registration(150, Stake::get()), }), topics: vec![], }), @@ -900,11 +1324,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, ); }); @@ -913,15 +1354,39 @@ 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, + ); + }); + } + + #[test] + fn deregister_fails_if_relayer_has_lanes_registrations() { + 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(), + 0, + )); + + System::::set_block_number(151); + + assert_noop!( + BridgeRelayers::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)), Error::::RegistrationIsStillActive, ); }); @@ -932,16 +1397,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() @@ -961,21 +1427,45 @@ mod tests { }); } + #[test] + fn deregister_works_after_last_lane_registration_is_removed() { + 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(), + 0, + )); + assert_ok!(BridgeRelayers::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + )); + + System::::set_block_number(151); + + 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)); }); } #[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 }, + registration(150, Stake::get() - 1), ); - assert!(!Pallet::::is_registration_active(®ISTER_RELAYER)); + assert!(BridgeRelayers::is_registration_active(®ISTER_RELAYER)); }); } @@ -986,22 +1476,606 @@ mod tests { RegisteredRelayers::::insert( REGISTER_RELAYER, - Registration { valid_till: 150, stake: Stake::get() }, + 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_properly_registeered() { + fn is_registration_active_is_true_when_relayer_is_registered_at_lanes() { + run_test(|| { + 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!(BridgeRelayers::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() }, + registration(151, Stake::get()), + ); + assert!(BridgeRelayers::is_registration_active(®ISTER_RELAYER)); + }); + } + + #[test] + fn register_at_lane_fails_for_unregistered_relayer() { + run_test(|| { + assert_noop!( + BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + ), + Error::::NotRegistered, + ); + }); + } + + #[test] + fn register_at_lane_fails_if_relayer_has_max_lane_registrations() { + run_test(|| { + let mut registration = registration(151, Stake::get() + LaneStake::get()); + for i in 0..MaxLanesPerRelayer::get() { + assert!(registration.register_at_lane(LaneId::new(42, i))); + } + + RegisteredRelayers::::insert(REGISTER_RELAYER, registration); + + assert_noop!( + BridgeRelayers::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 = NextLaneRelayersSet::empty(100); + for i in 1..=MaxNextRelayersPerLane::get() as u64 { + assert!(lane_relayers.try_insert(REGISTER_RELAYER + i, 0)); + } + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get() + LaneStake::get()), + ); + NextLaneRelayers::::insert(test_lane_id(), lane_relayers); + + assert_noop!( + BridgeRelayers::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() + LaneStake::get() - 1), + ); + + assert_noop!( + BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(FAILING_RELAYER), + test_lane_id(), + 0 + ), + Error::::StakeIsTooLow, + ); + }); + } + + #[test] + fn register_at_lane_works() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get() + LaneStake::get()), + ); + RegisteredRelayers::::insert( + REGISTER_RELAYER_2, + registration(151, Stake::get() + LaneStake::get()), + ); + + // when first relayer registers, we allow other relayers to register in next + // `InitialElectionLength` blocks + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 1 + )); + 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!( + next_lane_relayers.relayers(), + &[LaneRegistration::new(REGISTER_RELAYER, 1)] + ); + + // next relayer registers, it occupies the correct slot in the set + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER_2), + test_lane_id(), + 0 + )); + 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!( + next_lane_relayers.relayers(), + &[ + LaneRegistration::new(REGISTER_RELAYER_2, 0), + LaneRegistration::new(REGISTER_RELAYER, 1) + ] + ); + }); + } + + #[test] + fn register_at_lane_may_be_used_to_change_expected_reward() { + run_test(|| { + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get() + LaneStake::get()), + ); + + // at first we want reward `1` + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 1 + )); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!( + next_lane_relayers.relayers(), + &[LaneRegistration::new(REGISTER_RELAYER, 1)] + ); + + // but then we change our expected reward + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0 + )); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!( + next_lane_relayers.relayers(), + &[LaneRegistration::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 = NextLaneRelayersSet::empty(100); + for i in 1..MaxNextRelayersPerLane::get() as u64 { + assert!(lane_relayers.try_insert(REGISTER_RELAYER + 100 + i, 10)); + } + RegisteredRelayers::::insert( + REGISTER_RELAYER, + registration(151, Stake::get() + LaneStake::get()), + ); + RegisteredRelayers::::insert( + REGISTER_RELAYER_2, + registration(151, Stake::get() + LaneStake::get()), + ); + NextLaneRelayers::::insert(test_lane_id(), lane_relayers); + + // occupy last entry by `REGISTER_RELAYER` with bid = 15 + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 15 + ),); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.relayers().len() as u32, MaxNextRelayersPerLane::get()); + assert_eq!( + next_lane_relayers.relayers().last(), + Some(&LaneRegistration::new(REGISTER_RELAYER, 15)) + ); + + // then the `REGISTER_RELAYER_2` comes with better bid = 14 + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER_2), + test_lane_id(), + 14 + ),); + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.relayers().len() as u32, MaxNextRelayersPerLane::get()); + assert_eq!( + next_lane_relayers.relayers().last(), + Some(&LaneRegistration::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!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 13 + ),); + + let next_lane_relayers = BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap(); + assert_eq!(next_lane_relayers.relayers().len() as u32, MaxNextRelayersPerLane::get()); + assert_eq!( + next_lane_relayers.relayers().last(), + Some(&LaneRegistration::new(REGISTER_RELAYER, 13)) + ); + }); + } + + #[test] + fn deregister_at_lane_fails_for_unregistered_relayer() { + run_test(|| { + assert_noop!( + BridgeRelayers::deregister_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + ), + Error::::NotRegistered, + ); + }); + } + + #[test] + 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); + + // when relayer is not in the active set + assert_ok!(BridgeRelayers::deregister_at_lane( + 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_insert(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] + fn deregister_at_lane_works_when_relayer_is_not_in_active_set() { + 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 + )); + 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(), + &[LaneRegistration::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_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_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::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() + )); + 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(), + &[ActiveLaneRegistration::new(REGISTER_RELAYER, 0)] + ); + assert_eq!(BridgeRelayers::next_lane_relayers(test_lane_id()).unwrap().relayers(), &[]); + }); + } + + #[test] + fn advance_lane_epoch_requires_signed_origin() { + run_test(|| { + assert_noop!( + BridgeRelayers::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!( + BridgeRelayers::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() + LaneStake::get()), + ); + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0, + )); + + assert_noop!( + BridgeRelayers::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() + LaneStake::get()), + ); + assert_ok!(BridgeRelayers::register_at_lane( + RuntimeOrigin::signed(REGISTER_RELAYER), + test_lane_id(), + 0, + )); + + 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(next_lane_relayers.may_enact_at() + 77); + let result = BridgeRelayers::advance_lane_epoch( + RuntimeOrigin::signed(REGISTER_RELAYER), + 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!( + 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(), &[]); + }); + } + + #[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) ); - assert!(Pallet::::is_registration_active(®ISTER_RELAYER)); }); } } diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs index b527b460eb..4d930663d7 100644 --- a/modules/relayers/src/mock.rs +++ b/modules/relayers/src/mock.rs @@ -166,6 +166,7 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< Balances, ReserveId, Stake, + LaneStake, Lease, >; @@ -189,6 +190,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; @@ -198,8 +200,11 @@ parameter_types! { pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); pub MaxActiveRelayersPerLane: u32 = 4; pub MaxNextRelayersPerLane: u32 = 16; + pub InitialElectionLength: u32 = 4; pub SlotLength: u32 = 16; - pub PriorityBoostForActiveLaneRelayer: TransactionPriority = 1; + pub EpochLength: u32 = 1_024; + pub MaxLanesPerRelayer: u32 = 4; + pub PriorityBoostForActiveLaneRelayer: TransactionPriority = 4; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] @@ -287,9 +292,12 @@ impl pallet_bridge_relayers::Config for TestRuntime { type Reward = ThisChainBalance; type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; + type MaxLanesPerRelayer = MaxLanesPerRelayer; type MaxActiveRelayersPerLane = MaxActiveRelayersPerLane; type MaxNextRelayersPerLane = MaxNextRelayersPerLane; + type InitialElectionLength = InitialElectionLength; type SlotLength = SlotLength; + type EpochLength = EpochLength; type PriorityBoostPerMessage = ConstU64<1>; type PriorityBoostForActiveLaneRelayer = PriorityBoostForActiveLaneRelayer; type WeightInfo = (); @@ -318,6 +326,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; @@ -395,8 +405,15 @@ 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()) + .unwrap(); test() }) diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs index 55ed1622f2..fd1b96edd0 100644 --- a/modules/relayers/src/payment_adapter.rs +++ b/modules/relayers/src/payment_adapter.rs @@ -16,17 +16,21 @@ //! Code that allows relayers pallet to be used as a payment mechanism for the messages pallet. -use crate::{Config, Pallet}; +use crate::{ActiveLaneRelayers, Config, Pallet}; use bp_messages::{ source_chain::{DeliveryConfirmationPayments, RelayersRewards}, + target_chain::DeliveryPayments, LaneId, MessageNonce, }; -use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_relayers::{RelayerRewardAtSource, RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; -use frame_support::traits::Get; -use sp_arithmetic::traits::{Saturating, UniqueSaturatedFrom}; -use sp_runtime::traits::UniqueSaturatedInto; +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 @@ -89,6 +93,41 @@ where } } +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, + ) -> Option { + ActiveLaneRelayers::::get(lane) + .relayer(relayer) + .map(|r| r.relayer_reward_per_message()) + } + + fn pay_reward( + lane_id: LaneId, + relayer: T::AccountId, + _total_messages: MessageNonce, + valid_messages: MessageNonce, + _actual_weight: Weight, + ) { + if valid_messages == 0 { + return + } + + // remember that the relayer has delivered messages + ActiveLaneRelayers::::mutate_extant(lane_id, |active_lane_relayers| { + active_lane_relayers.note_delivered_message(&relayer); + }); + } +} + /// Register relayer rewards for delivering messages. fn register_relayers_rewards( relayers_rewards: RelayersRewards, @@ -103,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; @@ -165,7 +205,9 @@ mod tests { bp_messages::DeliveredMessages::new(1, Some(MAX_REWARD_PER_MESSAGE + 1)); delivered_messages.note_dispatched_message(); - TestDeliveryConfirmationPaymentsAdapter::pay_reward( + >::pay_reward( test_lane_id(), vec![bp_messages::UnrewardedRelayer { relayer: 42, messages: delivered_messages }] .into(), @@ -179,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), + ); + }) + } } diff --git a/modules/relayers/src/stake_adapter.rs b/modules/relayers/src/stake_adapter.rs index 2509e93368..83bf5d84f9 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 { @@ -129,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()), @@ -173,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/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs index c2c065b0c0..082c113d1e 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-30, 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/rip-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,13 @@ 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 deregister_at_lane() -> Weight; + fn advance_lane_epoch() -> Weight; fn slash_and_deregister() -> Weight; fn register_relayer_reward() -> Weight; } @@ -62,98 +66,203 @@ 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: 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)) } - /// 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: 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)) } - /// 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: 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)) + } + /// 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: 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)) + } + /// 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: 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)) } - /// 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`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// 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: `1340` + // Estimated: `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) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Storage: System Account (r:1 w:1) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode: - /// MaxEncodedLen) - fn slash_and_deregister() -> Weight { + /// 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: `380` - // Estimated: `11412` - // Minimum execution time: 42_358 nanoseconds. - Weight::from_parts(43_539_000, 11412) + // Measured: `1596` + // Estimated: `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(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(697), + /// added: 3172, 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: `56740` + // Estimated: `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)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// 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(697), + /// added: 3172, 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: + /// `MaxEncodedLen`) + /// + /// Storage: `System::Account` (r:1 w:1) + /// + /// 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: `2395` + // Estimated: `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)) } - /// 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: 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)) } @@ -161,98 +270,203 @@ 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: 50_439 nanoseconds. + Weight::from_parts(51_446_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: 21_916 nanoseconds. + Weight::from_parts(23_028_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: 25_145 nanoseconds. + Weight::from_parts(26_059_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: 12_568 nanoseconds. + Weight::from_parts(13_014_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: 29_748 nanoseconds. + Weight::from_parts(30_408_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`) + /// + /// Storage: `BridgeRelayers::NextLaneRelayers` (r:1 w:1) /// - /// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539, - /// mode: MaxEncodedLen) + /// 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: `1340` + // Estimated: `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) /// - /// Storage: Balances Reserves (r:1 w:1) + /// Proof: `BridgeRelayers::ActiveLaneRelayers` (`max_values`: None, `max_size`: Some(697), + /// added: 3172, mode: `MaxEncodedLen`) /// - /// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode: - /// MaxEncodedLen) + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) /// - /// Storage: System Account (r:1 w:1) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(577), + /// added: 3052, mode: `MaxEncodedLen`) /// - /// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode: - /// MaxEncodedLen) - fn slash_and_deregister() -> Weight { + /// 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: `380` - // Estimated: `11412` - // Minimum execution time: 42_358 nanoseconds. - Weight::from_parts(43_539_000, 11412) + // Measured: `1596` + // Estimated: `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(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(697), + /// added: 3172, 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: `56740` + // Estimated: `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)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// + /// 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(697), + /// added: 3172, 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: + /// `MaxEncodedLen`) + /// + /// Storage: `System::Account` (r:1 w:1) + /// + /// 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: `2395` + // Estimated: `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)) } - /// 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: 6_682 nanoseconds. + Weight::from_parts(7_047_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs index 8067b2dcca..6ee3014945 100644 --- a/primitives/messages/src/lib.rs +++ b/primitives/messages/src/lib.rs @@ -440,7 +440,7 @@ pub struct InboundMessageDetails { /// relayer and whose confirmations are still pending. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct UnrewardedRelayer { - /// Identifier of the relayer. + /// Identifier of the relayer at the source (sending) chain. pub relayer: RelayerId, /// Messages range, delivered by this relayer. pub messages: DeliveredMessages, diff --git a/primitives/relayers/Cargo.toml b/primitives/relayers/Cargo.toml index 8f0db3afe3..df912d67c7 100644 --- a/primitives/relayers/Cargo.toml +++ b/primitives/relayers/Cargo.toml @@ -25,6 +25,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 } @@ -44,6 +45,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/lane_relayers.rs b/primitives/relayers/src/lane_relayers.rs index 6abe878332..0687de57ef 100644 --- a/primitives/relayers/src/lane_relayers.rs +++ b/primitives/relayers/src/lane_relayers.rs @@ -153,6 +153,17 @@ where .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 relayer transactions. 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.active_set.len() != len_before + } + /// Activate next set of relayers. /// /// This set is replaced with the `next_set` contents. diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs index 0089afe3c4..4ad7461f96 100644 --- a/primitives/relayers/src/lib.rs +++ b/primitives/relayers/src/lib.rs @@ -84,6 +84,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 { diff --git a/primitives/relayers/src/registration.rs b/primitives/relayers/src/registration.rs index bc2d0d127a..e56ebfca6b 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 @@ -39,16 +39,27 @@ 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(MaxLanesPerRelayer))] +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 @@ -57,18 +68,140 @@ pub struct Registration { /// /// Please keep in mind that priority boost stops working some blocks before the /// registration ends (see [`StakeAndSlash::RequiredRegistrationLease`]). - pub valid_till: BlockNumber, + /// + /// Please also keep in mind that while relayer is registered at least at one lane, + /// the registration is always considered active. + 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. - pub stake: Balance, + /// The `stake` could be less than the [`StakeAndSlash::RequiredStake`] plus additional + /// [`StakeAndSlash::RequiredStake`] for every entry in the [`Self::lanes`] vector. In this + /// case, registration is still considered **ACTIVE**. The missing amount will be reserved + /// on the relayer account when he'll be modifying its registration in any way. + stake: Balance, + /// All lanes, where relayer has explicitly registered itself for additional + /// priority boost. + /// + /// Relayer pays additional [`StakeAndSlash::RequiredStake`] for every lane. + /// + /// The entry in this vector does not guarantee that the relayer is actually in + /// the active or the next set of relayers at given lane. It only says that the + /// relayer has tried to register at the lane. + lanes: BoundedVec, +} + +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() } + } + + /// Returns the last block number, where this registration is considered active. + /// + /// `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 { + 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 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 { + 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, + current_block_number: BlockNumber, + required_registration_lease: BlockNumber, + ) -> bool { + // 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 = 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 { + // still return true - we allow relayer to change his bid (expected reward) + true + } + } + + /// Remove lane registration. + 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() + } } /// 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 @@ -101,6 +234,7 @@ where BlockNumber: Default, { type RequiredStake = (); + type RequiredLaneStake = (); type RequiredRegistrationLease = (); fn reserve(_relayer: &AccountId, _amount: Balance) -> DispatchResult {