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:
Luke Parker 2023-10-22 03:59:21 -04:00
parent a702d65c3d
commit d66a7ee43e
No known key found for this signature in database
10 changed files with 193 additions and 434 deletions

18
Cargo.lock generated
View file

@ -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",

View file

@ -46,8 +46,6 @@ members = [
"substrate/validator-sets/primitives",
"substrate/validator-sets/pallet",
"substrate/staking/pallet",
"substrate/signals/pallet",
"substrate/runtime",

View file

@ -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" },

View file

@ -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",

View file

@ -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,

View file

@ -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"]

View file

@ -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/>.

View file

@ -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::*;

View file

@ -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 = [

View file

@ -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::*;