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

staking pallet change for fusion #20

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions substrate/frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ pub mod inflation;
pub mod ledger;
pub mod migrations;
pub mod slashing;
pub mod traits;
pub mod weights;

mod pallet;
Expand Down
204 changes: 141 additions & 63 deletions substrate/frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ use crate::{
election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo,
BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure,
MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf,
RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs,
RewardDestination, SessionInterface, StakingLedger, traits::FusionExt, ValidatorPrefs,
};

// FUSION CHANGE
// use pallet_fusion::FusionExt;

use super::pallet::*;

#[cfg(feature = "try-runtime")]
Expand Down Expand Up @@ -336,7 +339,14 @@ impl<T: Config> Pallet<T> {
if amount.is_zero() {
return None
}
let dest = Self::payee(StakingAccount::Stash(stash.clone()))?;
// FUSION CHANGE
let dest = Self::payee(StakingAccount::Stash(stash.clone())).or_else(|| {
if T::FusionExt::get_pool_id_from_funds_account(stash).is_some() {
Some(RewardDestination::Account(stash.clone()))
} else {
None
}
})?;

let maybe_imbalance = match dest {
RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(),
Expand Down Expand Up @@ -492,6 +502,9 @@ impl<T: Config> Pallet<T> {
});

Self::apply_unapplied_slashes(active_era);

// FUSION CHANGE
T::FusionExt::set_fusion_exposures();
}

