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

No unbonding when signer or next signer #1031

Merged
merged 24 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 18 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ At the moment this project **does not** adhere to
cleaned up. A lot of storage entries, events, and extrinsics were removed from the `Registry`
pallet. The genesis build config was also removed. Additionally, the `new/user/` HTTP endpoint in
the TSS was removed since it was no longer necessary.
- in [#1031](https://github.com/entropyxyz/entropy-core/pull/1031), more Staking calls were blocked to go through staking_extention. This makes sure no funds can be unbonded from a validator if they are currently in the signing comittee. This was applied to `unbond`, `chill`, and `withdraw_unbonded`
JesseAbram marked this conversation as resolved.
Show resolved Hide resolved

### Added
- Jumpstart network ([#918](https://github.com/entropyxyz/entropy-core/pull/918))
Expand All @@ -36,6 +37,7 @@ At the moment this project **does not** adhere to

### Changed
- Fix TSS `AccountId` keys in chainspec ([#993](https://github.com/entropyxyz/entropy-core/pull/993))
- No unbonding when signer or next signer ([#1031](https://github.com/entropyxyz/entropy-core/pull/1031))

### Removed
- Remove `prune_registration` extrinsic ([#1022](https://github.com/entropyxyz/entropy-core/pull/1022))
Expand Down
Binary file modified crates/client/entropy_metadata.scale
Binary file not shown.
2 changes: 1 addition & 1 deletion node/cli/src/chain_spec/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use sc_service::ChainType;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::{sr25519, ByteArray};
use sp_runtime::Perbill;
use sp_runtime::{BoundedVec, Perbill};

pub fn devnet_three_node_initial_tss_servers(
) -> Vec<(sp_runtime::AccountId32, TssX25519PublicKey, String)> {
Expand Down
2 changes: 1 addition & 1 deletion node/cli/src/chain_spec/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use sc_service::ChainType;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::{sr25519, ByteArray};
use sp_runtime::Perbill;
use sp_runtime::{BoundedVec, Perbill};

/// The configuration used for the Threshold Signature Scheme server integration tests.
///
Expand Down
76 changes: 75 additions & 1 deletion pallets/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ use frame_support::{
};
use frame_system::{EventRecord, RawOrigin};
use pallet_parameters::{SignersInfo, SignersSize};
use pallet_staking::{Pallet as FrameStaking, RewardDestination, ValidatorPrefs};
use pallet_staking::{
Event as FrameStakingEvent, Pallet as FrameStaking, RewardDestination, ValidatorPrefs,
};
use sp_std::{vec, vec::Vec};

use super::*;
Expand All @@ -42,6 +44,16 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
assert_eq!(event, &system_event);
}

fn assert_last_event_frame_staking<T: Config>(
generic_event: <T as pallet_staking::Config>::RuntimeEvent,
) {
let events = frame_system::Pallet::<T>::events();
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
// compare to the last event record
let EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}

pub fn create_validators<T: Config>(
count: u32,
seed: u32,
Expand Down Expand Up @@ -124,11 +136,73 @@ benchmarks! {
assert_last_event::<T>(Event::<T>::ThresholdAccountChanged(bonder, server_info).into());
}

unbond {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you take the body of this (and all the other benches added in this PR) and manually RustFmt it using the Rust Playground? This isn't done by the fmt script since it's since a decl macro.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping this

HCastano marked this conversation as resolved.
Show resolved Hide resolved
let s in 0 .. MAX_SIGNERS as u32;

let caller: T::AccountId = whitelisted_caller();
let validator_id_res = <T as pallet_session::Config>::ValidatorId::try_from(caller.clone()).or(Err(Error::<T>::InvalidValidatorId)).unwrap();
let bonder: T::AccountId = account("bond", 0, SEED);
let threshold: T::AccountId = account("threshold", 0, SEED);

let signers = vec![validator_id_res.clone(); s as usize];
Signers::<T>::put(signers.clone());
NextSigners::<T>::put(NextSignerInfo {
next_signers: signers,
confirmations: vec![],
});
prep_bond_and_validate::<T>(true, caller.clone(), bonder.clone(), threshold, NULL_ARR);
}: _(RawOrigin::Signed(bonder.clone()), 10u32.into())
verify {
assert_last_event_frame_staking::<T>(FrameStakingEvent::Unbonded{ stash: bonder, amount: 10u32.into() }.into() );

}

chill {
let c in 0 .. MAX_SIGNERS as u32;

let caller: T::AccountId = whitelisted_caller();
let validator_id_res = <T as pallet_session::Config>::ValidatorId::try_from(caller.clone()).or(Err(Error::<T>::InvalidValidatorId)).unwrap();
let bonder: T::AccountId = account("bond", 0, SEED);
let threshold: T::AccountId = account("threshold", 0, SEED);

let signers = vec![validator_id_res.clone(); c as usize];
Signers::<T>::put(signers.clone());
NextSigners::<T>::put(NextSignerInfo {
next_signers: signers,
confirmations: vec![],
});

prep_bond_and_validate::<T>(true, caller.clone(), bonder.clone(), threshold, NULL_ARR);
let bond = <T as pallet_staking::Config>::Currency::minimum_balance() * 10u32.into();

// assume fully unbonded as slightly more weight, but not enough to handle partial unbond
assert_ok!(<FrameStaking<T>>::unbond(
RawOrigin::Signed(bonder.clone()).into(),
bond,
));


}: _(RawOrigin::Signed(bonder.clone()))
verify {
assert_last_event_frame_staking::<T>(FrameStakingEvent::Chilled{ stash: bonder }.into() );

}


withdraw_unbonded {
let c in 0 .. MAX_SIGNERS as u32;

let caller: T::AccountId = whitelisted_caller();
let bonder: T::AccountId = account("bond", 0, SEED);
let threshold: T::AccountId = account("threshold", 0, SEED);
let validator_id_res = <T as pallet_session::Config>::ValidatorId::try_from(caller.clone()).or(Err(Error::<T>::InvalidValidatorId)).unwrap();

let signers = vec![validator_id_res.clone(); c as usize];
Signers::<T>::put(signers.clone());
NextSigners::<T>::put(NextSignerInfo {
next_signers: signers,
confirmations: vec![],
});

prep_bond_and_validate::<T>(true, caller.clone(), bonder.clone(), threshold, NULL_ARR);
let bond = <T as pallet_staking::Config>::Currency::minimum_balance() * 10u32.into();
Expand Down
97 changes: 89 additions & 8 deletions pallets/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ pub mod pallet {
NotNextSigner,
ReshareNotInProgress,
AlreadyConfirmed,
NoUnbondingWhenSigner,
NoUnbondingWhenNextSigner,
}

#[pallet::event]
Expand Down Expand Up @@ -402,9 +404,44 @@ pub mod pallet {
Ok(())
}

/// Wraps's substrate withdraw unbonded but clears extra state if fully unbonded
/// Wraps's substrate unbond but checks to make sure targeted validator is not a signer or next signer
JesseAbram marked this conversation as resolved.
Show resolved Hide resolved
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::withdraw_unbonded())]
#[pallet::weight(<T as Config>::WeightInfo::unbond(MAX_SIGNERS as u32))]
pub fn unbond(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let controller = ensure_signed(origin.clone())?;
let ledger =
pallet_staking::Pallet::<T>::ledger(StakingAccount::Controller(controller.clone()))
.map_err(|_| Error::<T>::NoThresholdKey)?;

let signers_length = Self::ensure_not_signer_or_next_signer(&ledger.stash)?;

pallet_staking::Pallet::<T>::unbond(origin, value)?;

Ok(Some(<T as Config>::WeightInfo::unbond(signers_length)).into())
}

/// Wraps's substrate chill but checks to make targeted validator sure not signer or next signer
JesseAbram marked this conversation as resolved.
Show resolved Hide resolved
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::chill(MAX_SIGNERS as u32))]
pub fn chill(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call it fate, but I was listening to this while reviewing 😆

let controller = ensure_signed(origin.clone())?;
let ledger =
pallet_staking::Pallet::<T>::ledger(StakingAccount::Controller(controller.clone()))
.map_err(|_| Error::<T>::NoThresholdKey)?;
HCastano marked this conversation as resolved.
Show resolved Hide resolved

let signers_length = Self::ensure_not_signer_or_next_signer(&ledger.stash)?;

pallet_staking::Pallet::<T>::chill(origin)?;

Ok(Some(<T as Config>::WeightInfo::chill(signers_length)).into())
}

/// Wraps's substrate withdraw unbonded but clears extra state if fully unbonded
JesseAbram marked this conversation as resolved.
Show resolved Hide resolved
#[pallet::call_index(4)]
#[pallet::weight(<T as Config>::WeightInfo::withdraw_unbonded(MAX_SIGNERS as u32))]
pub fn withdraw_unbonded(
origin: OriginFor<T>,
num_slashing_spans: u32,
Expand All @@ -414,8 +451,11 @@ pub mod pallet {
pallet_staking::Pallet::<T>::ledger(StakingAccount::Controller(controller.clone()))
.map_err(|_| Error::<T>::NoThresholdKey)?;

let validator_id = <T as pallet_session::Config>::ValidatorId::try_from(ledger.stash)
.or(Err(Error::<T>::InvalidValidatorId))?;
let validator_id =
<T as pallet_session::Config>::ValidatorId::try_from(ledger.stash.clone())
.or(Err(Error::<T>::InvalidValidatorId))?;

let signers_length = Self::ensure_not_signer_or_next_signer(&ledger.stash)?;

pallet_staking::Pallet::<T>::withdraw_unbonded(origin, num_slashing_spans)?;
// TODO: do not allow unbonding of validator if not enough validators https://github.com/entropyxyz/entropy-core/issues/942
Expand All @@ -426,15 +466,15 @@ pub mod pallet {
IsValidatorSynced::<T>::remove(&validator_id);
Self::deposit_event(Event::NodeInfoRemoved(controller));
}
Ok(().into())
Ok(Some(<T as Config>::WeightInfo::withdraw_unbonded(signers_length)).into())
}

/// Wrap's Substrate's `staking_pallet::validate()` extrinsic, but enforces that
/// information about a validator's threshold server is provided.
///
/// Note that - just like the original `validate()` extrinsic - the effects of this are
/// only applied in the following era.
#[pallet::call_index(3)]
#[pallet::call_index(5)]
#[pallet::weight(<T as Config>::WeightInfo::validate())]
pub fn validate(
origin: OriginFor<T>,
Expand Down Expand Up @@ -473,7 +513,7 @@ pub mod pallet {

/// Let a validator declare if their kvdb is synced or not synced
/// `synced`: State of validator's kvdb
#[pallet::call_index(4)]
#[pallet::call_index(6)]
#[pallet::weight(<T as Config>::WeightInfo::declare_synced())]
pub fn declare_synced(origin: OriginFor<T>, synced: bool) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
Expand All @@ -483,7 +523,7 @@ pub mod pallet {
Ok(())
}

#[pallet::call_index(5)]
#[pallet::call_index(7)]
#[pallet::weight(({
<T as Config>::WeightInfo::confirm_key_reshare_confirmed(MAX_SIGNERS as u32)
.max(<T as Config>::WeightInfo::confirm_key_reshare_completed())
Expand Down Expand Up @@ -533,6 +573,47 @@ pub mod pallet {
Ok(ledger.stash)
}

/// Ensures that the current validator is not a signer or a next signer
pub fn ensure_not_signer_or_next_signer(
HCastano marked this conversation as resolved.
Show resolved Hide resolved
stash: &T::AccountId,
) -> Result<u32, DispatchError> {
let nominations = pallet_staking::Nominators::<T>::get(stash)
.map_or_else(Vec::new, |x| x.targets.into_inner());

let signers = Self::signers();

// Check if the validator_id or any nominated validator is in signers
let in_signers = |id: &T::AccountId| {
let validator_id = <T as pallet_session::Config>::ValidatorId::try_from(id.clone());
match validator_id {
Ok(v_id) => signers.contains(&v_id),
Err(_) => false,
}
};

ensure!(!in_signers(stash), Error::<T>::NoUnbondingWhenSigner);
ensure!(!nominations.iter().any(in_signers), Error::<T>::NoUnbondingWhenSigner);
HCastano marked this conversation as resolved.
Show resolved Hide resolved

if let Some(next_signers) = Self::next_signers() {
let next_signers_contains = |id: &T::AccountId| {
let validator_id =
<T as pallet_session::Config>::ValidatorId::try_from(id.clone());
match validator_id {
Ok(v_id) => next_signers.next_signers.contains(&v_id),
Err(_) => false,
}
};

ensure!(!next_signers_contains(stash), Error::<T>::NoUnbondingWhenNextSigner);
ensure!(
!nominations.iter().any(next_signers_contains),
Error::<T>::NoUnbondingWhenNextSigner
);
}

Ok(signers.len() as u32)
}

pub fn get_randomness() -> ChaCha20Rng {
let phrase = b"signer_rotation";
// TODO: Is randomness freshness an issue here
Expand Down
2 changes: 1 addition & 1 deletion pallets/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ impl pallet_parameters::Config for Test {
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap();
let pallet_balances = pallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)],
balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (7, 100), (8, 100), (101, 200)],
HCastano marked this conversation as resolved.
Show resolved Hide resolved
};
let pallet_staking_extension = pallet_staking_extension::GenesisConfig::<Test> {
// (ValidatorID, (AccountId, X25519PublicKey, TssServerURL))
Expand Down
Loading
Loading