Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Manual] Marking offline collators not producing blocks during X rounds #2259

Merged
merged 34 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c5dba6
begin manual mark-offline
Agusrodri Apr 25, 2023
0abb53b
remove CandidateLastActive
Agusrodri Apr 25, 2023
f94f2dc
fix precompile staking
Agusrodri Apr 25, 2023
53e88d9
Merge branch 'master' into agustin-mark-offline-manual
Agusrodri Apr 25, 2023
c85d6bc
comments and fmt
Agusrodri Apr 27, 2023
c15b68b
fix do_go_offline() weight
Agusrodri Apr 27, 2023
dfca843
Merge branch 'master' into agustin-mark-offline-manual
Agusrodri Aug 7, 2023
0ebf241
make it compile
Agusrodri Aug 7, 2023
8dd8de2
apply some suggestions
Agusrodri Aug 7, 2023
edf7606
Merge branch 'master' into agustin-mark-offline-manual
Agusrodri Aug 28, 2023
f492b18
test case MaxOfflineRounds < RewardPaymentDelay
Agusrodri Aug 28, 2023
75edd04
fix test
Agusrodri Aug 28, 2023
5772221
add killswitch for marking offline feature
Agusrodri Aug 28, 2023
f6c620a
fix mock config in parachain staking precompile
Agusrodri Aug 28, 2023
323f2cf
add benchmark for notify_inactive_collator extrinsic
Agusrodri Aug 30, 2023
bb15115
Merge remote-tracking branch 'origin/master' into agustin-mark-offlin…
Agusrodri Aug 30, 2023
1c37f1d
refactor benchmark
Agusrodri Aug 30, 2023
3d62950
fix call to select_top_candidates in benchmark
Agusrodri Aug 30, 2023
639c7ff
minor changes in benchmark
Agusrodri Aug 31, 2023
e9ca1a6
add killswitch as a storage item
Agusrodri Oct 3, 2023
ac93655
Merge remote-tracking branch 'origin/master' into agustin-mark-offlin…
Agusrodri Oct 3, 2023
2e28afb
fmt
Agusrodri Oct 3, 2023
80f33d4
remove ConstBool
Agusrodri Oct 3, 2023
7aaf43b
enable killswitch in benchmark
Agusrodri Oct 3, 2023
6e45d4d
Merge remote-tracking branch 'origin/master' into agustin-mark-offlin…
Agusrodri Oct 4, 2023
7aee203
add enable_marking_offline extrinsic
Agusrodri Oct 4, 2023
418295c
optmize collators_len read and benchmark
Agusrodri Oct 5, 2023
85301b1
Add locally-generated benchmark for notify_inactive_collator
notlesh Oct 5, 2023
b5ea5cd
Hook up newly added benchmark to pallet call
notlesh Oct 5, 2023
166a300
add extra check for current round
Agusrodri Oct 5, 2023
31af76e
Merge branch 'agustin-mark-offline-manual' of github.com:moonbeam-fou…
Agusrodri Oct 5, 2023
518b3a9
minor changes in tests
Agusrodri Oct 6, 2023
bfd327d
add test
Agusrodri Oct 6, 2023
b036f4c
modify rounds_to_check syntax
Agusrodri Oct 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions pallets/parachain-staking/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
//! Benchmarking
use crate::{
AwardedPts, BalanceOf, BottomDelegations, Call, CandidateBondLessRequest, Config,
DelegationAction, Pallet, ParachainBondConfig, ParachainBondInfo, Points, Range, RewardPayment,
Round, ScheduledRequest, Staked, TopDelegations,
DelegationAction, EnableMarkingOffline, Pallet, ParachainBondConfig, ParachainBondInfo, Points,
Range, RewardPayment, Round, ScheduledRequest, Staked, TopDelegations,
};
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite};
use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize};
Expand Down Expand Up @@ -2192,6 +2192,71 @@ benchmarks! {
verify {
assert_eq!(T::Currency::free_balance(&collator), original_free_balance + 50u32.into());
}

notify_inactive_collator {
use crate::{AtStake, CollatorSnapshot, AwardedPts};

// Blocks per-round must be greater than TotalSelected
Pallet::<T>::set_blocks_per_round(RawOrigin::Root.into(), 101u32)?;
Pallet::<T>::set_total_selected(RawOrigin::Root.into(), 100u32)?;

let mut candidate_count = 1u32;
let mut seed = USER_SEED;

// Create 100 collators
for i in 0..100 {
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
seed += i;
let collator = create_funded_collator::<T>(
"collator",
seed,
min_candidate_stk::<T>() * 1_000_000u32.into(),
true,
candidate_count
)?;
candidate_count += 1;
}

// Create two collators more: the one that will be marked as inactive
// and the one that will act as the caller of the extrinsic.
seed += 1;
let inactive_collator: T::AccountId = create_funded_collator::<T>(
"collator",
seed,
min_candidate_stk::<T>() * 1_000_000u32.into(),
true,
candidate_count
)?;
candidate_count += 1;

seed += 1;
let caller: T::AccountId = create_funded_collator::<T>(
"collator",
seed,
min_candidate_stk::<T>() * 1_000_000u32.into(),
true,
candidate_count
)?;

// Roll to round 2 and call to select_top_candidates.
// We do this to be able to have more than 66% of TotalSelected.
roll_to_and_author::<T>(2, caller.clone());
Pallet::<T>::select_top_candidates(2);

// Manually change these values for inactive_collator,
// so that it can be marked as inactive.
<AtStake<T>>::insert(1, &inactive_collator, CollatorSnapshot::default());
<AwardedPts<T>>::insert(1, &inactive_collator, 0);

<AtStake<T>>::insert(2, &inactive_collator, CollatorSnapshot::default());
<AwardedPts<T>>::insert(2, &inactive_collator, 0);

// Enable killswitch
<EnableMarkingOffline<T>>::set(true);

}: _(RawOrigin::Signed(caller), inactive_collator.clone())
verify {
assert!(!Pallet::<T>::candidate_info(&inactive_collator).expect("must exist").is_active());
}
}