/// Compute payout for era.
Expand All @@ -502,8 +515,12 @@ impl<T: Config> Pallet<T> {

let era_duration = (now_as_millis_u64.defensive_saturating_sub(active_era_start))
.saturated_into::<u64>();
let staked = Self::eras_total_stake(&active_era.index);
let mut staked = Self::eras_total_stake(&active_era.index);
let issuance = T::Currency::total_issuance();
// FUSION CHANGE
if staked > issuance {
staked = issuance;
}
let (validator_payout, remainder) =
T::EraPayout::era_payout(staked, issuance, era_duration);

Expand All @@ -517,6 +534,9 @@ impl<T: Config> Pallet<T> {
<ErasValidatorReward<T>>::insert(&active_era.index, validator_payout);
T::RewardRemainder::on_unbalanced(T::Currency::issue(remainder));

// FUSION CHANGE
T::FusionExt::handle_end_era(active_era.index, era_duration);

// Clear offending validators.
<OffendingValidators<T>>::kill();
}
Expand Down Expand Up @@ -671,6 +691,10 @@ impl<T: Config> Pallet<T> {
T::CurrencyToVote::to_currency(e, total_issuance)
};

let active_era = ActiveEra::<T>::get()
.map(|era_info| era_info.index)
.unwrap_or(0);

supports
.into_iter()
.map(|(validator, support)| {
Expand All @@ -686,6 +710,11 @@ impl<T: Config> Pallet<T> {
if nominator == validator {
own = own.saturating_add(stake);
} else {
// FUSION CHANGE
// This will update the fusion exposure in case the nominator is a fusion pool.
let _ = T::FusionExt::update_pool_exposure(
&nominator, &validator, stake, active_era,
);
others.push(IndividualExposure { who: nominator, value: stake });
}
total = total.saturating_add(stake);
Expand Down Expand Up @@ -825,6 +854,14 @@ impl<T: Config> Pallet<T> {
bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
};

// FUSION CHANGE
// We account for the fusion voters count in the final_predicted_len.
// We do not update final_predicted_len as the next 'while' loop would have been unecessary longer.
let fusion_voters_count = T::FusionExt::get_active_pool_count()
.try_into()
.unwrap_or(u32::MIN);
let final_predicted_len = final_predicted_len.saturating_add(fusion_voters_count);

let mut all_voters = Vec::<_>::with_capacity(final_predicted_len as usize);

// cache a few things.
Expand All @@ -835,76 +872,107 @@ impl<T: Config> Pallet<T> {
let mut nominators_taken = 0u32;
let mut min_active_stake = u64::MAX;

// FUSION CHANGE
let mut snapshot_voters_size_exceeded = false;
if fusion_voters_count > 0 {
let fusion_voters = T::FusionExt::get_fusion_voters();
for (account, value, targets) in fusion_voters.into_iter() {
let Ok(bounded_targets) = BoundedVec::try_from(targets) else {
log::error!("Failed to convert targets for account: {:?}", account);
continue;
};
let fusion_vote = (account, value, bounded_targets);
if voters_size_tracker
.try_register_voter(&fusion_vote, &bounds)
.is_err()
{
// No more space left for the election snapshot, stop iterating.
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
size: voters_size_tracker.size as u32,
});
snapshot_voters_size_exceeded = true;
break;
}
all_voters.push(fusion_vote);
nominators_taken.saturating_inc();
if value < min_active_stake {
min_active_stake = value;
}
}
}

let mut sorted_voters = T::VoterList::iter();
while all_voters.len() < final_predicted_len as usize &&
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
{
let voter = match sorted_voters.next() {
Some(voter) => {
voters_seen.saturating_inc();
voter
},
None => break,
};
if !snapshot_voters_size_exceeded {
while all_voters.len() < final_predicted_len as usize &&
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
{
let voter = match sorted_voters.next() {
Some(voter) => {
voters_seen.saturating_inc();
voter
},
None => break,
};

let voter_weight = weight_of(&voter);
// if voter weight is zero, do not consider this voter for the snapshot.
if voter_weight.is_zero() {
log!(debug, "voter's active balance is 0. skip this voter.");
continue
}
let voter_weight = weight_of(&voter);
// if voter weight is zero, do not consider this voter for the snapshot.
if voter_weight.is_zero() {
log!(debug, "voter's active balance is 0. skip this voter.");
continue
}

if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
if !targets.is_empty() {
// Note on lazy nomination quota: we do not check the nomination quota of the
// voter at this point and accept all the current nominations. The nomination
// quota is only enforced at `nominate` time.
if let Some(Nominations { targets, .. }) = <Nominators<T>>::get(&voter) {
if !targets.is_empty() {
// Note on lazy nomination quota: we do not check the nomination quota of the
// voter at this point and accept all the current nominations. The nomination
// quota is only enforced at `nominate` time.

let voter = (voter, voter_weight, targets);
if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
// no more space left for the election result, stop iterating.
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
size: voters_size_tracker.size as u32,
});
break
}

all_voters.push(voter);
nominators_taken.saturating_inc();
} else {
// technically should never happen, but not much we can do about it.
}
min_active_stake =
if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
} else if Validators::<T>::contains_key(&voter) {
// if this voter is a validator:
let self_vote = (
voter.clone(),
voter_weight,
vec![voter.clone()]
.try_into()
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
);

let voter = (voter, voter_weight, targets);
if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
// no more space left for the election result, stop iterating.
if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
// no more space left for the election snapshot, stop iterating.
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
size: voters_size_tracker.size as u32,
});
break
}

all_voters.push(voter);
nominators_taken.saturating_inc();
all_voters.push(self_vote);
validators_taken.saturating_inc();
} else {
// technically should never happen, but not much we can do about it.
}
min_active_stake =
if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
} else if Validators::<T>::contains_key(&voter) {
// if this voter is a validator:
let self_vote = (
voter.clone(),
voter_weight,
vec![voter.clone()]
.try_into()
.expect("`MaxVotesPerVoter` must be greater than or equal to 1"),
);

if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
// no more space left for the election snapshot, stop iterating.
Self::deposit_event(Event::<T>::SnapshotVotersSizeExceeded {
size: voters_size_tracker.size as u32,
});
break
// this can only happen if: 1. there a bug in the bags-list (or whatever is the
// sorted list) logic and the state of the two pallets is no longer compatible, or
// because the nominators is not decodable since they have more nomination than
// `T::NominationsQuota::get_quota`. The latter can rarely happen, and is not
// really an emergency or bug if it does.
defensive!(
"DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
voter,
);
}
all_voters.push(self_vote);
validators_taken.saturating_inc();
} else {
// this can only happen if: 1. there a bug in the bags-list (or whatever is the
// sorted list) logic and the state of the two pallets is no longer compatible, or
// because the nominators is not decodable since they have more nomination than
// `T::NominationsQuota::get_quota`. The latter can rarely happen, and is not
// really an emergency or bug if it does.
defensive!(
"DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now",
voter,
);
}
}

