mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-12 09:26:51 +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:
parent
a702d65c3d
commit
d66a7ee43e
10 changed files with 193 additions and 434 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -8567,7 +8567,6 @@ dependencies = [
|
|||
"serai-in-instructions-pallet",
|
||||
"serai-primitives",
|
||||
"serai-signals-pallet",
|
||||
"serai-staking-pallet",
|
||||
"serai-validator-sets-pallet",
|
||||
"sp-api",
|
||||
"sp-authority-discovery",
|
||||
|
@ -8599,22 +8598,6 @@ dependencies = [
|
|||
"sp-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serai-staking-pallet"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"pallet-session",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-coins-pallet",
|
||||
"serai-primitives",
|
||||
"serai-validator-sets-pallet",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serai-validator-sets-pallet"
|
||||
version = "0.1.0"
|
||||
|
@ -8624,6 +8607,7 @@ dependencies = [
|
|||
"pallet-session",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-coins-pallet",
|
||||
"serai-primitives",
|
||||
"serai-validator-sets-primitives",
|
||||
"sp-application-crypto",
|
||||
|
|
|
@ -46,8 +46,6 @@ members = [
|
|||
"substrate/validator-sets/primitives",
|
||||
"substrate/validator-sets/pallet",
|
||||
|
||||
"substrate/staking/pallet",
|
||||
|
||||
"substrate/signals/pallet",
|
||||
|
||||
"substrate/runtime",
|
||||
|
|
|
@ -60,8 +60,6 @@ exceptions = [
|
|||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-staking-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-signals-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-runtime" },
|
||||
|
|
|
@ -48,7 +48,6 @@ pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", d
|
|||
coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", default-features = false }
|
||||
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
|
||||
staking-pallet = { package = "serai-staking-pallet", path = "../staking/pallet", default-features = false }
|
||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
|
||||
|
@ -103,7 +102,6 @@ std = [
|
|||
"coins-pallet/std",
|
||||
|
||||
"validator-sets-pallet/std",
|
||||
"staking-pallet/std",
|
||||
"pallet-session/std",
|
||||
|
||||
"in-instructions-pallet/std",
|
||||
|
|
|
@ -18,7 +18,6 @@ pub use pallet_transaction_payment as transaction_payment;
|
|||
|
||||
pub use coins_pallet as coins;
|
||||
|
||||
pub use staking_pallet as staking;
|
||||
pub use validator_sets_pallet as validator_sets;
|
||||
pub use pallet_session as session;
|
||||
|
||||
|
@ -169,7 +168,6 @@ impl Contains<RuntimeCall> for CallFilter {
|
|||
// All of these pallets are our own, and all of their written calls are intended to be called
|
||||
RuntimeCall::Coins(call) => !matches!(call, coins::Call::__Ignore(_, _)),
|
||||
RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)),
|
||||
RuntimeCall::Staking(call) => !matches!(call, staking::Call::__Ignore(_, _)),
|
||||
RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)),
|
||||
RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)),
|
||||
|
||||
|
@ -248,9 +246,6 @@ impl coins::Config for Runtime {
|
|||
impl validator_sets::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
impl staking::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
pub struct IdentityValidatorIdOf;
|
||||
impl Convert<PublicKey, Option<PublicKey>> for IdentityValidatorIdOf {
|
||||
|
@ -265,7 +260,7 @@ impl session::Config for Runtime {
|
|||
type ValidatorIdOf = IdentityValidatorIdOf;
|
||||
type ShouldEndSession = Babe;
|
||||
type NextSessionRotation = Babe;
|
||||
type SessionManager = Staking;
|
||||
type SessionManager = ValidatorSets;
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = SessionKeys;
|
||||
type WeightInfo = session::weights::SubstrateWeight<Runtime>;
|
||||
|
@ -349,7 +344,6 @@ construct_runtime!(
|
|||
Coins: coins,
|
||||
|
||||
ValidatorSets: validator_sets,
|
||||
Staking: staking,
|
||||
Session: session,
|
||||
|
||||
InInstructions: in_instructions,
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
[package]
|
||||
name = "serai-staking-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Staking pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/staking/pallet"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||
|
||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
|
||||
"validator-sets-pallet/std",
|
||||
|
||||
"pallet-session/std",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"frame-system/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
]
|
||||
|
||||
default = ["std"]
|
|
@ -1,15 +0,0 @@
|
|||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2022-2023 Luke Parker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License Version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -1,233 +0,0 @@
|
|||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use sp_runtime::traits::TrailingZeroInput;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use serai_primitives::*;
|
||||
|
||||
use coins_pallet::{Config as CoinsConfig, Pallet as Coins};
|
||||
|
||||
use validator_sets_pallet::{
|
||||
primitives::{Session, ValidatorSet},
|
||||
Config as VsConfig, Pallet as VsPallet,
|
||||
};
|
||||
use pallet_session::{Config as SessionConfig, SessionManager};
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
StakeUnavilable,
|
||||
NoDeallocation,
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
Staked {
|
||||
validator: T::AccountId,
|
||||
amount: Amount,
|
||||
},
|
||||
Unstaked {
|
||||
validator: T::AccountId,
|
||||
amount: Amount,
|
||||
},
|
||||
ImmediateDeallocation {
|
||||
validator: T::AccountId,
|
||||
network: NetworkId,
|
||||
amount: Amount,
|
||||
},
|
||||
DeallocationClaimed {
|
||||
validator: T::AccountId,
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
amount: Amount,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
frame_system::Config + CoinsConfig + VsConfig + SessionConfig<ValidatorId = PublicKey>
|
||||
{
|
||||
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
/// The amount of funds this account has staked.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn staked)]
|
||||
pub type Staked<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
|
||||
|
||||
/// The amount of stake this account has allocated to validator sets.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn allocated)]
|
||||
pub type Allocated<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
fn account() -> T::AccountId {
|
||||
// Substrate has a pattern of using simply using 8-bytes (as a PalletId) directly as an
|
||||
// AccountId. This replicates its internals to remove the 8-byte limit
|
||||
T::AccountId::decode(&mut TrailingZeroInput::new(b"staking")).unwrap()
|
||||
}
|
||||
|
||||
fn add_stake(account: T::AccountId, amount: u64) {
|
||||
Staked::<T>::mutate(account, |staked| *staked += amount);
|
||||
Self::deposit_event(Event::Staked { validator: account, amount: Amount(amount) });
|
||||
}
|
||||
|
||||
fn remove_stake(account: T::AccountId, amount: u64) -> Result<(), Error<T>> {
|
||||
Staked::<T>::mutate(account, |staked| {
|
||||
let available = *staked - Self::allocated(account);
|
||||
if available < amount {
|
||||
Err(Error::<T>::StakeUnavilable)?;
|
||||
}
|
||||
*staked -= amount;
|
||||
Self::deposit_event(Event::Unstaked { validator: account, amount: Amount(amount) });
|
||||
Ok::<_, Error<T>>(())
|
||||
})
|
||||
}
|
||||
|
||||
fn allocate_internal(account: &T::AccountId, amount: u64) -> Result<(), Error<T>> {
|
||||
Allocated::<T>::try_mutate(account, |allocated| {
|
||||
let available = Self::staked(account) - *allocated;
|
||||
if available < amount {
|
||||
Err(Error::<T>::StakeUnavilable)?;
|
||||
}
|
||||
*allocated += amount;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn deallocate_internal(account: &T::AccountId, amount: u64) -> Result<(), Error<T>> {
|
||||
Allocated::<T>::try_mutate(account, |allocated| {
|
||||
if *allocated < amount {
|
||||
Err(Error::<T>::StakeUnavilable)?;
|
||||
}
|
||||
*allocated -= amount;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Stake funds from this account.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn stake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
||||
let signer = ensure_signed(origin)?;
|
||||
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
||||
Coins::<T>::transfer_internal(signer, Self::account(), balance)?;
|
||||
Self::add_stake(signer, amount);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unstake funds from this account. Only unallocated funds may be unstaked.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn unstake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
||||
let signer = ensure_signed(origin)?;
|
||||
Self::remove_stake(signer, amount)?;
|
||||
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
||||
Coins::<T>::transfer_internal(Self::account(), signer, balance)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allocate `amount` to a given validator set.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn allocate(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
#[pallet::compact] amount: u64,
|
||||
) -> DispatchResult {
|
||||
let account = ensure_signed(origin)?;
|
||||
|
||||
// add to amount allocated
|
||||
Self::allocate_internal(&account, amount)?;
|
||||
// This does not emit an event as the validator-sets pallet will
|
||||
|
||||
// increase allocation for participant in validator set
|
||||
VsPallet::<T>::increase_allocation(network, account, Amount(amount))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deallocate `amount` from a given validator set.
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn deallocate(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
#[pallet::compact] amount: u64,
|
||||
) -> DispatchResult {
|
||||
let account = ensure_signed(origin)?;
|
||||
|
||||
// decrease allocation in validator set
|
||||
let can_immediately_deallocate =
|
||||
VsPallet::<T>::decrease_allocation(network, account, Amount(amount))?;
|
||||
if can_immediately_deallocate {
|
||||
Self::deallocate_internal(&account, amount)?;
|
||||
Self::deposit_event(Event::ImmediateDeallocation {
|
||||
validator: account,
|
||||
network,
|
||||
amount: Amount(amount),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pallet::call_index(4)]
|
||||
#[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) = VsPallet::<T>::take_deallocatable_amount(network, session, account) else {
|
||||
Err(Error::<T>::NoDeallocation)?
|
||||
};
|
||||
Self::deallocate_internal(&account, amount.0)?;
|
||||
Self::deposit_event(Event::DeallocationClaimed {
|
||||
validator: account,
|
||||
network,
|
||||
session,
|
||||
amount,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// 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> SessionManager<T::ValidatorId> for Pallet<T> {
|
||||
fn new_session(_new_index: u32) -> Option<Vec<T::ValidatorId>> {
|
||||
VsPallet::<T>::new_session();
|
||||
// TODO: Where do we return their stake?
|
||||
Some(VsPallet::<T>::select_validators(NetworkId::Serai))
|
||||
}
|
||||
|
||||
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(VsPallet::<T>::select_validators(NetworkId::Serai))
|
||||
}
|
||||
|
||||
fn end_session(end_index: u32) {
|
||||
VsPallet::<T>::retire_set(ValidatorSet {
|
||||
network: NetworkId::Serai,
|
||||
session: Session(end_index),
|
||||
})
|
||||
}
|
||||
|
||||
fn start_session(_start_index: u32) {}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
|
@ -29,6 +29,8 @@ pallet-session = { git = "https://github.com/serai-dex/substrate", default-featu
|
|||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../primitives", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
|
@ -47,6 +49,8 @@ std = [
|
|||
|
||||
"serai-primitives/std",
|
||||
"validator-sets-primitives/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in a new issue