#[cfg(test)]
Expand Down
84 changes: 83 additions & 1 deletion pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ pub mod pallet {
/// Minimum number of blocks per round
#[pallet::constant]
type MinBlocksPerRound: Get<u32>;
/// If a collator doesn't produce any block on this number of rounds, it is notified as inactive
#[pallet::constant]
type MaxOfflineRounds: Get<u32>;
/// Number of rounds that candidates remain bonded before exit request is executable
#[pallet::constant]
type LeaveCandidatesDelay: Get<RoundIndex>;
Expand Down Expand Up @@ -170,6 +173,10 @@ pub mod pallet {
/// Handler to distribute a collator's reward.
/// To use the default implementation of minting rewards, specify the type `()`.
type PayoutCollatorReward: PayoutCollatorReward<Self>;
/// Handler to notify the runtime when a collator is inactive.
/// The default behavior is to mark the collator as offline.
/// If you need to use the default implementation, specify the type `()`.
type OnInactiveCollator: OnInactiveCollator<Self>;
/// Handler to notify the runtime when a new round begin.
/// If you don't need it, you can specify the type `()`.
type OnNewRound: OnNewRound;
Expand Down Expand Up @@ -227,12 +234,15 @@ pub mod pallet {
TooLowDelegationCountToAutoCompound,
TooLowCandidateAutoCompoundingDelegationCountToAutoCompound,
TooLowCandidateAutoCompoundingDelegationCountToDelegate,
TooLowCollatorCountToNotifyAsInactive,
CannotBeNotifiedAsInactive,
TooLowCandidateAutoCompoundingDelegationCountToLeaveCandidates,
TooLowCandidateCountWeightHint,
TooLowCandidateCountWeightHintGoOffline,
CandidateLimitReached,
CannotSetAboveMaxCandidates,
RemovedCall,
MarkingOfflineNotEnabled,
}

#[pallet::event]
Expand Down Expand Up @@ -608,7 +618,7 @@ pub mod pallet {
Twox64Concat,
T::AccountId,
CollatorSnapshot<T::AccountId, BalanceOf<T>>,
ValueQuery,
OptionQuery,
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
>;

#[pallet::storage]
Expand Down Expand Up @@ -645,6 +655,11 @@ pub mod pallet {
ValueQuery,
>;

#[pallet::storage]
#[pallet::getter(fn marking_offline)]
/// Killswitch to enable/disable marking offline feature.
pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
/// Initialize balance and register all as collators: `(collator AccountId, balance Amount)`
Expand Down Expand Up @@ -1375,6 +1390,73 @@ pub mod pallet {

Ok(().into())
}

/// Notify a collator is inactive during MaxOfflineRounds
#[pallet::call_index(29)]
#[pallet::weight(
// TODO: add the proper benchmark function once it is available
T::DbWeight::get().reads_writes(0 as u64, 0 as u64)
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
)]
pub fn notify_inactive_collator(
origin: OriginFor<T>,
collator: T::AccountId,
) -> DispatchResult {
ensure!(
<EnableMarkingOffline<T>>::get(),
<Error<T>>::MarkingOfflineNotEnabled
);
ensure_signed(origin)?;

let collators = <SelectedCandidates<T>>::get();
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
let max_collators = <TotalSelected<T>>::get();

// Check collators length is not below or eq to 66% of max_collators.
// We use saturating logic here with (2/3)
// as it is dangerous to use floating point numbers directly.
ensure!(
collators.len() * 3 > (max_collators * 2) as usize,
<Error<T>>::TooLowCollatorCountToNotifyAsInactive
);

let round_info = <Round<T>>::get();
let max_offline_rounds = T::MaxOfflineRounds::get();

// Take last round to have rounds_to_check = [7,8,9]
// in case we are in round 10 for instance
let round = round_info.current.saturating_sub(1);

let mut rounds_to_check: Vec<RoundIndex> = vec![];
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
for round_index in (0..max_offline_rounds).rev() {
rounds_to_check.push(round.saturating_sub(round_index));
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
}
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved

// If this counter is eq to max_offline_rounds,
// the collator should be notified as inactive
let mut inactive_counter: RoundIndex = 0u32;

// Iter rounds to check
//
// - The collator has AtStake associated and their AwardedPts are zero
//
// If the previous condition is met in all rounds of rounds_to_check,
// the collator is notified as inactive
for r in rounds_to_check {
let stake = <AtStake<T>>::get(r, &collator);
let pts = <AwardedPts<T>>::get(r, &collator);

if stake.is_some() && pts.is_zero() {
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
inactive_counter = inactive_counter.saturating_add(1);
}
}

if inactive_counter == max_offline_rounds {
let _ = T::OnInactiveCollator::on_inactive_collator(collator.clone(), round);
} else {
return Err(<Error<T>>::CannotBeNotifiedAsInactive.into());
}

Ok(().into())
}
}

/// Represents a payout made via `pay_one_collator_reward`.
Expand Down
9 changes: 9 additions & 0 deletions pallets/parachain-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
pallet, AwardedPts, Config, Event as ParachainStakingEvent, InflationInfo, Points, Range,
COLLATOR_LOCK_ID, DELEGATOR_LOCK_ID,
};
use block_author::BlockAuthor as BlockAuthorMap;
use frame_support::{
construct_runtime, parameter_types,
traits::{Everything, GenesisBuild, LockIdentifier, OnFinalize, OnInitialize},
Expand Down Expand Up @@ -111,6 +112,7 @@ const GENESIS_PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(30
const GENESIS_NUM_SELECTED_CANDIDATES: u32 = 5;
parameter_types! {
pub const MinBlocksPerRound: u32 = 3;
pub const MaxOfflineRounds: u32 = 1;
pub const LeaveCandidatesDelay: u32 = 2;
pub const CandidateBondLessDelay: u32 = 2;
pub const LeaveDelegatorsDelay: u32 = 2;
Expand All @@ -130,6 +132,7 @@ impl Config for Test {
type Currency = Balances;
type MonetaryGovernanceOrigin = frame_system::EnsureRoot<AccountId>;
type MinBlocksPerRound = MinBlocksPerRound;
type MaxOfflineRounds = MaxOfflineRounds;
type LeaveCandidatesDelay = LeaveCandidatesDelay;
type CandidateBondLessDelay = CandidateBondLessDelay;
type LeaveDelegatorsDelay = LeaveDelegatorsDelay;
Expand All @@ -145,6 +148,7 @@ impl Config for Test {
type BlockAuthor = BlockAuthor;
type OnCollatorPayout = ();
type PayoutCollatorReward = ();
type OnInactiveCollator = ();
type OnNewRound = ();
type WeightInfo = ();
type MaxCandidates = MaxCandidates;
Expand Down Expand Up @@ -515,6 +519,11 @@ pub(crate) fn set_author(round: BlockNumber, acc: u64, pts: u32) {
<AwardedPts<Test>>::mutate(round, acc, |p| *p += pts);
}

// Allows to change the block author (default is always 0)
pub(crate) fn set_block_author(acc: u64) {
<BlockAuthorMap<Test>>::set(acc);
}

/// fn to query the lock amount
pub(crate) fn query_lock_amount(account_id: u64, id: LockIdentifier) -> Option<Balance> {
for lock in Balances::locks(&account_id) {
Expand Down
Loading
Loading