mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Remove the staking pallet for validator-sets alone
The staking pallet is an indirection which offered no practical benefit yet increased the overhead of every call.
This commit is contained in:
@@ -16,9 +16,12 @@ pub mod pallet {
|
||||
pub use validator_sets_primitives as primitives;
|
||||
use primitives::*;
|
||||
|
||||
use coins_pallet::Pallet as Coins;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
frame_system::Config<AccountId = Public>
|
||||
+ coins_pallet::Config
|
||||
+ pallet_session::Config<ValidatorId = Public>
|
||||
+ TypeInfo
|
||||
{
|
||||
@@ -245,37 +248,6 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
fn is_bft(network: NetworkId) -> bool {
|
||||
let allocation_per_key_share = AllocationPerKeyShare::<T>::get(network).unwrap().0;
|
||||
|
||||
let mut validators_len = 0;
|
||||
let mut top = None;
|
||||
let mut key_shares = 0;
|
||||
for (_, amount) in SortedAllocationsIter::<T>::new(network) {
|
||||
validators_len += 1;
|
||||
|
||||
key_shares += amount.0 / allocation_per_key_share;
|
||||
if top.is_none() {
|
||||
top = Some(key_shares);
|
||||
}
|
||||
|
||||
if key_shares > u64::from(MAX_KEY_SHARES_PER_SET) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(top) = top else { return false };
|
||||
|
||||
// key_shares may be over MAX_KEY_SHARES_PER_SET, which will cause an off-chain reduction of
|
||||
// each validator's key shares until their sum is MAX_KEY_SHARES_PER_SET
|
||||
// post_amortization_key_shares_for_top_validator yields what the top validator's key shares
|
||||
// would be after such a reduction, letting us evaluate this correctly
|
||||
let top = post_amortization_key_shares_for_top_validator(validators_len, top, key_shares);
|
||||
(top * 3) < key_shares.min(MAX_KEY_SHARES_PER_SET.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pending deallocations, keyed by the Session they become unlocked on.
|
||||
#[pallet::storage]
|
||||
type PendingDeallocations<T: Config> =
|
||||
@@ -312,6 +284,11 @@ pub mod pallet {
|
||||
amount: Amount,
|
||||
delayed_until: Option<Session>,
|
||||
},
|
||||
DeallocationClaimed {
|
||||
validator: T::AccountId,
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
},
|
||||
SetRetired {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
@@ -383,6 +360,8 @@ pub mod pallet {
|
||||
DeallocationWouldRemoveParticipant,
|
||||
/// Deallocation would cause the validator set to no longer achieve fault tolerance.
|
||||
DeallocationWouldRemoveFaultTolerance,
|
||||
/// Deallocation to be claimed doesn't exist.
|
||||
NonExistentDeallocation,
|
||||
/// Validator Set already generated keys.
|
||||
AlreadyGeneratedKeys,
|
||||
/// An invalid MuSig signature was provided.
|
||||
@@ -426,78 +405,41 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn set_keys(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
key_pair: KeyPair,
|
||||
signature: Signature,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
// signature isn't checked as this is an unsigned transaction, and validate_unsigned
|
||||
// (called by pre_dispatch) checks it
|
||||
let _ = signature;
|
||||
|
||||
let session = Session(pallet_session::Pallet::<T>::current_index());
|
||||
|
||||
let set = ValidatorSet { session, network };
|
||||
|
||||
Keys::<T>::set(set, Some(key_pair.clone()));
|
||||
Self::deposit_event(Event::KeyGen { set, key_pair });
|
||||
|
||||
Ok(())
|
||||
fn account() -> T::AccountId {
|
||||
system_address(b"validator-sets").into()
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
fn is_bft(network: NetworkId) -> bool {
|
||||
let allocation_per_key_share = AllocationPerKeyShare::<T>::get(network).unwrap().0;
|
||||
|
||||
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
// Match to be exhaustive
|
||||
let (network, key_pair, signature) = match call {
|
||||
Call::set_keys { network, ref key_pair, ref signature } => (network, key_pair, signature),
|
||||
Call::__Ignore(_, _) => unreachable!(),
|
||||
};
|
||||
let mut validators_len = 0;
|
||||
let mut top = None;
|
||||
let mut key_shares = 0;
|
||||
for (_, amount) in SortedAllocationsIter::<T>::new(network) {
|
||||
validators_len += 1;
|
||||
|
||||
let session = Session(pallet_session::Pallet::<T>::current_index());
|
||||
key_shares += amount.0 / allocation_per_key_share;
|
||||
if top.is_none() {
|
||||
top = Some(key_shares);
|
||||
}
|
||||
|
||||
let set = ValidatorSet { session, network: *network };
|
||||
match Self::verify_signature(set, key_pair, signature) {
|
||||
Err(Error::AlreadyGeneratedKeys) => Err(InvalidTransaction::Stale)?,
|
||||
Err(Error::NonExistentValidatorSet) |
|
||||
Err(Error::InsufficientAllocation) |
|
||||
Err(Error::NotEnoughAllocated) |
|
||||
Err(Error::AllocationWouldRemoveFaultTolerance) |
|
||||
Err(Error::DeallocationWouldRemoveParticipant) |
|
||||
Err(Error::DeallocationWouldRemoveFaultTolerance) |
|
||||
Err(Error::NonExistentValidator) |
|
||||
Err(Error::BadSignature) => Err(InvalidTransaction::BadProof)?,
|
||||
Err(Error::__Ignore(_, _)) => unreachable!(),
|
||||
Ok(()) => (),
|
||||
if key_shares > u64::from(MAX_KEY_SHARES_PER_SET) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ValidTransaction::with_tag_prefix("validator-sets")
|
||||
.and_provides(set)
|
||||
// Set a 10 block longevity, though this should be included in the next block
|
||||
.longevity(10)
|
||||
.propagate(true)
|
||||
.build()
|
||||
let Some(top) = top else { return false };
|
||||
|
||||
// key_shares may be over MAX_KEY_SHARES_PER_SET, which will cause an off-chain reduction of
|
||||
// each validator's key shares until their sum is MAX_KEY_SHARES_PER_SET
|
||||
// post_amortization_key_shares_for_top_validator yields what the top validator's key shares
|
||||
// would be after such a reduction, letting us evaluate this correctly
|
||||
let top = post_amortization_key_shares_for_top_validator(validators_len, top, key_shares);
|
||||
(top * 3) < key_shares.min(MAX_KEY_SHARES_PER_SET.into())
|
||||
}
|
||||
|
||||
// Explicitly provide a pre-dispatch which calls validate_unsigned
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[frame_support::transactional]
|
||||
pub fn increase_allocation(
|
||||
fn increase_allocation(
|
||||
network: NetworkId,
|
||||
account: T::AccountId,
|
||||
amount: Amount,
|
||||
@@ -548,8 +490,7 @@ pub mod pallet {
|
||||
/// doesn't become used (preventing deallocation).
|
||||
///
|
||||
/// Returns if the amount is immediately eligible for deallocation.
|
||||
#[frame_support::transactional]
|
||||
pub fn decrease_allocation(
|
||||
fn decrease_allocation(
|
||||
network: NetworkId,
|
||||
account: T::AccountId,
|
||||
amount: Amount,
|
||||
@@ -609,7 +550,7 @@ pub mod pallet {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Set it to PendingDeallocations, letting the staking pallet release it on a future session
|
||||
// Set it to PendingDeallocations, letting it be released upon a future session
|
||||
// This unwrap should be fine as this account is active, meaning a session has occurred
|
||||
let mut to_unlock_on = Self::session(network).unwrap();
|
||||
if network == NetworkId::Serai {
|
||||
@@ -665,7 +606,7 @@ pub mod pallet {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn new_session() {
|
||||
fn new_session() {
|
||||
for network in serai_primitives::NETWORKS {
|
||||
// If this network hasn't started sessions yet, don't start one now
|
||||
let Some(current_session) = Self::session(network) else { continue };
|
||||
@@ -677,10 +618,6 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_validators(network: NetworkId) -> Vec<Public> {
|
||||
Self::participants(network).into()
|
||||
}
|
||||
|
||||
pub fn retire_set(set: ValidatorSet) {
|
||||
MuSigKeys::<T>::remove(set);
|
||||
Keys::<T>::remove(set);
|
||||
@@ -690,7 +627,7 @@ pub mod pallet {
|
||||
/// Take the amount deallocatable.
|
||||
///
|
||||
/// `session` refers to the Session the stake becomes deallocatable on.
|
||||
pub fn take_deallocatable_amount(
|
||||
fn take_deallocatable_amount(
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
key: Public,
|
||||
@@ -702,6 +639,154 @@ pub mod pallet {
|
||||
PendingDeallocations::<T>::take((network, session, key))
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn set_keys(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
key_pair: KeyPair,
|
||||
signature: Signature,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
// signature isn't checked as this is an unsigned transaction, and validate_unsigned
|
||||
// (called by pre_dispatch) checks it
|
||||
let _ = signature;
|
||||
|
||||
let session = Session(pallet_session::Pallet::<T>::current_index());
|
||||
|
||||
let set = ValidatorSet { session, network };
|
||||
|
||||
Keys::<T>::set(set, Some(key_pair.clone()));
|
||||
Self::deposit_event(Event::KeyGen { set, key_pair });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn allocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
|
||||
let validator = ensure_signed(origin)?;
|
||||
Coins::<T>::transfer_internal(
|
||||
validator,
|
||||
Self::account(),
|
||||
Balance { coin: Coin::Serai, amount },
|
||||
)?;
|
||||
Self::increase_allocation(network, validator, amount)
|
||||
}
|
||||
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn deallocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
|
||||
let account = ensure_signed(origin)?;
|
||||
|
||||
let can_immediately_deallocate = Self::decrease_allocation(network, account, amount)?;
|
||||
if can_immediately_deallocate {
|
||||
Coins::<T>::transfer_internal(
|
||||
Self::account(),
|
||||
account,
|
||||
Balance { coin: Coin::Serai, amount },
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn claim_deallocation(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
) -> DispatchResult {
|
||||
let account = ensure_signed(origin)?;
|
||||
let Some(amount) = Self::take_deallocatable_amount(network, session, account) else {
|
||||
Err(Error::<T>::NonExistentDeallocation)?
|
||||
};
|
||||
Coins::<T>::transfer_internal(
|
||||
Self::account(),
|
||||
account,
|
||||
Balance { coin: Coin::Serai, amount },
|
||||
)?;
|
||||
Self::deposit_event(Event::DeallocationClaimed {
|
||||
validator: account,
|
||||
network,
|
||||
session,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
|
||||
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
// Match to be exhaustive
|
||||
let (network, key_pair, signature) = match call {
|
||||
Call::set_keys { network, ref key_pair, ref signature } => (network, key_pair, signature),
|
||||
Call::allocate { .. } | Call::deallocate { .. } | Call::claim_deallocation { .. } => {
|
||||
Err(InvalidTransaction::Call)?
|
||||
}
|
||||
Call::__Ignore(_, _) => unreachable!(),
|
||||
};
|
||||
|
||||
let session = Session(pallet_session::Pallet::<T>::current_index());
|
||||
|
||||
let set = ValidatorSet { session, network: *network };
|
||||
match Self::verify_signature(set, key_pair, signature) {
|
||||
Err(Error::AlreadyGeneratedKeys) => Err(InvalidTransaction::Stale)?,
|
||||
Err(Error::NonExistentValidatorSet) |
|
||||
Err(Error::InsufficientAllocation) |
|
||||
Err(Error::NotEnoughAllocated) |
|
||||
Err(Error::AllocationWouldRemoveFaultTolerance) |
|
||||
Err(Error::DeallocationWouldRemoveParticipant) |
|
||||
Err(Error::DeallocationWouldRemoveFaultTolerance) |
|
||||
Err(Error::NonExistentDeallocation) |
|
||||
Err(Error::NonExistentValidator) |
|
||||
Err(Error::BadSignature) => Err(InvalidTransaction::BadProof)?,
|
||||
Err(Error::__Ignore(_, _)) => unreachable!(),
|
||||
Ok(()) => (),
|
||||
}
|
||||
|
||||
ValidTransaction::with_tag_prefix("validator-sets")
|
||||
.and_provides(set)
|
||||
// Set a 10 block longevity, though this should be included in the next block
|
||||
.longevity(10)
|
||||
.propagate(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Explicitly provide a pre-dispatch which calls validate_unsigned
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
// Call order is end_session(i - 1) -> start_session(i) -> new_session(i + 1)
|
||||
// new_session(i + 1) is called immediately after start_session(i)
|
||||
// then we wait until the session ends then get a call to end_session(i) and so on.
|
||||
impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
|
||||
fn new_session(_new_index: u32) -> Option<Vec<T::ValidatorId>> {
|
||||
Self::new_session();
|
||||
// TODO: Where do we return their stake?
|
||||
Some(Self::participants(NetworkId::Serai).into())
|
||||
}
|
||||
|
||||
fn new_session_genesis(_: u32) -> Option<Vec<T::ValidatorId>> {
|
||||
// TODO: Because we don't call new_session here, we don't emit NewSet { Serai, session: 1 }
|
||||
Some(Self::participants(NetworkId::Serai).into())
|
||||
}
|
||||
|
||||
fn end_session(end_index: u32) {
|
||||
Self::retire_set(ValidatorSet { network: NetworkId::Serai, session: Session(end_index) })
|
||||
}
|
||||
|
||||
fn start_session(_start_index: u32) {}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
Reference in New Issue
Block a user