Expand Down Expand Up @@ -1369,6 +1437,9 @@ where
consumed_weight += T::DbWeight::get().reads_writes(reads, writes);
};

// FUSION CHANGE
let mut fusion_weight = Weight::from_parts(0, 0);

let active_era = {
let active_era = Self::active_era();
add_db_reads_writes(1, 0);
Expand Down Expand Up @@ -1445,6 +1516,13 @@ where
add_db_reads_writes(rw, rw);
}
unapplied.reporters = details.reporters.clone();

// FUSION CHANGE
// We need to notify slashing in Fusion and record the funds, if needed.
// We need to do this so if the slash is applied manually, we find it
fusion_weight =
T::FusionExt::add_fusion_slash(slash_era, &stash, &unapplied.others);

if slash_defer_duration == 0 {
// Apply right away.
slashing::apply_slash::<T>(unapplied, slash_era);
Expand Down Expand Up @@ -1477,7 +1555,7 @@ where
}
}

consumed_weight
consumed_weight.saturating_add(fusion_weight)
}
}

Expand Down
15 changes: 13 additions & 2 deletions substrate/frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use crate::{
slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout,
EraRewardPoints, Exposure, ExposurePage, Forcing, MaxNominationsOf, NegativeImbalanceOf,
Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface,
StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs,
StakingLedger, traits::FusionExt, UnappliedSlash, UnlockChunk, ValidatorPrefs,
};

// The speculative number of spans are used as an input of the weight annotation of
Expand All @@ -61,6 +61,7 @@ pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32;
#[frame_support::pallet]
pub mod pallet {
use frame_election_provider_support::ElectionDataProvider;
// use pallet_fusion::FusionExt;

use crate::{BenchmarkingConfig, PagedExposureMetadata};

Expand Down Expand Up @@ -283,6 +284,9 @@ pub mod pallet {

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

/// Fusion pallet trait
type FusionExt: FusionExt<Self::AccountId, BalanceOf<Self>, u32>;
}

/// The ideal number of active validators.
Expand Down Expand Up @@ -1533,9 +1537,16 @@ pub mod pallet {
let last_item = slash_indices[slash_indices.len() - 1];
ensure!((last_item as usize) < unapplied.len(), Error::<T>::InvalidSlashIndex);

// FUSION CHANGE
let mut removed_slash_validators = Vec::new();
for (removed, index) in slash_indices.into_iter().enumerate() {
let index = (index as usize) - removed;
unapplied.remove(index);
let removed_element = unapplied.remove(index);
removed_slash_validators.push(removed_element.validator);
}

if !removed_slash_validators.is_empty() {
T::FusionExt::cancel_fusion_slash(era, &removed_slash_validators);
}

UnappliedSlashes::<T>::insert(&era, &unapplied);
Expand Down
22 changes: 14 additions & 8 deletions substrate/frame/staking/src/slashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@

use crate::{
BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra,
OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash,
OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, traits::FusionExt, UnappliedSlash,
ValidatorSlashInEra,
};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
ensure,
traits::{Currency, Defensive, DefensiveSaturating, Get, Imbalance, OnUnbalanced},
};
// use pallet_fusion::FusionExt;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{Saturating, Zero},
Expand Down Expand Up @@ -648,13 +649,18 @@ pub(crate) fn apply_slash<T: Config>(
);

for &(ref nominator, nominator_slash) in &unapplied_slash.others {
do_slash::<T>(
nominator,
nominator_slash,
&mut reward_payout,
&mut slashed_imbalance,
slash_era,
);
// FUSION CHANGE
let is_fusion_pool =
T::FusionExt::apply_fusion_slash(slash_era, &unapplied_slash.validator, nominator);
if !is_fusion_pool {
do_slash::<T>(
nominator,
nominator_slash,
&mut reward_payout,
&mut slashed_imbalance,
slash_era,
);
}
}

pay_reporters::<T>(reward_payout, slashed_imbalance, &unapplied_slash.reporters);
Expand Down
Loading
Loading