Remove support for stake redelegation (#7995)
* Remove support for stake redelegation * fixup
This commit is contained in:
parent
effe6e3ff3
commit
e6803daf10
|
@ -94,12 +94,13 @@ The Stakes and the RewardsPool are accounts that are owned by the same `Stake` p
|
||||||
|
|
||||||
### StakeInstruction::DelegateStake
|
### StakeInstruction::DelegateStake
|
||||||
|
|
||||||
The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. The transaction must be signed by the stake's `authorized_staker`. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated. Stakes may be re-delegated at any time, and updated stakes are reflected immediately, but only one re-delegation is permitted per epoch.
|
The Stake account is moved from Initialized to StakeState::Stake form, or from a deactivated (i.e. fully cooled-down) StakeState::Stake to activated StakeState::Stake. This is how stakers choose the vote account and validator node to which their stake account lamports are delegated. The transaction must be signed by the stake's `authorized_staker`.
|
||||||
|
|
||||||
* `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
|
* `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
|
||||||
* `account[1]` - R - The VoteState instance.
|
* `account[1]` - R - The VoteState instance.
|
||||||
* `account[2]` - R - sysvar::clock account, carries information about current Bank epoch
|
* `account[2]` - R - sysvar::clock account, carries information about current Bank epoch
|
||||||
* `account[3]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
|
* `account[3]` - R - sysvar::stakehistory account, carries information about stake history
|
||||||
|
* `account[4]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
|
||||||
|
|
||||||
### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\)
|
### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\)
|
||||||
|
|
||||||
|
|
|
@ -306,6 +306,7 @@ pub fn delegate_stake(
|
||||||
AccountMeta::new(*stake_pubkey, false),
|
AccountMeta::new(*stake_pubkey, false),
|
||||||
AccountMeta::new_readonly(*vote_pubkey, false),
|
AccountMeta::new_readonly(*vote_pubkey, false),
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
||||||
AccountMeta::new_readonly(crate::config::id(), false),
|
AccountMeta::new_readonly(crate::config::id(), false),
|
||||||
]
|
]
|
||||||
.with_signer(authorized_pubkey);
|
.with_signer(authorized_pubkey);
|
||||||
|
@ -396,9 +397,10 @@ pub fn process_instruction(
|
||||||
StakeInstruction::DelegateStake => {
|
StakeInstruction::DelegateStake => {
|
||||||
let vote = next_keyed_account(keyed_accounts)?;
|
let vote = next_keyed_account(keyed_accounts)?;
|
||||||
|
|
||||||
me.delegate_stake(
|
me.delegate(
|
||||||
&vote,
|
&vote,
|
||||||
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
|
&StakeHistory::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
&config::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
&config::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
&signers,
|
&signers,
|
||||||
)
|
)
|
||||||
|
@ -418,7 +420,7 @@ pub fn process_instruction(
|
||||||
&signers,
|
&signers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
StakeInstruction::Deactivate => me.deactivate_stake(
|
StakeInstruction::Deactivate => me.deactivate(
|
||||||
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
&signers,
|
&signers,
|
||||||
),
|
),
|
||||||
|
@ -658,6 +660,13 @@ mod tests {
|
||||||
false,
|
false,
|
||||||
&RefCell::new(sysvar::clock::Clock::default().create_account(1))
|
&RefCell::new(sysvar::clock::Clock::default().create_account(1))
|
||||||
),
|
),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::stake_history::id(),
|
||||||
|
false,
|
||||||
|
&RefCell::new(
|
||||||
|
sysvar::stake_history::StakeHistory::default().create_account(1)
|
||||||
|
)
|
||||||
|
),
|
||||||
KeyedAccount::new(
|
KeyedAccount::new(
|
||||||
&config::id(),
|
&config::id(),
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -8,14 +8,11 @@ use serde_derive::{Deserialize, Serialize};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::{Account, KeyedAccount},
|
account::{Account, KeyedAccount},
|
||||||
account_utils::{State, StateMut},
|
account_utils::{State, StateMut},
|
||||||
clock::{Clock, Epoch, Slot, UnixTimestamp},
|
clock::{Clock, Epoch, UnixTimestamp},
|
||||||
instruction::InstructionError,
|
instruction::InstructionError,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
sysvar::{
|
|
||||||
self,
|
|
||||||
stake_history::{StakeHistory, StakeHistoryEntry},
|
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use solana_vote_program::vote_state::VoteState;
|
use solana_vote_program::vote_state::VoteState;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
@ -154,7 +151,7 @@ impl Meta {
|
||||||
pub struct Delegation {
|
pub struct Delegation {
|
||||||
/// to whom the stake is delegated
|
/// to whom the stake is delegated
|
||||||
pub voter_pubkey: Pubkey,
|
pub voter_pubkey: Pubkey,
|
||||||
/// activated stake amount, set at delegate_stake() time
|
/// activated stake amount, set at delegate() time
|
||||||
pub stake: u64,
|
pub stake: u64,
|
||||||
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
|
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
|
||||||
pub activation_epoch: Epoch,
|
pub activation_epoch: Epoch,
|
||||||
|
@ -311,32 +308,11 @@ impl Delegation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||||
pub struct Stake {
|
pub struct Stake {
|
||||||
pub delegation: Delegation,
|
pub delegation: Delegation,
|
||||||
/// the epoch when voter_pubkey was most recently set
|
|
||||||
pub voter_pubkey_epoch: Epoch,
|
|
||||||
/// credits observed is credits from vote account state when delegated or redeemed
|
/// credits observed is credits from vote account state when delegated or redeemed
|
||||||
pub credits_observed: u64,
|
pub credits_observed: u64,
|
||||||
/// history of prior delegates and the epoch ranges for which
|
|
||||||
/// they were set, circular buffer
|
|
||||||
pub prior_delegates: [(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES],
|
|
||||||
/// next pointer
|
|
||||||
pub prior_delegates_idx: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exposed to a slashing condition
|
|
||||||
|
|
||||||
impl Default for Stake {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
delegation: Delegation::default(),
|
|
||||||
voter_pubkey_epoch: 0,
|
|
||||||
credits_observed: 0,
|
|
||||||
prior_delegates: <[(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES]>::default(),
|
|
||||||
prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Authorized {
|
impl Authorized {
|
||||||
|
@ -455,25 +431,19 @@ impl Stake {
|
||||||
voter_pubkey: &Pubkey,
|
voter_pubkey: &Pubkey,
|
||||||
vote_state: &VoteState,
|
vote_state: &VoteState,
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
|
stake_history: &StakeHistory,
|
||||||
|
config: &Config,
|
||||||
) -> Result<(), StakeError> {
|
) -> Result<(), StakeError> {
|
||||||
// only one re-delegation supported per epoch
|
// can't redelegate if stake is active. either the stake
|
||||||
if self.voter_pubkey_epoch == clock.epoch {
|
// is freshly activated or has fully de-activated. redelegation
|
||||||
|
// implies re-activation
|
||||||
|
if self.stake(clock.epoch, Some(stake_history)) != 0 {
|
||||||
return Err(StakeError::TooSoonToRedelegate);
|
return Err(StakeError::TooSoonToRedelegate);
|
||||||
}
|
}
|
||||||
|
self.delegation.activation_epoch = clock.epoch;
|
||||||
// remember prior delegate and when we switched, to support later slashing
|
self.delegation.deactivation_epoch = std::u64::MAX;
|
||||||
self.prior_delegates_idx += 1;
|
|
||||||
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
|
|
||||||
|
|
||||||
self.prior_delegates[self.prior_delegates_idx] = (
|
|
||||||
self.delegation.voter_pubkey,
|
|
||||||
self.voter_pubkey_epoch,
|
|
||||||
clock.epoch,
|
|
||||||
clock.slot,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.delegation.voter_pubkey = *voter_pubkey;
|
self.delegation.voter_pubkey = *voter_pubkey;
|
||||||
self.voter_pubkey_epoch = clock.epoch;
|
self.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
|
||||||
self.credits_observed = vote_state.credits();
|
self.credits_observed = vote_state.credits();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -507,9 +477,7 @@ impl Stake {
|
||||||
activation_epoch,
|
activation_epoch,
|
||||||
config.warmup_cooldown_rate,
|
config.warmup_cooldown_rate,
|
||||||
),
|
),
|
||||||
voter_pubkey_epoch: activation_epoch,
|
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
..Stake::default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,18 +505,15 @@ pub trait StakeAccount {
|
||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
) -> Result<(), InstructionError>;
|
) -> Result<(), InstructionError>;
|
||||||
fn delegate_stake(
|
fn delegate(
|
||||||
&self,
|
&self,
|
||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
clock: &sysvar::clock::Clock,
|
clock: &Clock,
|
||||||
|
stake_history: &StakeHistory,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
) -> Result<(), InstructionError>;
|
) -> Result<(), InstructionError>;
|
||||||
fn deactivate_stake(
|
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError>;
|
||||||
&self,
|
|
||||||
clock: &sysvar::clock::Clock,
|
|
||||||
signers: &HashSet<Pubkey>,
|
|
||||||
) -> Result<(), InstructionError>;
|
|
||||||
fn set_lockup(
|
fn set_lockup(
|
||||||
&self,
|
&self,
|
||||||
lockup: &Lockup,
|
lockup: &Lockup,
|
||||||
|
@ -564,8 +529,8 @@ pub trait StakeAccount {
|
||||||
&self,
|
&self,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
to: &KeyedAccount,
|
to: &KeyedAccount,
|
||||||
clock: &sysvar::clock::Clock,
|
clock: &Clock,
|
||||||
stake_history: &sysvar::stake_history::StakeHistory,
|
stake_history: &StakeHistory,
|
||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
) -> Result<(), InstructionError>;
|
) -> Result<(), InstructionError>;
|
||||||
}
|
}
|
||||||
|
@ -616,10 +581,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
_ => Err(InstructionError::InvalidAccountData),
|
_ => Err(InstructionError::InvalidAccountData),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn delegate_stake(
|
fn delegate(
|
||||||
&self,
|
&self,
|
||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
clock: &sysvar::clock::Clock,
|
clock: &Clock,
|
||||||
|
stake_history: &StakeHistory,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
|
@ -637,17 +603,19 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
}
|
}
|
||||||
StakeState::Stake(meta, mut stake) => {
|
StakeState::Stake(meta, mut stake) => {
|
||||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||||
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
|
stake.redelegate(
|
||||||
|
vote_account.unsigned_key(),
|
||||||
|
&vote_account.state()?,
|
||||||
|
clock,
|
||||||
|
stake_history,
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
self.set_state(&StakeState::Stake(meta, stake))
|
self.set_state(&StakeState::Stake(meta, stake))
|
||||||
}
|
}
|
||||||
_ => Err(InstructionError::InvalidAccountData),
|
_ => Err(InstructionError::InvalidAccountData),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn deactivate_stake(
|
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError> {
|
||||||
&self,
|
|
||||||
clock: &sysvar::clock::Clock,
|
|
||||||
signers: &HashSet<Pubkey>,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
if let StakeState::Stake(meta, mut stake) = self.state()? {
|
if let StakeState::Stake(meta, mut stake) = self.state()? {
|
||||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||||
stake.deactivate(clock.epoch)?;
|
stake.deactivate(clock.epoch)?;
|
||||||
|
@ -743,8 +711,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
&self,
|
&self,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
to: &KeyedAccount,
|
to: &KeyedAccount,
|
||||||
clock: &sysvar::clock::Clock,
|
clock: &Clock,
|
||||||
stake_history: &sysvar::stake_history::StakeHistory,
|
stake_history: &StakeHistory,
|
||||||
signers: &HashSet<Pubkey>,
|
signers: &HashSet<Pubkey>,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let (lockup, reserve, is_staked) = match self.state()? {
|
let (lockup, reserve, is_staked) = match self.state()? {
|
||||||
|
@ -1012,10 +980,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_delegate_stake() {
|
fn test_stake_delegate() {
|
||||||
let mut clock = sysvar::clock::Clock {
|
let mut clock = Clock {
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
..sysvar::clock::Clock::default()
|
..Clock::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let vote_pubkey = Pubkey::new_rand();
|
let vote_pubkey = Pubkey::new_rand();
|
||||||
|
@ -1052,25 +1020,12 @@ mod tests {
|
||||||
// unsigned keyed account
|
// unsigned keyed account
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
||||||
|
|
||||||
{
|
|
||||||
let stake_state: StakeState = stake_keyed_account.state().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
stake_state,
|
|
||||||
StakeState::Initialized(Meta {
|
|
||||||
authorized: Authorized {
|
|
||||||
staker: stake_pubkey,
|
|
||||||
withdrawer: stake_pubkey,
|
|
||||||
},
|
|
||||||
..Meta::default()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut signers = HashSet::default();
|
let mut signers = HashSet::default();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&vote_keyed_account,
|
&vote_keyed_account,
|
||||||
&clock,
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&signers,
|
&signers,
|
||||||
),
|
),
|
||||||
|
@ -1081,10 +1036,16 @@ mod tests {
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||||
signers.insert(stake_pubkey);
|
signers.insert(stake_pubkey);
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers,)
|
.delegate(
|
||||||
|
&vote_keyed_account,
|
||||||
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
|
&Config::default(),
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// verify that delegate_stake() looks right, compare against hand-rolled
|
// verify that delegate() looks right, compare against hand-rolled
|
||||||
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake,
|
stake,
|
||||||
|
@ -1096,79 +1057,74 @@ mod tests {
|
||||||
deactivation_epoch: std::u64::MAX,
|
deactivation_epoch: std::u64::MAX,
|
||||||
..Delegation::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
voter_pubkey_epoch: clock.epoch,
|
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// verify that delegate_stake can be called twice, 2nd is redelegate
|
|
||||||
|
clock.epoch += 1;
|
||||||
|
|
||||||
|
// verify that delegate fails if stake is still active
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&vote_keyed_account,
|
&vote_keyed_account,
|
||||||
&clock,
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&signers
|
&signers
|
||||||
),
|
),
|
||||||
Err(StakeError::TooSoonToRedelegate.into())
|
Err(StakeError::TooSoonToRedelegate.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// deactivate, so we can re-delegate
|
||||||
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
||||||
|
|
||||||
|
// without stake history, cool down is instantaneous
|
||||||
clock.epoch += 1;
|
clock.epoch += 1;
|
||||||
// verify that delegate_stake can be called twice, 2nd is redelegate
|
|
||||||
|
// verify that delegate can be called twice, 2nd is redelegate
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
|
.delegate(
|
||||||
|
&vote_keyed_account,
|
||||||
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
|
&Config::default(),
|
||||||
|
&signers
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// verify that non-stakes fail delegate_stake()
|
// verify that delegate() looks right, compare against hand-rolled
|
||||||
|
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
stake,
|
||||||
|
Stake {
|
||||||
|
delegation: Delegation {
|
||||||
|
voter_pubkey: vote_pubkey,
|
||||||
|
stake: stake_lamports,
|
||||||
|
activation_epoch: clock.epoch,
|
||||||
|
deactivation_epoch: std::u64::MAX,
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
|
credits_observed: vote_state.credits(),
|
||||||
|
..Stake::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// verify that non-stakes fail delegate()
|
||||||
let stake_state = StakeState::RewardsPool;
|
let stake_state = StakeState::RewardsPool;
|
||||||
|
|
||||||
stake_keyed_account.set_state(&stake_state).unwrap();
|
stake_keyed_account.set_state(&stake_state).unwrap();
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
|
.delegate(
|
||||||
|
&vote_keyed_account,
|
||||||
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
|
&Config::default(),
|
||||||
|
&signers
|
||||||
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_stake_redelegate() {
|
|
||||||
// what a freshly delegated stake looks like
|
|
||||||
let mut stake = Stake {
|
|
||||||
delegation: Delegation {
|
|
||||||
voter_pubkey: Pubkey::new_rand(),
|
|
||||||
..Delegation::default()
|
|
||||||
},
|
|
||||||
voter_pubkey_epoch: 0,
|
|
||||||
..Stake::default()
|
|
||||||
};
|
|
||||||
// verify that redelegation works when epoch is changing, that
|
|
||||||
// wraparound works, and that the stake is delegated
|
|
||||||
// to the most recent vote account
|
|
||||||
for epoch in 1..=MAX_PRIOR_DELEGATES + 2 {
|
|
||||||
let voter_pubkey = Pubkey::new_rand();
|
|
||||||
assert_eq!(
|
|
||||||
stake.redelegate(
|
|
||||||
&voter_pubkey,
|
|
||||||
&VoteState::default(),
|
|
||||||
&sysvar::clock::Clock {
|
|
||||||
epoch: epoch as u64,
|
|
||||||
..sysvar::clock::Clock::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
stake.redelegate(
|
|
||||||
&voter_pubkey,
|
|
||||||
&VoteState::default(),
|
|
||||||
&sysvar::clock::Clock {
|
|
||||||
epoch: epoch as u64,
|
|
||||||
..sysvar::clock::Clock::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Err(StakeError::TooSoonToRedelegate)
|
|
||||||
);
|
|
||||||
assert_eq!(stake.delegation.voter_pubkey, voter_pubkey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_stake_history_from_delegations(
|
fn create_stake_history_from_delegations(
|
||||||
bootstrap: Option<u64>,
|
bootstrap: Option<u64>,
|
||||||
epochs: std::ops::Range<Epoch>,
|
epochs: std::ops::Range<Epoch>,
|
||||||
|
@ -1546,7 +1502,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deactivate_stake() {
|
fn test_deactivate() {
|
||||||
let stake_pubkey = Pubkey::new_rand();
|
let stake_pubkey = Pubkey::new_rand();
|
||||||
let stake_lamports = 42;
|
let stake_lamports = 42;
|
||||||
let stake_account = Account::new_ref_data_with_space(
|
let stake_account = Account::new_ref_data_with_space(
|
||||||
|
@ -1557,16 +1513,16 @@ mod tests {
|
||||||
)
|
)
|
||||||
.expect("stake_account");
|
.expect("stake_account");
|
||||||
|
|
||||||
let clock = sysvar::clock::Clock {
|
let clock = Clock {
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
..sysvar::clock::Clock::default()
|
..Clock::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// signed keyed account but not staked yet
|
// signed keyed account but not staked yet
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||||
let signers = vec![stake_pubkey].into_iter().collect();
|
let signers = vec![stake_pubkey].into_iter().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.deactivate_stake(&clock, &signers),
|
stake_keyed_account.deactivate(&clock, &signers),
|
||||||
Err(InstructionError::InvalidAccountData)
|
Err(InstructionError::InvalidAccountData)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1581,9 +1537,10 @@ mod tests {
|
||||||
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
||||||
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&vote_keyed_account,
|
&vote_keyed_account,
|
||||||
&clock,
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&signers
|
&signers
|
||||||
),
|
),
|
||||||
|
@ -1593,20 +1550,17 @@ mod tests {
|
||||||
// no signers fails
|
// no signers fails
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.deactivate_stake(&clock, &HashSet::default()),
|
stake_keyed_account.deactivate(&clock, &HashSet::default()),
|
||||||
Err(InstructionError::MissingRequiredSignature)
|
Err(InstructionError::MissingRequiredSignature)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Deactivate after staking
|
// Deactivate after staking
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||||
assert_eq!(
|
assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(()));
|
||||||
stake_keyed_account.deactivate_stake(&clock, &signers),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
|
|
||||||
// verify that deactivate() only works once
|
// verify that deactivate() only works once
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.deactivate_stake(&clock, &signers),
|
stake_keyed_account.deactivate(&clock, &signers),
|
||||||
Err(StakeError::AlreadyDeactivated.into())
|
Err(StakeError::AlreadyDeactivated.into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1673,9 +1627,10 @@ mod tests {
|
||||||
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
||||||
|
|
||||||
stake_keyed_account
|
stake_keyed_account
|
||||||
.delegate_stake(
|
.delegate(
|
||||||
&vote_keyed_account,
|
&vote_keyed_account,
|
||||||
&Clock::default(),
|
&Clock::default(),
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&vec![stake_pubkey].into_iter().collect(),
|
&vec![stake_pubkey].into_iter().collect(),
|
||||||
)
|
)
|
||||||
|
@ -1717,7 +1672,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.expect("stake_account");
|
.expect("stake_account");
|
||||||
|
|
||||||
let mut clock = sysvar::clock::Clock::default();
|
let mut clock = Clock::default();
|
||||||
|
|
||||||
let to = Pubkey::new_rand();
|
let to = Pubkey::new_rand();
|
||||||
let to_account = Account::new_ref(1, 0, &system_program::id());
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
||||||
|
@ -1795,9 +1750,10 @@ mod tests {
|
||||||
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
||||||
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&vote_keyed_account,
|
&vote_keyed_account,
|
||||||
&clock,
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&signers,
|
&signers,
|
||||||
),
|
),
|
||||||
|
@ -1837,10 +1793,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// deactivate the stake before withdrawal
|
// deactivate the stake before withdrawal
|
||||||
assert_eq!(
|
assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(()));
|
||||||
stake_keyed_account.deactivate_stake(&clock, &signers),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
// simulate time passing
|
// simulate time passing
|
||||||
clock.epoch += 100;
|
clock.epoch += 100;
|
||||||
|
|
||||||
|
@ -1885,8 +1838,8 @@ mod tests {
|
||||||
)
|
)
|
||||||
.expect("stake_account");
|
.expect("stake_account");
|
||||||
|
|
||||||
let clock = sysvar::clock::Clock::default();
|
let clock = Clock::default();
|
||||||
let mut future = sysvar::clock::Clock::default();
|
let mut future = Clock::default();
|
||||||
future.epoch += 16;
|
future.epoch += 16;
|
||||||
|
|
||||||
let to = Pubkey::new_rand();
|
let to = Pubkey::new_rand();
|
||||||
|
@ -1907,9 +1860,10 @@ mod tests {
|
||||||
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
||||||
let signers = vec![stake_pubkey].into_iter().collect();
|
let signers = vec![stake_pubkey].into_iter().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&vote_keyed_account,
|
&vote_keyed_account,
|
||||||
&future,
|
&future,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&signers,
|
&signers,
|
||||||
),
|
),
|
||||||
|
@ -1960,7 +1914,7 @@ mod tests {
|
||||||
stake_keyed_account.withdraw(
|
stake_keyed_account.withdraw(
|
||||||
total_lamports,
|
total_lamports,
|
||||||
&to_keyed_account,
|
&to_keyed_account,
|
||||||
&sysvar::clock::Clock::default(),
|
&Clock::default(),
|
||||||
&StakeHistory::default(),
|
&StakeHistory::default(),
|
||||||
&signers,
|
&signers,
|
||||||
),
|
),
|
||||||
|
@ -1994,7 +1948,7 @@ mod tests {
|
||||||
|
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||||
|
|
||||||
let mut clock = sysvar::clock::Clock::default();
|
let mut clock = Clock::default();
|
||||||
|
|
||||||
let signers = vec![stake_pubkey].into_iter().collect();
|
let signers = vec![stake_pubkey].into_iter().collect();
|
||||||
|
|
||||||
|
@ -2202,7 +2156,7 @@ mod tests {
|
||||||
let to_account = Account::new_ref(1, 0, &system_program::id());
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
||||||
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
||||||
|
|
||||||
let clock = sysvar::clock::Clock::default();
|
let clock = Clock::default();
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||||
|
|
||||||
let stake_pubkey0 = Pubkey::new_rand();
|
let stake_pubkey0 = Pubkey::new_rand();
|
||||||
|
@ -2816,7 +2770,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.expect("stake_account");
|
.expect("stake_account");
|
||||||
|
|
||||||
let mut clock = Clock::default();
|
let clock = Clock::default();
|
||||||
|
|
||||||
let vote_pubkey = Pubkey::new_rand();
|
let vote_pubkey = Pubkey::new_rand();
|
||||||
let vote_account = RefCell::new(vote_state::create_account(
|
let vote_account = RefCell::new(vote_state::create_account(
|
||||||
|
@ -2830,9 +2784,18 @@ mod tests {
|
||||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||||
let signers = vec![stake_pubkey].into_iter().collect();
|
let signers = vec![stake_pubkey].into_iter().collect();
|
||||||
stake_keyed_account
|
stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
|
.delegate(
|
||||||
|
&vote_keyed_account,
|
||||||
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
|
&Config::default(),
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// deactivate, so we can re-delegate
|
||||||
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
||||||
|
|
||||||
let new_staker_pubkey = Pubkey::new_rand();
|
let new_staker_pubkey = Pubkey::new_rand();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.authorize(
|
stake_keyed_account.authorize(
|
||||||
|
@ -2864,13 +2827,12 @@ mod tests {
|
||||||
let new_vote_keyed_account = KeyedAccount::new(&new_voter_pubkey, false, &new_vote_account);
|
let new_vote_keyed_account = KeyedAccount::new(&new_voter_pubkey, false, &new_vote_account);
|
||||||
new_vote_keyed_account.set_state(&vote_state).unwrap();
|
new_vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
// time passes, so we can re-delegate
|
|
||||||
clock.epoch += 1;
|
|
||||||
// Random other account should fail
|
// Random other account should fail
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&new_vote_keyed_account,
|
&new_vote_keyed_account,
|
||||||
&clock,
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&other_signers,
|
&other_signers,
|
||||||
),
|
),
|
||||||
|
@ -2880,9 +2842,10 @@ mod tests {
|
||||||
let new_signers = vec![new_staker_pubkey].into_iter().collect();
|
let new_signers = vec![new_staker_pubkey].into_iter().collect();
|
||||||
// Authorized staker should succeed
|
// Authorized staker should succeed
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(
|
stake_keyed_account.delegate(
|
||||||
&new_vote_keyed_account,
|
&new_vote_keyed_account,
|
||||||
&clock,
|
&clock,
|
||||||
|
&StakeHistory::default(),
|
||||||
&Config::default(),
|
&Config::default(),
|
||||||
&new_signers
|
&new_signers
|
||||||
),
|
),
|
||||||
|
@ -2893,9 +2856,6 @@ mod tests {
|
||||||
assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey);
|
assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey);
|
||||||
|
|
||||||
// Test another staking action
|
// Test another staking action
|
||||||
assert_eq!(
|
assert_eq!(stake_keyed_account.deactivate(&clock, &new_signers), Ok(()));
|
||||||
stake_keyed_account.deactivate_stake(&clock, &new_signers),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub mod rpc_port;
|
||||||
pub mod short_vec;
|
pub mod short_vec;
|
||||||
pub mod slot_hashes;
|
pub mod slot_hashes;
|
||||||
pub mod slot_history;
|
pub mod slot_history;
|
||||||
|
pub mod stake_history;
|
||||||
pub mod system_instruction;
|
pub mod system_instruction;
|
||||||
pub mod system_program;
|
pub mod system_program;
|
||||||
pub mod sysvar;
|
pub mod sysvar;
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
//! named accounts for synthesized data accounts for bank state, etc.
|
||||||
|
//!
|
||||||
|
//! this account carries history about stake activations and de-activations
|
||||||
|
//!
|
||||||
|
pub use crate::clock::Epoch;
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
|
||||||
|
pub struct StakeHistoryEntry {
|
||||||
|
pub effective: u64, // effective stake at this epoch
|
||||||
|
pub activating: u64, // sum of portion of stakes not fully warmed up
|
||||||
|
pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
|
||||||
|
pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
|
||||||
|
|
||||||
|
impl StakeHistory {
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> {
|
||||||
|
self.binary_search_by(|probe| epoch.cmp(&probe.0))
|
||||||
|
.ok()
|
||||||
|
.map(|index| &self[index].1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
|
||||||
|
match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
|
||||||
|
Ok(index) => (self.0)[index] = (epoch, entry),
|
||||||
|
Err(index) => (self.0).insert(index, (epoch, entry)),
|
||||||
|
}
|
||||||
|
(self.0).truncate(MAX_ENTRIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for StakeHistory {
|
||||||
|
type Target = Vec<(Epoch, StakeHistoryEntry)>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stake_history() {
|
||||||
|
let mut stake_history = StakeHistory::default();
|
||||||
|
|
||||||
|
for i in 0..MAX_ENTRIES as u64 + 1 {
|
||||||
|
stake_history.add(
|
||||||
|
i,
|
||||||
|
StakeHistoryEntry {
|
||||||
|
activating: i,
|
||||||
|
..StakeHistoryEntry::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(stake_history.len(), MAX_ENTRIES);
|
||||||
|
assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1);
|
||||||
|
assert_eq!(stake_history.get(&0), None);
|
||||||
|
assert_eq!(
|
||||||
|
stake_history.get(&1),
|
||||||
|
Some(&StakeHistoryEntry {
|
||||||
|
activating: 1,
|
||||||
|
..StakeHistoryEntry::default()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,26 +2,12 @@
|
||||||
//!
|
//!
|
||||||
//! this account carries history about stake activations and de-activations
|
//! this account carries history about stake activations and de-activations
|
||||||
//!
|
//!
|
||||||
pub use crate::clock::Epoch;
|
pub use crate::stake_history::StakeHistory;
|
||||||
|
|
||||||
use crate::{account::Account, sysvar::Sysvar};
|
use crate::{account::Account, sysvar::Sysvar};
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
crate::declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
|
crate::declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
|
||||||
|
|
||||||
pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
|
|
||||||
pub struct StakeHistoryEntry {
|
|
||||||
pub effective: u64, // effective stake at this epoch
|
|
||||||
pub activating: u64, // sum of portion of stakes not fully warmed up
|
|
||||||
pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
|
|
||||||
pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
|
|
||||||
|
|
||||||
impl Sysvar for StakeHistory {
|
impl Sysvar for StakeHistory {
|
||||||
// override
|
// override
|
||||||
fn size_of() -> usize {
|
fn size_of() -> usize {
|
||||||
|
@ -30,30 +16,6 @@ impl Sysvar for StakeHistory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StakeHistory {
|
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
||||||
pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> {
|
|
||||||
self.binary_search_by(|probe| epoch.cmp(&probe.0))
|
|
||||||
.ok()
|
|
||||||
.map(|index| &self[index].1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
|
|
||||||
match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
|
|
||||||
Ok(index) => (self.0)[index] = (epoch, entry),
|
|
||||||
Err(index) => (self.0).insert(index, (epoch, entry)),
|
|
||||||
}
|
|
||||||
(self.0).truncate(MAX_ENTRIES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for StakeHistory {
|
|
||||||
type Target = Vec<(Epoch, StakeHistoryEntry)>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account {
|
pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account {
|
||||||
stake_history.create_account(lamports)
|
stake_history.create_account(lamports)
|
||||||
}
|
}
|
||||||
|
@ -61,15 +23,23 @@ pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::stake_history::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size_of() {
|
fn test_size_of() {
|
||||||
|
let mut stake_history = StakeHistory::default();
|
||||||
|
for i in 0..MAX_ENTRIES as u64 {
|
||||||
|
stake_history.add(
|
||||||
|
i,
|
||||||
|
StakeHistoryEntry {
|
||||||
|
activating: i,
|
||||||
|
..StakeHistoryEntry::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bincode::serialized_size(&StakeHistory(vec![
|
bincode::serialized_size(&stake_history).unwrap() as usize,
|
||||||
(0, StakeHistoryEntry::default());
|
|
||||||
MAX_ENTRIES
|
|
||||||
]))
|
|
||||||
.unwrap() as usize,
|
|
||||||
StakeHistory::size_of()
|
StakeHistory::size_of()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue