mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Add deallocate function to validator-sets session abstraction
This commit is contained in:
@@ -3,21 +3,13 @@ use borsh::{BorshSerialize, BorshDeserialize};
|
|||||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||||
|
|
||||||
use serai_primitives::{
|
use serai_primitives::{
|
||||||
crypto::{ExternalKey, KeyPair, Signature},
|
crypto::{ExternalKey, EmbeddedEllipticCurveKeys, KeyPair, Signature},
|
||||||
address::SeraiAddress,
|
address::SeraiAddress,
|
||||||
balance::Amount,
|
balance::Amount,
|
||||||
network_id::*,
|
network_id::*,
|
||||||
validator_sets::*,
|
validator_sets::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Key(s) on embedded elliptic curve(s).
|
|
||||||
///
|
|
||||||
/// This may be a single key if the external network uses the same embedded elliptic curve as
|
|
||||||
/// used for the key to oraclize onto Serai. Else, it'll be a key on the embedded elliptic curve
|
|
||||||
/// used for the key to oraclize onto Serai concatenated with the key on the embedded elliptic
|
|
||||||
/// curve used for the external network.
|
|
||||||
pub type EmbeddedEllipticCurveKeys = BoundedVec<u8, ConstU32<{ 2 * ExternalKey::MAX_LEN }>>;
|
|
||||||
|
|
||||||
/// A call to the validator sets.
|
/// A call to the validator sets.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Call {
|
pub enum Call {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ use sp_core::{ConstU32, bounded::BoundedVec};
|
|||||||
|
|
||||||
/// A Ristretto public key.
|
/// A Ristretto public key.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "non_canonical_scale_derivations",
|
||||||
|
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
|
||||||
|
)]
|
||||||
pub struct Public(pub [u8; 32]);
|
pub struct Public(pub [u8; 32]);
|
||||||
impl From<sp_core::sr25519::Public> for Public {
|
impl From<sp_core::sr25519::Public> for Public {
|
||||||
fn from(public: sp_core::sr25519::Public) -> Self {
|
fn from(public: sp_core::sr25519::Public) -> Self {
|
||||||
@@ -19,6 +23,10 @@ impl From<Public> for sp_core::sr25519::Public {
|
|||||||
|
|
||||||
/// A sr25519 signature.
|
/// A sr25519 signature.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "non_canonical_scale_derivations",
|
||||||
|
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
|
||||||
|
)]
|
||||||
pub struct Signature(pub [u8; 64]);
|
pub struct Signature(pub [u8; 64]);
|
||||||
impl From<sp_core::sr25519::Signature> for Signature {
|
impl From<sp_core::sr25519::Signature> for Signature {
|
||||||
fn from(signature: sp_core::sr25519::Signature) -> Self {
|
fn from(signature: sp_core::sr25519::Signature) -> Self {
|
||||||
@@ -33,6 +41,10 @@ impl From<Signature> for sp_core::sr25519::Signature {
|
|||||||
|
|
||||||
/// A key for an external network.
|
/// A key for an external network.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "non_canonical_scale_derivations",
|
||||||
|
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
|
||||||
|
)]
|
||||||
pub struct ExternalKey(
|
pub struct ExternalKey(
|
||||||
#[borsh(
|
#[borsh(
|
||||||
serialize_with = "crate::borsh_serialize_bounded_vec",
|
serialize_with = "crate::borsh_serialize_bounded_vec",
|
||||||
@@ -58,9 +70,21 @@ impl ExternalKey {
|
|||||||
pub const MAX_LEN: u32 = 96;
|
pub const MAX_LEN: u32 = 96;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Key(s) on embedded elliptic curve(s).
|
||||||
|
///
|
||||||
|
/// This may be a single key if the external network uses the same embedded elliptic curve as
|
||||||
|
/// used for the key to oraclize onto Serai. Else, it'll be a key on the embedded elliptic curve
|
||||||
|
/// used for the key to oraclize onto Serai concatenated with the key on the embedded elliptic
|
||||||
|
/// curve used for the external network.
|
||||||
|
pub type EmbeddedEllipticCurveKeys = BoundedVec<u8, ConstU32<{ 2 * ExternalKey::MAX_LEN }>>;
|
||||||
|
|
||||||
/// The key pair for a validator set.
|
/// The key pair for a validator set.
|
||||||
///
|
///
|
||||||
/// This is their Ristretto key, used for publishing data onto Serai, and their key on the external
|
/// This is their Ristretto key, used for publishing data onto Serai, and their key on the external
|
||||||
/// network.
|
/// network.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "non_canonical_scale_derivations",
|
||||||
|
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
|
||||||
|
)]
|
||||||
pub struct KeyPair(pub Public, pub ExternalKey);
|
pub struct KeyPair(pub Public, pub ExternalKey);
|
||||||
|
|||||||
@@ -84,3 +84,15 @@ impl From<sp_core::H256> for BlockHash {
|
|||||||
// These share encodings as 32-byte arrays
|
// These share encodings as 32-byte arrays
|
||||||
impl scale::EncodeLike<sp_core::H256> for BlockHash {}
|
impl scale::EncodeLike<sp_core::H256> for BlockHash {}
|
||||||
impl scale::EncodeLike<sp_core::H256> for &BlockHash {}
|
impl scale::EncodeLike<sp_core::H256> for &BlockHash {}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::{BlockNumber, BlockHash};
|
||||||
|
pub use crate::constants::*;
|
||||||
|
pub use crate::address::*;
|
||||||
|
pub use crate::coin::*;
|
||||||
|
pub use crate::balance::*;
|
||||||
|
pub use crate::network_id::*;
|
||||||
|
pub use crate::validator_sets::*;
|
||||||
|
pub use crate::instructions::*;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ fn downtime_per_slash_point(validators: NonZero<u16>) -> Duration {
|
|||||||
|
|
||||||
/// A slash for a validator.
|
/// A slash for a validator.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "non_canonical_scale_derivations",
|
||||||
|
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
|
||||||
|
)]
|
||||||
pub enum Slash {
|
pub enum Slash {
|
||||||
/// The slash points accumulated by this validator.
|
/// The slash points accumulated by this validator.
|
||||||
///
|
///
|
||||||
@@ -198,6 +202,10 @@ impl Slash {
|
|||||||
|
|
||||||
/// A report of all slashes incurred for a `ValidatorSet`.
|
/// A report of all slashes incurred for a `ValidatorSet`.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "non_canonical_scale_derivations",
|
||||||
|
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
|
||||||
|
)]
|
||||||
pub struct SlashReport(
|
pub struct SlashReport(
|
||||||
#[borsh(
|
#[borsh(
|
||||||
serialize_with = "crate::borsh_serialize_bounded_vec",
|
serialize_with = "crate::borsh_serialize_bounded_vec",
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use serai_primitives::{constants::MAX_KEY_SHARES_PER_SET, network_id::NetworkId,
|
|||||||
use frame_support::storage::{StorageMap, StoragePrefixedMap};
|
use frame_support::storage::{StorageMap, StoragePrefixedMap};
|
||||||
|
|
||||||
/// The key to use for the allocations map.
|
/// The key to use for the allocations map.
|
||||||
type AllocationsKey = (NetworkId, Public);
|
pub(crate) type AllocationsKey = (NetworkId, Public);
|
||||||
/// The key to use for the sorted allocations map.
|
/// The key to use for the sorted allocations map.
|
||||||
type SortedAllocationsKey = (NetworkId, [u8; 8], [u8; 16], Public);
|
pub(crate) type SortedAllocationsKey = (NetworkId, [u8; 8], [u8; 16], Public);
|
||||||
|
|
||||||
/// The storage underlying `Allocations`.
|
/// The storage underlying `Allocations`.
|
||||||
///
|
///
|
||||||
@@ -150,11 +150,8 @@ impl<Storage: AllocationsStorage> Allocations for Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn expected_key_shares(network: NetworkId, allocation_per_key_share: Amount) -> u64 {
|
fn expected_key_shares(network: NetworkId, allocation_per_key_share: Amount) -> u64 {
|
||||||
let mut validators_len = 0;
|
|
||||||
let mut total_key_shares = 0;
|
let mut total_key_shares = 0;
|
||||||
for (_, amount) in Self::iter_allocations(network, allocation_per_key_share) {
|
for (_, amount) in Self::iter_allocations(network, allocation_per_key_share) {
|
||||||
validators_len += 1;
|
|
||||||
|
|
||||||
let key_shares = amount.0 / allocation_per_key_share.0;
|
let key_shares = amount.0 / allocation_per_key_share.0;
|
||||||
total_key_shares += key_shares;
|
total_key_shares += key_shares;
|
||||||
|
|
||||||
|
|||||||
@@ -7,21 +7,21 @@ use serai_primitives::{
|
|||||||
validator_sets::{Session, ValidatorSet, amortize_excess_key_shares},
|
validator_sets::{Session, ValidatorSet, amortize_excess_key_shares},
|
||||||
};
|
};
|
||||||
|
|
||||||
use frame_support::storage::{StorageValue, StorageMap, StoragePrefixedMap};
|
use frame_support::storage::{StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap};
|
||||||
|
|
||||||
use crate::allocations::*;
|
use crate::allocations::*;
|
||||||
|
|
||||||
/// The list of genesis validators.
|
/// The list of genesis validators.
|
||||||
type GenesisValidators = BoundedVec<Public, ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 }>>;
|
pub(crate) type GenesisValidators = BoundedVec<Public, ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 }>>;
|
||||||
|
|
||||||
/// The key for the SelectedValidators map.
|
/// The key for the SelectedValidators map.
|
||||||
type SelectedValidatorsKey = (ValidatorSet, [u8; 16], Public);
|
pub(crate) type SelectedValidatorsKey = (ValidatorSet, [u8; 16], Public);
|
||||||
|
|
||||||
pub(crate) trait SessionsStorage: AllocationsStorage {
|
pub(crate) trait SessionsStorage: AllocationsStorage {
|
||||||
/// The genesis validators
|
/// The genesis validators
|
||||||
///
|
///
|
||||||
/// The usage of is shared with the rest of the pallet. `Sessions` only reads it.
|
/// The usage of is shared with the rest of the pallet. `Sessions` only reads it.
|
||||||
type GenesisValidators: StorageValue<GenesisValidators, Query = GenesisValidators>;
|
type GenesisValidators: StorageValue<GenesisValidators, Query = Option<GenesisValidators>>;
|
||||||
|
|
||||||
/// The allocation required for a key share.
|
/// The allocation required for a key share.
|
||||||
///
|
///
|
||||||
@@ -44,12 +44,17 @@ pub(crate) trait SessionsStorage: AllocationsStorage {
|
|||||||
///
|
///
|
||||||
/// This is opaque and to be exclusively read/write by `Sessions`.
|
/// This is opaque and to be exclusively read/write by `Sessions`.
|
||||||
// The value is how many key shares the validator has.
|
// The value is how many key shares the validator has.
|
||||||
type SelectedValidators: StorageMap<SelectedValidatorsKey, u64> + StoragePrefixedMap<()>;
|
type SelectedValidators: StorageMap<SelectedValidatorsKey, u64> + StoragePrefixedMap<u64>;
|
||||||
|
|
||||||
/// The total allocated stake for a network.
|
/// The total allocated stake for a network.
|
||||||
///
|
///
|
||||||
/// This is opaque and to be exclusively read/write by `Sessions`.
|
/// This is opaque and to be exclusively read/write by `Sessions`.
|
||||||
type TotalAllocatedStake: StorageMap<NetworkId, Amount, Query = Option<Amount>>;
|
type TotalAllocatedStake: StorageMap<NetworkId, Amount, Query = Option<Amount>>;
|
||||||
|
|
||||||
|
/// The delayed deallocations.
|
||||||
|
///
|
||||||
|
/// This is opaque and to be exclusively read/write by `Sessions`.
|
||||||
|
type DelayedDeallocations: StorageDoubleMap<Public, Session, Amount, Query = Option<Amount>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The storage key for the SelectedValidators map.
|
/// The storage key for the SelectedValidators map.
|
||||||
@@ -58,7 +63,7 @@ fn selected_validators_key(set: ValidatorSet, key: Public) -> SelectedValidators
|
|||||||
(set, hash, key)
|
(set, hash, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_validators<Storage: StorageMap<SelectedValidatorsKey, u64> + StoragePrefixedMap<()>>(
|
fn selected_validators<Storage: StoragePrefixedMap<u64>>(
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
) -> impl Iterator<Item = (Public, u64)> {
|
) -> impl Iterator<Item = (Public, u64)> {
|
||||||
let mut prefix = Storage::final_prefix().to_vec();
|
let mut prefix = Storage::final_prefix().to_vec();
|
||||||
@@ -77,11 +82,7 @@ fn selected_validators<Storage: StorageMap<SelectedValidatorsKey, u64> + Storage
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_selected_validators<
|
fn clear_selected_validators<Storage: StoragePrefixedMap<u64>>(set: ValidatorSet) {
|
||||||
Storage: StorageMap<SelectedValidatorsKey, u64> + StoragePrefixedMap<()>,
|
|
||||||
>(
|
|
||||||
set: ValidatorSet,
|
|
||||||
) {
|
|
||||||
let mut prefix = Storage::final_prefix().to_vec();
|
let mut prefix = Storage::final_prefix().to_vec();
|
||||||
prefix.extend(&set.encode());
|
prefix.extend(&set.encode());
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
@@ -96,6 +97,17 @@ pub(crate) enum AllocationError {
|
|||||||
IntroducesSinglePointOfFailure,
|
IntroducesSinglePointOfFailure,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) enum DeallocationTimeline {
|
||||||
|
Immediate,
|
||||||
|
Delayed { unlocks_at: Session },
|
||||||
|
}
|
||||||
|
pub(crate) enum DeallocationError {
|
||||||
|
NoAllocationPerKeyShareSet,
|
||||||
|
NotEnoughAllocated,
|
||||||
|
RemainingAllocationLessThanKeyShare,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) trait Sessions {
|
pub(crate) trait Sessions {
|
||||||
/// Attempt to spawn a new session for the specified network.
|
/// Attempt to spawn a new session for the specified network.
|
||||||
///
|
///
|
||||||
@@ -115,11 +127,6 @@ pub(crate) trait Sessions {
|
|||||||
/// latest-decided session.
|
/// latest-decided session.
|
||||||
fn accept_handover(network: NetworkId);
|
fn accept_handover(network: NetworkId);
|
||||||
|
|
||||||
/// Retire a validator set.
|
|
||||||
///
|
|
||||||
/// This MUST be called only for sessions which are no longer current.
|
|
||||||
fn retire(set: ValidatorSet);
|
|
||||||
|
|
||||||
/// Increase a validator's allocation.
|
/// Increase a validator's allocation.
|
||||||
///
|
///
|
||||||
/// This does not perform any transfers of any coins/tokens. It solely performs the book-keeping
|
/// This does not perform any transfers of any coins/tokens. It solely performs the book-keeping
|
||||||
@@ -129,6 +136,16 @@ pub(crate) trait Sessions {
|
|||||||
validator: Public,
|
validator: Public,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
) -> Result<(), AllocationError>;
|
) -> Result<(), AllocationError>;
|
||||||
|
|
||||||
|
/// Decrease a validator's allocation.
|
||||||
|
///
|
||||||
|
/// This does not perform any transfers of any coins/tokens. It solely performs the book-keeping
|
||||||
|
/// of it.
|
||||||
|
fn decrease_allocation(
|
||||||
|
network: NetworkId,
|
||||||
|
validator: Public,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<DeallocationTimeline, DeallocationError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Storage: SessionsStorage> Sessions for Storage {
|
impl<Storage: SessionsStorage> Sessions for Storage {
|
||||||
@@ -176,6 +193,7 @@ impl<Storage: SessionsStorage> Sessions for Storage {
|
|||||||
|
|
||||||
if include_genesis_validators {
|
if include_genesis_validators {
|
||||||
let mut genesis_validators = Storage::GenesisValidators::get()
|
let mut genesis_validators = Storage::GenesisValidators::get()
|
||||||
|
.expect("genesis validators wasn't set")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|validator| (validator, 1))
|
.map(|validator| (validator, 1))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -232,16 +250,14 @@ impl<Storage: SessionsStorage> Sessions for Storage {
|
|||||||
}
|
}
|
||||||
// Update the total allocated stake variable to the current session
|
// Update the total allocated stake variable to the current session
|
||||||
Storage::TotalAllocatedStake::set(network, Some(total_allocated_stake));
|
Storage::TotalAllocatedStake::set(network, Some(total_allocated_stake));
|
||||||
}
|
|
||||||
|
|
||||||
fn retire(set: ValidatorSet) {
|
// Clean-up the historic set's storage, if one exists
|
||||||
assert!(
|
if let Some(historic_session) = current.0.checked_sub(2).map(Session) {
|
||||||
Some(set.session).map(|session| session.0) <
|
clear_selected_validators::<Storage::SelectedValidators>(ValidatorSet {
|
||||||
Storage::CurrentSession::get(set.network).map(|session| session.0),
|
network,
|
||||||
"retiring a set which is active/upcoming"
|
session: historic_session,
|
||||||
);
|
});
|
||||||
// Clean-up this set's storage
|
}
|
||||||
clear_selected_validators::<Storage::SelectedValidators>(set);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn increase_allocation(
|
fn increase_allocation(
|
||||||
@@ -310,4 +326,86 @@ impl<Storage: SessionsStorage> Sessions for Storage {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decrease_allocation(
|
||||||
|
network: NetworkId,
|
||||||
|
validator: Public,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<DeallocationTimeline, DeallocationError> {
|
||||||
|
/*
|
||||||
|
Decrease the allocation.
|
||||||
|
|
||||||
|
This doesn't affect the key shares, as that's immutable after creation, and doesn't affect
|
||||||
|
affect the `TotalAllocatedStake` as the validator either isn't current or the deallocation
|
||||||
|
will be queued *but is still considered allocated for this session*.
|
||||||
|
|
||||||
|
When the next set is selected, and becomes current, `TotalAllocatedStake` will be updated
|
||||||
|
per the allocations as-is.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
let Some(allocation_per_key_share) = Storage::AllocationPerKeyShare::get(network) else {
|
||||||
|
Err(DeallocationError::NoAllocationPerKeyShareSet)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let existing_allocation = Self::get_allocation(network, validator).unwrap_or(Amount(0));
|
||||||
|
let new_allocation =
|
||||||
|
(existing_allocation - amount).ok_or(DeallocationError::NotEnoughAllocated)?;
|
||||||
|
if (new_allocation != Amount(0)) && (new_allocation < allocation_per_key_share) {
|
||||||
|
Err(DeallocationError::RemainingAllocationLessThanKeyShare)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::set_allocation(network, validator, new_allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
For a validator present in set #n, they should only be able to deallocate once set #n+2 is
|
||||||
|
current. That means if set #n is malicious, and they rotate to a malicious set #n+1 with a
|
||||||
|
reduced stake requirement, further handovers can be stopped during set #n+1 (along with
|
||||||
|
stopping any pending deallocations).
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
let check_presence = |session| {
|
||||||
|
Storage::SelectedValidators::contains_key(selected_validators_key(
|
||||||
|
ValidatorSet { network, session },
|
||||||
|
validator,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
// Find the latest set this validator was present in, which isn't historic
|
||||||
|
let find_latest_session = || {
|
||||||
|
// Check the latest decided session
|
||||||
|
if let Some(latest) = Storage::LatestDecidedSession::get(network) {
|
||||||
|
if check_presence(latest) {
|
||||||
|
return Some(latest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was a latest decided session, but we weren't in it, check current
|
||||||
|
if let Some(current) = Storage::CurrentSession::get(network) {
|
||||||
|
if check_presence(current) {
|
||||||
|
return Some(current);
|
||||||
|
}
|
||||||
|
// Finally, check the prior session, as we shouldn't be able to deallocate from a
|
||||||
|
// session we were in solely because we weren't selected for further sessions
|
||||||
|
if let Some(prior) = current.0.checked_sub(1).map(Session) {
|
||||||
|
if check_presence(prior) {
|
||||||
|
return Some(prior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(present) = find_latest_session() {
|
||||||
|
// Because they were present in this session, determine the session this unlocks at
|
||||||
|
let unlocks_at = Session(present.0 + 2);
|
||||||
|
Storage::DelayedDeallocations::mutate(validator, unlocks_at, |delayed| {
|
||||||
|
*delayed = Some((delayed.unwrap_or(Amount(0)) + amount).unwrap());
|
||||||
|
});
|
||||||
|
return Ok(DeallocationTimeline::Delayed { unlocks_at });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because the network either doesn't have a current session, or this validator wasn't present,
|
||||||
|
// immediately handle the deallocation
|
||||||
|
Ok(DeallocationTimeline::Immediate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user