shrink stakes (#7122)
This commit is contained in:
parent
0f66e5e49b
commit
acbe89a159
|
@ -564,23 +564,23 @@ pub fn process_show_stake_account(
|
||||||
println!("credits observed: {}", stake.credits_observed);
|
println!("credits observed: {}", stake.credits_observed);
|
||||||
println!(
|
println!(
|
||||||
"delegated stake: {}",
|
"delegated stake: {}",
|
||||||
build_balance_message(stake.stake, use_lamports_unit, true)
|
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
||||||
);
|
);
|
||||||
if stake.voter_pubkey != Pubkey::default() {
|
if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||||
println!("delegated voter pubkey: {}", stake.voter_pubkey);
|
println!("delegated voter pubkey: {}", stake.delegation.voter_pubkey);
|
||||||
}
|
}
|
||||||
println!(
|
println!(
|
||||||
"stake activates starting from epoch: {}",
|
"stake activates starting from epoch: {}",
|
||||||
if stake.activation_epoch < std::u64::MAX {
|
if stake.delegation.activation_epoch < std::u64::MAX {
|
||||||
stake.activation_epoch
|
stake.delegation.activation_epoch
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if stake.deactivation_epoch < std::u64::MAX {
|
if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||||
println!(
|
println!(
|
||||||
"stake deactivates starting from epoch: {}",
|
"stake deactivates starting from epoch: {}",
|
||||||
stake.deactivation_epoch
|
stake.delegation.deactivation_epoch
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
show_authorized(&authorized);
|
show_authorized(&authorized);
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub(crate) mod tests {
|
||||||
};
|
};
|
||||||
use solana_stake_program::{
|
use solana_stake_program::{
|
||||||
stake_instruction,
|
stake_instruction,
|
||||||
stake_state::{Authorized, Stake},
|
stake_state::{Authorized, Delegation, Stake},
|
||||||
};
|
};
|
||||||
use solana_vote_program::{vote_instruction, vote_state::VoteInit};
|
use solana_vote_program::{vote_instruction, vote_state::VoteInit};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -183,8 +183,11 @@ pub(crate) mod tests {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let stake = BOOTSTRAP_LEADER_LAMPORTS * 100;
|
let stake = BOOTSTRAP_LEADER_LAMPORTS * 100;
|
||||||
let leader_stake = Stake {
|
let leader_stake = Stake {
|
||||||
|
delegation: Delegation {
|
||||||
stake: BOOTSTRAP_LEADER_LAMPORTS,
|
stake: BOOTSTRAP_LEADER_LAMPORTS,
|
||||||
activation_epoch: std::u64::MAX, // mark as bootstrap
|
activation_epoch: std::u64::MAX, // mark as bootstrap
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,8 +220,11 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
// simulated stake
|
// simulated stake
|
||||||
let other_stake = Stake {
|
let other_stake = Stake {
|
||||||
|
delegation: Delegation {
|
||||||
stake,
|
stake,
|
||||||
activation_epoch: bank.epoch(),
|
activation_epoch: bank.epoch(),
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -185,8 +185,7 @@ impl LocalCluster {
|
||||||
stake_config::create_account(
|
stake_config::create_account(
|
||||||
1,
|
1,
|
||||||
&stake_config::Config {
|
&stake_config::Config {
|
||||||
warmup_rate: 1_000_000_000.0f64,
|
warmup_cooldown_rate: 1_000_000_000.0f64,
|
||||||
cooldown_rate: 1_000_000_000.0f64,
|
|
||||||
slash_penalty: std::u8::MAX,
|
slash_penalty: std::u8::MAX,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -551,8 +550,8 @@ impl LocalCluster {
|
||||||
VoteState::from(&vote_account),
|
VoteState::from(&vote_account),
|
||||||
) {
|
) {
|
||||||
(Some(stake_state), Some(vote_state)) => {
|
(Some(stake_state), Some(vote_state)) => {
|
||||||
if stake_state.voter_pubkey != vote_account_pubkey
|
if stake_state.delegation.voter_pubkey != vote_account_pubkey
|
||||||
|| stake_state.stake != amount
|
|| stake_state.delegation.stake != amount
|
||||||
{
|
{
|
||||||
Err(Error::new(ErrorKind::Other, "invalid stake account state"))
|
Err(Error::new(ErrorKind::Other, "invalid stake account state"))
|
||||||
} else if vote_state.node_pubkey != node_pubkey {
|
} else if vote_state.node_pubkey != node_pubkey {
|
||||||
|
|
|
@ -14,16 +14,13 @@ solana_sdk::declare_id!("StakeConfig11111111111111111111111111111111");
|
||||||
|
|
||||||
// means that no more than RATE of current effective stake may be added or subtracted per
|
// means that no more than RATE of current effective stake may be added or subtracted per
|
||||||
// epoch
|
// epoch
|
||||||
pub const DEFAULT_WARMUP_RATE: f64 = 0.25;
|
pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
|
||||||
pub const DEFAULT_COOLDOWN_RATE: f64 = 0.25;
|
|
||||||
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
|
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// how much stake we can activate per-epoch as a fraction of currently effective stake
|
/// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake
|
||||||
pub warmup_rate: f64,
|
pub warmup_cooldown_rate: f64,
|
||||||
/// how much stake we can deactivate as a fraction of currently effective stake
|
|
||||||
pub cooldown_rate: f64,
|
|
||||||
/// percentage of stake lost when slash, expressed as a portion of std::u8::MAX
|
/// percentage of stake lost when slash, expressed as a portion of std::u8::MAX
|
||||||
pub slash_penalty: u8,
|
pub slash_penalty: u8,
|
||||||
}
|
}
|
||||||
|
@ -39,8 +36,7 @@ impl Config {
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
warmup_rate: DEFAULT_WARMUP_RATE,
|
warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE,
|
||||||
cooldown_rate: DEFAULT_COOLDOWN_RATE,
|
|
||||||
slash_penalty: DEFAULT_SLASH_PENALTY,
|
slash_penalty: DEFAULT_SLASH_PENALTY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,17 +44,27 @@ impl StakeState {
|
||||||
pub fn stake_from(account: &Account) -> Option<Stake> {
|
pub fn stake_from(account: &Account) -> Option<Stake> {
|
||||||
Self::from(account).and_then(|state: Self| state.stake())
|
Self::from(account).and_then(|state: Self| state.stake())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn authorized_from(account: &Account) -> Option<Authorized> {
|
|
||||||
Self::from(account).and_then(|state: Self| state.authorized())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stake(&self) -> Option<Stake> {
|
pub fn stake(&self) -> Option<Stake> {
|
||||||
match self {
|
match self {
|
||||||
StakeState::Stake(_meta, stake) => Some(*stake),
|
StakeState::Stake(_meta, stake) => Some(*stake),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn delegation_from(account: &Account) -> Option<Delegation> {
|
||||||
|
Self::from(account).and_then(|state: Self| state.delegation())
|
||||||
|
}
|
||||||
|
pub fn delegation(&self) -> Option<Delegation> {
|
||||||
|
match self {
|
||||||
|
StakeState::Stake(_meta, stake) => Some(stake.delegation),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authorized_from(account: &Account) -> Option<Authorized> {
|
||||||
|
Self::from(account).and_then(|state: Self| state.authorized())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn authorized(&self) -> Option<Authorized> {
|
pub fn authorized(&self) -> Option<Authorized> {
|
||||||
match self {
|
match self {
|
||||||
StakeState::Stake(meta, _stake) => Some(meta.authorized),
|
StakeState::Stake(meta, _stake) => Some(meta.authorized),
|
||||||
|
@ -105,82 +115,47 @@ impl Meta {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||||
pub struct Stake {
|
pub struct Delegation {
|
||||||
/// most recently delegated vote account pubkey
|
/// to whom the stake is delegated
|
||||||
pub voter_pubkey: Pubkey,
|
pub voter_pubkey: Pubkey,
|
||||||
/// 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
|
|
||||||
pub credits_observed: u64,
|
|
||||||
/// activated stake amount, set at delegate_stake() time
|
/// activated stake amount, set at delegate_stake() 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,
|
||||||
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
||||||
pub deactivation_epoch: Epoch,
|
pub deactivation_epoch: Epoch,
|
||||||
/// stake config (warmup, etc.)
|
/// how much stake we can activate per-epoch as a fraction of currently effective stake
|
||||||
pub config: Config,
|
pub warmup_cooldown_rate: f64,
|
||||||
/// 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 Delegation {
|
||||||
|
|
||||||
impl Default for Stake {
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
voter_pubkey: Pubkey::default(),
|
voter_pubkey: Pubkey::default(),
|
||||||
voter_pubkey_epoch: 0,
|
|
||||||
credits_observed: 0,
|
|
||||||
stake: 0,
|
stake: 0,
|
||||||
activation_epoch: 0,
|
activation_epoch: 0,
|
||||||
deactivation_epoch: std::u64::MAX,
|
deactivation_epoch: std::u64::MAX,
|
||||||
config: Config::default(),
|
warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
|
||||||
prior_delegates: <[(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES]>::default(),
|
|
||||||
prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Authorized {
|
impl Delegation {
|
||||||
pub fn auto(authorized: &Pubkey) -> Self {
|
pub fn new(
|
||||||
|
voter_pubkey: &Pubkey,
|
||||||
|
stake: u64,
|
||||||
|
activation_epoch: Epoch,
|
||||||
|
warmup_cooldown_rate: f64,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
staker: *authorized,
|
voter_pubkey: *voter_pubkey,
|
||||||
withdrawer: *authorized,
|
stake,
|
||||||
|
activation_epoch,
|
||||||
|
warmup_cooldown_rate,
|
||||||
|
..Delegation::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn check(
|
pub fn is_bootstrap(&self) -> bool {
|
||||||
&self,
|
|
||||||
signers: &HashSet<Pubkey>,
|
|
||||||
stake_authorize: StakeAuthorize,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
match stake_authorize {
|
|
||||||
StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
|
|
||||||
StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
|
|
||||||
_ => Err(InstructionError::MissingRequiredSignature),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn authorize(
|
|
||||||
&mut self,
|
|
||||||
signers: &HashSet<Pubkey>,
|
|
||||||
new_authorized: &Pubkey,
|
|
||||||
stake_authorize: StakeAuthorize,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
self.check(signers, stake_authorize)?;
|
|
||||||
match stake_authorize {
|
|
||||||
StakeAuthorize::Staker => self.staker = *new_authorized,
|
|
||||||
StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stake {
|
|
||||||
fn is_bootstrap(&self) -> bool {
|
|
||||||
self.activation_epoch == std::u64::MAX
|
self.activation_epoch == std::u64::MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +197,7 @@ impl Stake {
|
||||||
|
|
||||||
// portion of activating stake in this epoch I'm entitled to
|
// portion of activating stake in this epoch I'm entitled to
|
||||||
effective_stake = effective_stake.saturating_sub(
|
effective_stake = effective_stake.saturating_sub(
|
||||||
((weight * entry.effective as f64 * self.config.cooldown_rate) as u64).max(1),
|
((weight * entry.effective as f64 * self.warmup_cooldown_rate) as u64).max(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
if effective_stake == 0 {
|
if effective_stake == 0 {
|
||||||
|
@ -274,7 +249,7 @@ impl Stake {
|
||||||
|
|
||||||
// portion of activating stake in this epoch I'm entitled to
|
// portion of activating stake in this epoch I'm entitled to
|
||||||
effective_stake +=
|
effective_stake +=
|
||||||
((weight * entry.effective as f64 * self.config.warmup_rate) as u64).max(1);
|
((weight * entry.effective as f64 * self.warmup_cooldown_rate) as u64).max(1);
|
||||||
|
|
||||||
if effective_stake >= self.stake {
|
if effective_stake >= self.stake {
|
||||||
effective_stake = self.stake;
|
effective_stake = self.stake;
|
||||||
|
@ -297,7 +272,74 @@ impl Stake {
|
||||||
(self.stake, 0)
|
(self.stake, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||||
|
pub struct Stake {
|
||||||
|
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
|
||||||
|
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 {
|
||||||
|
pub fn auto(authorized: &Pubkey) -> Self {
|
||||||
|
Self {
|
||||||
|
staker: *authorized,
|
||||||
|
withdrawer: *authorized,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn check(
|
||||||
|
&self,
|
||||||
|
signers: &HashSet<Pubkey>,
|
||||||
|
stake_authorize: StakeAuthorize,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
match stake_authorize {
|
||||||
|
StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
|
||||||
|
StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
|
||||||
|
_ => Err(InstructionError::MissingRequiredSignature),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authorize(
|
||||||
|
&mut self,
|
||||||
|
signers: &HashSet<Pubkey>,
|
||||||
|
new_authorized: &Pubkey,
|
||||||
|
stake_authorize: StakeAuthorize,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
self.check(signers, stake_authorize)?;
|
||||||
|
match stake_authorize {
|
||||||
|
StakeAuthorize::Staker => self.staker = *new_authorized,
|
||||||
|
StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stake {
|
||||||
|
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
||||||
|
self.delegation.stake(epoch, history)
|
||||||
|
}
|
||||||
/// for a given stake and vote_state, calculate what distributions and what updates should be made
|
/// for a given stake and vote_state, calculate what distributions and what updates should be made
|
||||||
/// returns a tuple in the case of a payout of:
|
/// returns a tuple in the case of a payout of:
|
||||||
/// * voter_rewards to be distributed
|
/// * voter_rewards to be distributed
|
||||||
|
@ -332,7 +374,7 @@ impl Stake {
|
||||||
};
|
};
|
||||||
|
|
||||||
total_rewards +=
|
total_rewards +=
|
||||||
(self.stake(*epoch, stake_history) * epoch_credits) as f64 * point_value;
|
(self.delegation.stake(*epoch, stake_history) * epoch_credits) as f64 * point_value;
|
||||||
|
|
||||||
// don't want to assume anything about order of the iterator...
|
// don't want to assume anything about order of the iterator...
|
||||||
credits_observed = credits_observed.max(*credits);
|
credits_observed = credits_observed.max(*credits);
|
||||||
|
@ -372,25 +414,28 @@ impl Stake {
|
||||||
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
|
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
|
||||||
|
|
||||||
self.prior_delegates[self.prior_delegates_idx] = (
|
self.prior_delegates[self.prior_delegates_idx] = (
|
||||||
self.voter_pubkey,
|
self.delegation.voter_pubkey,
|
||||||
self.voter_pubkey_epoch,
|
self.voter_pubkey_epoch,
|
||||||
clock.epoch,
|
clock.epoch,
|
||||||
clock.slot,
|
clock.slot,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.voter_pubkey = *voter_pubkey;
|
self.delegation.voter_pubkey = *voter_pubkey;
|
||||||
self.voter_pubkey_epoch = clock.epoch;
|
self.voter_pubkey_epoch = clock.epoch;
|
||||||
self.credits_observed = vote_state.credits();
|
self.credits_observed = vote_state.credits();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split(&mut self, lamports: u64) -> Result<Self, StakeError> {
|
fn split(&mut self, lamports: u64) -> Result<Self, StakeError> {
|
||||||
if lamports > self.stake {
|
if lamports > self.delegation.stake {
|
||||||
return Err(StakeError::InsufficientStake);
|
return Err(StakeError::InsufficientStake);
|
||||||
}
|
}
|
||||||
self.stake -= lamports;
|
self.delegation.stake -= lamports;
|
||||||
let new = Self {
|
let new = Self {
|
||||||
|
delegation: Delegation {
|
||||||
stake: lamports,
|
stake: lamports,
|
||||||
|
..self.delegation
|
||||||
|
},
|
||||||
..*self
|
..*self
|
||||||
};
|
};
|
||||||
Ok(new)
|
Ok(new)
|
||||||
|
@ -404,21 +449,23 @@ impl Stake {
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
delegation: Delegation::new(
|
||||||
|
voter_pubkey,
|
||||||
stake,
|
stake,
|
||||||
activation_epoch,
|
activation_epoch,
|
||||||
voter_pubkey: *voter_pubkey,
|
config.warmup_cooldown_rate,
|
||||||
|
),
|
||||||
voter_pubkey_epoch: activation_epoch,
|
voter_pubkey_epoch: activation_epoch,
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
config: *config,
|
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
|
fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
|
||||||
if self.deactivation_epoch != std::u64::MAX {
|
if self.delegation.deactivation_epoch != std::u64::MAX {
|
||||||
Err(StakeError::AlreadyDeactivated)
|
Err(StakeError::AlreadyDeactivated)
|
||||||
} else {
|
} else {
|
||||||
self.deactivation_epoch = epoch;
|
self.delegation.deactivation_epoch = epoch;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -576,7 +623,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
|
|
||||||
// the only valid use of current voter_pubkey, redelegation breaks
|
// the only valid use of current voter_pubkey, redelegation breaks
|
||||||
// rewards redemption for previous voter_pubkey
|
// rewards redemption for previous voter_pubkey
|
||||||
if stake.voter_pubkey != *vote_account.unsigned_key() {
|
if stake.delegation.voter_pubkey != *vote_account.unsigned_key() {
|
||||||
return Err(InstructionError::InvalidArgument);
|
return Err(InstructionError::InvalidArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,7 +643,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
vote_account.account.lamports += voters_reward;
|
vote_account.account.lamports += voters_reward;
|
||||||
|
|
||||||
stake.credits_observed = credits_observed;
|
stake.credits_observed = credits_observed;
|
||||||
stake.stake += stakers_reward;
|
stake.delegation.stake += stakers_reward;
|
||||||
|
|
||||||
self.set_state(&StakeState::Stake(meta, stake))
|
self.set_state(&StakeState::Stake(meta, stake))
|
||||||
} else {
|
} else {
|
||||||
|
@ -688,13 +735,13 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
StakeState::Stake(meta, stake) => {
|
StakeState::Stake(meta, stake) => {
|
||||||
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
||||||
// if we have a deactivation epoch and we're in cooldown
|
// if we have a deactivation epoch and we're in cooldown
|
||||||
let staked = if clock.epoch >= stake.deactivation_epoch {
|
let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
|
||||||
stake.stake(clock.epoch, Some(stake_history))
|
stake.delegation.stake(clock.epoch, Some(stake_history))
|
||||||
} else {
|
} else {
|
||||||
// Assume full stake if the stake account hasn't been
|
// Assume full stake if the stake account hasn't been
|
||||||
// de-activated, because in the future the exposed stake
|
// de-activated, because in the future the exposed stake
|
||||||
// might be higher than stake.stake() due to warmup
|
// might be higher than stake.stake() due to warmup
|
||||||
stake.stake
|
stake.delegation.stake
|
||||||
};
|
};
|
||||||
|
|
||||||
(meta.lockup, staked + meta.rent_exempt_reserve, staked != 0)
|
(meta.lockup, staked + meta.rent_exempt_reserve, staked != 0)
|
||||||
|
@ -746,7 +793,7 @@ pub fn new_stake_history_entry<'a, I>(
|
||||||
history: Option<&StakeHistory>,
|
history: Option<&StakeHistory>,
|
||||||
) -> StakeHistoryEntry
|
) -> StakeHistoryEntry
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a Stake>,
|
I: Iterator<Item = &'a Delegation>,
|
||||||
{
|
{
|
||||||
// whatever the stake says they had for the epoch
|
// whatever the stake says they had for the epoch
|
||||||
// and whatever the were still waiting for
|
// and whatever the were still waiting for
|
||||||
|
@ -820,17 +867,17 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_is_bootstrap() {
|
fn test_stake_is_bootstrap() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Stake {
|
Delegation {
|
||||||
activation_epoch: std::u64::MAX,
|
activation_epoch: std::u64::MAX,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
}
|
}
|
||||||
.is_bootstrap(),
|
.is_bootstrap(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Stake {
|
Delegation {
|
||||||
activation_epoch: 0,
|
activation_epoch: 0,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
}
|
}
|
||||||
.is_bootstrap(),
|
.is_bootstrap(),
|
||||||
false
|
false
|
||||||
|
@ -911,12 +958,15 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake,
|
stake,
|
||||||
Stake {
|
Stake {
|
||||||
|
delegation: Delegation {
|
||||||
voter_pubkey: vote_pubkey,
|
voter_pubkey: vote_pubkey,
|
||||||
voter_pubkey_epoch: clock.epoch,
|
|
||||||
credits_observed: vote_state.credits(),
|
|
||||||
stake: stake_lamports,
|
stake: stake_lamports,
|
||||||
activation_epoch: clock.epoch,
|
activation_epoch: clock.epoch,
|
||||||
deactivation_epoch: std::u64::MAX,
|
deactivation_epoch: std::u64::MAX,
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
|
voter_pubkey_epoch: clock.epoch,
|
||||||
|
credits_observed: vote_state.credits(),
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -950,7 +1000,10 @@ mod tests {
|
||||||
fn test_stake_redelegate() {
|
fn test_stake_redelegate() {
|
||||||
// what a freshly delegated stake looks like
|
// what a freshly delegated stake looks like
|
||||||
let mut stake = Stake {
|
let mut stake = Stake {
|
||||||
|
delegation: Delegation {
|
||||||
voter_pubkey: Pubkey::new_rand(),
|
voter_pubkey: Pubkey::new_rand(),
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
voter_pubkey_epoch: 0,
|
voter_pubkey_epoch: 0,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
@ -981,22 +1034,22 @@ mod tests {
|
||||||
),
|
),
|
||||||
Err(StakeError::TooSoonToRedelegate)
|
Err(StakeError::TooSoonToRedelegate)
|
||||||
);
|
);
|
||||||
assert_eq!(stake.voter_pubkey, voter_pubkey);
|
assert_eq!(stake.delegation.voter_pubkey, voter_pubkey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_stake_history_from_stakes(
|
fn create_stake_history_from_delegations(
|
||||||
bootstrap: Option<u64>,
|
bootstrap: Option<u64>,
|
||||||
epochs: std::ops::Range<Epoch>,
|
epochs: std::ops::Range<Epoch>,
|
||||||
stakes: &[Stake],
|
delegations: &[Delegation],
|
||||||
) -> StakeHistory {
|
) -> StakeHistory {
|
||||||
let mut stake_history = StakeHistory::default();
|
let mut stake_history = StakeHistory::default();
|
||||||
|
|
||||||
let bootstrap_stake = if let Some(bootstrap) = bootstrap {
|
let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
|
||||||
vec![Stake {
|
vec![Delegation {
|
||||||
activation_epoch: std::u64::MAX,
|
activation_epoch: std::u64::MAX,
|
||||||
stake: bootstrap,
|
stake: bootstrap,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
}]
|
}]
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -1005,7 +1058,7 @@ mod tests {
|
||||||
for epoch in epochs {
|
for epoch in epochs {
|
||||||
let entry = new_stake_history_entry(
|
let entry = new_stake_history_entry(
|
||||||
epoch,
|
epoch,
|
||||||
stakes.iter().chain(bootstrap_stake.iter()),
|
delegations.iter().chain(bootstrap_delegation.iter()),
|
||||||
Some(&stake_history),
|
Some(&stake_history),
|
||||||
);
|
);
|
||||||
stake_history.add(epoch, entry);
|
stake_history.add(epoch, entry);
|
||||||
|
@ -1016,15 +1069,15 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_activating_and_deactivating() {
|
fn test_stake_activating_and_deactivating() {
|
||||||
let stake = Stake {
|
let stake = Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 0, // activating at zero
|
activation_epoch: 0, // activating at zero
|
||||||
deactivation_epoch: 5,
|
deactivation_epoch: 5,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// save this off so stake.config.warmup_rate changes don't break this test
|
// save this off so stake.config.warmup_rate changes don't break this test
|
||||||
let increment = (1_000 as f64 * stake.config.warmup_rate) as u64;
|
let increment = (1_000 as f64 * stake.warmup_cooldown_rate) as u64;
|
||||||
|
|
||||||
let mut stake_history = StakeHistory::default();
|
let mut stake_history = StakeHistory::default();
|
||||||
// assert that this stake follows step function if there's no history
|
// assert that this stake follows step function if there's no history
|
||||||
|
@ -1123,11 +1176,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stop_activating_after_deactivation() {
|
fn test_stop_activating_after_deactivation() {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let stake = Stake {
|
let stake = Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 0,
|
activation_epoch: 0,
|
||||||
deactivation_epoch: 3,
|
deactivation_epoch: 3,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_stake = 1_000;
|
let base_stake = 1_000;
|
||||||
|
@ -1158,11 +1211,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
if epoch < stake.deactivation_epoch {
|
if epoch < stake.deactivation_epoch {
|
||||||
let increase = (effective as f64 * stake.config.warmup_rate) as u64;
|
let increase = (effective as f64 * stake.warmup_cooldown_rate) as u64;
|
||||||
effective += increase.min(activating);
|
effective += increase.min(activating);
|
||||||
other_activations.push(0);
|
other_activations.push(0);
|
||||||
} else {
|
} else {
|
||||||
let decrease = (effective as f64 * stake.config.cooldown_rate) as u64;
|
let decrease = (effective as f64 * stake.warmup_cooldown_rate) as u64;
|
||||||
effective -= decrease.min(deactivating);
|
effective -= decrease.min(deactivating);
|
||||||
effective += other_activation;
|
effective += other_activation;
|
||||||
other_activations.push(other_activation);
|
other_activations.push(other_activation);
|
||||||
|
@ -1187,25 +1240,26 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_warmup_cooldown_sub_integer_moves() {
|
fn test_stake_warmup_cooldown_sub_integer_moves() {
|
||||||
let stakes = [Stake {
|
let delegations = [Delegation {
|
||||||
stake: 2,
|
stake: 2,
|
||||||
activation_epoch: 0, // activating at zero
|
activation_epoch: 0, // activating at zero
|
||||||
deactivation_epoch: 5,
|
deactivation_epoch: 5,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
}];
|
}];
|
||||||
// give 2 epochs of cooldown
|
// give 2 epochs of cooldown
|
||||||
let epochs = 7;
|
let epochs = 7;
|
||||||
// make boostrap stake smaller than warmup so warmup/cooldownn
|
// make boostrap stake smaller than warmup so warmup/cooldownn
|
||||||
// increment is always smaller than 1
|
// increment is always smaller than 1
|
||||||
let bootstrap = (stakes[0].config.warmup_rate * 100.0 / 2.0) as u64;
|
let bootstrap = (delegations[0].warmup_cooldown_rate * 100.0 / 2.0) as u64;
|
||||||
let stake_history = create_stake_history_from_stakes(Some(bootstrap), 0..epochs, &stakes);
|
let stake_history =
|
||||||
|
create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations);
|
||||||
let mut max_stake = 0;
|
let mut max_stake = 0;
|
||||||
let mut min_stake = 2;
|
let mut min_stake = 2;
|
||||||
|
|
||||||
for epoch in 0..epochs {
|
for epoch in 0..epochs {
|
||||||
let stake = stakes
|
let stake = delegations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stake| stake.stake(epoch, Some(&stake_history)))
|
.map(|delegation| delegation.stake(epoch, Some(&stake_history)))
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
max_stake = max_stake.max(stake);
|
max_stake = max_stake.max(stake);
|
||||||
min_stake = min_stake.min(stake);
|
min_stake = min_stake.min(stake);
|
||||||
|
@ -1216,42 +1270,42 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_warmup_cooldown() {
|
fn test_stake_warmup_cooldown() {
|
||||||
let stakes = [
|
let delegations = [
|
||||||
Stake {
|
Delegation {
|
||||||
// never deactivates
|
// never deactivates
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: std::u64::MAX,
|
activation_epoch: std::u64::MAX,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 0,
|
activation_epoch: 0,
|
||||||
deactivation_epoch: 9,
|
deactivation_epoch: 9,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 1,
|
activation_epoch: 1,
|
||||||
deactivation_epoch: 6,
|
deactivation_epoch: 6,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 2,
|
activation_epoch: 2,
|
||||||
deactivation_epoch: 5,
|
deactivation_epoch: 5,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 2,
|
activation_epoch: 2,
|
||||||
deactivation_epoch: 4,
|
deactivation_epoch: 4,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Delegation {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activation_epoch: 4,
|
activation_epoch: 4,
|
||||||
deactivation_epoch: 4,
|
deactivation_epoch: 4,
|
||||||
..Stake::default()
|
..Delegation::default()
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// chosen to ensure that the last activated stake (at 4) finishes
|
// chosen to ensure that the last activated stake (at 4) finishes
|
||||||
|
@ -1260,19 +1314,19 @@ mod tests {
|
||||||
// when all alone, but the above overlap a lot
|
// when all alone, but the above overlap a lot
|
||||||
let epochs = 20;
|
let epochs = 20;
|
||||||
|
|
||||||
let stake_history = create_stake_history_from_stakes(None, 0..epochs, &stakes);
|
let stake_history = create_stake_history_from_delegations(None, 0..epochs, &delegations);
|
||||||
|
|
||||||
let mut prev_total_effective_stake = stakes
|
let mut prev_total_effective_stake = delegations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stake| stake.stake(0, Some(&stake_history)))
|
.map(|delegation| delegation.stake(0, Some(&stake_history)))
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
|
|
||||||
// uncomment and add ! for fun with graphing
|
// uncomment and add ! for fun with graphing
|
||||||
// eprintln("\n{:8} {:8} {:8}", " epoch", " total", " delta");
|
// eprintln("\n{:8} {:8} {:8}", " epoch", " total", " delta");
|
||||||
for epoch in 1..epochs {
|
for epoch in 1..epochs {
|
||||||
let total_effective_stake = stakes
|
let total_effective_stake = delegations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stake| stake.stake(epoch, Some(&stake_history)))
|
.map(|delegation| delegation.stake(epoch, Some(&stake_history)))
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
|
|
||||||
let delta = if total_effective_stake > prev_total_effective_stake {
|
let delta = if total_effective_stake > prev_total_effective_stake {
|
||||||
|
@ -1288,7 +1342,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
delta
|
delta
|
||||||
<= ((prev_total_effective_stake as f64 * Config::default().warmup_rate) as u64)
|
<= ((prev_total_effective_stake as f64 * Config::default().warmup_cooldown_rate) as u64)
|
||||||
.max(1)
|
.max(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1610,10 +1664,12 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
|
|
||||||
let stake_history = create_stake_history_from_stakes(
|
let stake_history = create_stake_history_from_delegations(
|
||||||
None,
|
None,
|
||||||
0..future.epoch,
|
0..future.epoch,
|
||||||
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()],
|
&[StakeState::stake_from(&stake_keyed_account.account)
|
||||||
|
.unwrap()
|
||||||
|
.delegation],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to withdraw stake
|
// Try to withdraw stake
|
||||||
|
@ -1763,14 +1819,14 @@ mod tests {
|
||||||
|
|
||||||
// this one should be able to collect exactly 2
|
// this one should be able to collect exactly 2
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((0, stake.stake * 2, 2)),
|
Some((0, stake.delegation.stake * 2, 2)),
|
||||||
stake.calculate_rewards(1.0, &vote_state, None)
|
stake.calculate_rewards(1.0, &vote_state, None)
|
||||||
);
|
);
|
||||||
|
|
||||||
stake.credits_observed = 1;
|
stake.credits_observed = 1;
|
||||||
// this one should be able to collect exactly 1 (only observed one)
|
// this one should be able to collect exactly 1 (only observed one)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((0, stake.stake * 1, 2)),
|
Some((0, stake.delegation.stake * 1, 2)),
|
||||||
stake.calculate_rewards(1.0, &vote_state, None)
|
stake.calculate_rewards(1.0, &vote_state, None)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1783,7 +1839,7 @@ mod tests {
|
||||||
vote_state.increment_credits(2);
|
vote_state.increment_credits(2);
|
||||||
// this one should be able to collect 1 now, one credit by a stake of 1
|
// this one should be able to collect 1 now, one credit by a stake of 1
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((0, stake.stake * 1, 3)),
|
Some((0, stake.delegation.stake * 1, 3)),
|
||||||
stake.calculate_rewards(1.0, &vote_state, None)
|
stake.calculate_rewards(1.0, &vote_state, None)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1791,7 +1847,11 @@ mod tests {
|
||||||
// this one should be able to collect everything from t=0 a warmed up stake of 2
|
// this one should be able to collect everything from t=0 a warmed up stake of 2
|
||||||
// (2 credits at stake of 1) + (1 credit at a stake of 2)
|
// (2 credits at stake of 1) + (1 credit at a stake of 2)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some((0, stake.stake * 1 + stake.stake * 2, 3)),
|
Some((
|
||||||
|
0,
|
||||||
|
stake.delegation.stake * 1 + stake.delegation.stake * 2,
|
||||||
|
3
|
||||||
|
)),
|
||||||
stake.calculate_rewards(1.0, &vote_state, None)
|
stake.calculate_rewards(1.0, &vote_state, None)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1858,10 +1918,12 @@ mod tests {
|
||||||
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
|
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let stake_history = create_stake_history_from_stakes(
|
let stake_history = create_stake_history_from_delegations(
|
||||||
Some(100),
|
Some(100),
|
||||||
0..10,
|
0..10,
|
||||||
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()],
|
&[StakeState::stake_from(&stake_keyed_account.account)
|
||||||
|
.unwrap()
|
||||||
|
.delegation],
|
||||||
);
|
);
|
||||||
|
|
||||||
// no credits to claim
|
// no credits to claim
|
||||||
|
@ -1936,7 +1998,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
// verify rewards are added to stake
|
// verify rewards are added to stake
|
||||||
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
||||||
assert_eq!(stake.stake, stake_keyed_account.account.lamports);
|
assert_eq!(stake.delegation.stake, stake_keyed_account.account.lamports);
|
||||||
|
|
||||||
let wrong_vote_pubkey = Pubkey::new_rand();
|
let wrong_vote_pubkey = Pubkey::new_rand();
|
||||||
let mut wrong_vote_keyed_account =
|
let mut wrong_vote_keyed_account =
|
||||||
|
@ -2124,13 +2186,7 @@ mod tests {
|
||||||
let stake_lamports = 42;
|
let stake_lamports = 42;
|
||||||
let mut stake_account = Account::new_data_with_space(
|
let mut stake_account = Account::new_data_with_space(
|
||||||
stake_lamports,
|
stake_lamports,
|
||||||
&StakeState::Stake(
|
&StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)),
|
||||||
Meta::auto(&stake_pubkey),
|
|
||||||
Stake {
|
|
||||||
stake: stake_lamports,
|
|
||||||
..Stake::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
std::mem::size_of::<StakeState>(),
|
std::mem::size_of::<StakeState>(),
|
||||||
&id(),
|
&id(),
|
||||||
)
|
)
|
||||||
|
@ -2154,6 +2210,17 @@ mod tests {
|
||||||
Err(InstructionError::InvalidAccountData)
|
Err(InstructionError::InvalidAccountData)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
impl Stake {
|
||||||
|
fn just_stake(stake: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
delegation: Delegation {
|
||||||
|
stake,
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
|
..Stake::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_more_than_staked() {
|
fn test_split_more_than_staked() {
|
||||||
|
@ -2163,10 +2230,7 @@ mod tests {
|
||||||
stake_lamports,
|
stake_lamports,
|
||||||
&StakeState::Stake(
|
&StakeState::Stake(
|
||||||
Meta::auto(&stake_pubkey),
|
Meta::auto(&stake_pubkey),
|
||||||
Stake {
|
Stake::just_stake(stake_lamports / 2 - 1),
|
||||||
stake: stake_lamports / 2 - 1,
|
|
||||||
..Stake::default()
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
std::mem::size_of::<StakeState>(),
|
std::mem::size_of::<StakeState>(),
|
||||||
&id(),
|
&id(),
|
||||||
|
@ -2209,13 +2273,7 @@ mod tests {
|
||||||
// test splitting both an Initialized stake and a Staked stake
|
// test splitting both an Initialized stake and a Staked stake
|
||||||
for state in &[
|
for state in &[
|
||||||
StakeState::Initialized(meta),
|
StakeState::Initialized(meta),
|
||||||
StakeState::Stake(
|
StakeState::Stake(meta, Stake::just_stake(stake_lamports)),
|
||||||
meta,
|
|
||||||
Stake {
|
|
||||||
stake: stake_lamports,
|
|
||||||
..Stake::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
] {
|
] {
|
||||||
let mut stake_account = Account::new_data_with_space(
|
let mut stake_account = Account::new_data_with_space(
|
||||||
stake_lamports,
|
stake_lamports,
|
||||||
|
@ -2277,7 +2335,10 @@ mod tests {
|
||||||
Ok(StakeState::Stake(
|
Ok(StakeState::Stake(
|
||||||
*meta,
|
*meta,
|
||||||
Stake {
|
Stake {
|
||||||
|
delegation: Delegation {
|
||||||
stake: stake_lamports - rent_exempt_reserve,
|
stake: stake_lamports - rent_exempt_reserve,
|
||||||
|
..stake.delegation
|
||||||
|
},
|
||||||
..*stake
|
..*stake
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
@ -2306,13 +2367,7 @@ mod tests {
|
||||||
// test splitting both an Initialized stake and a Staked stake
|
// test splitting both an Initialized stake and a Staked stake
|
||||||
for state in &[
|
for state in &[
|
||||||
StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||||
StakeState::Stake(
|
StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)),
|
||||||
Meta::auto(&stake_pubkey),
|
|
||||||
Stake {
|
|
||||||
stake: stake_lamports,
|
|
||||||
..Stake::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
] {
|
] {
|
||||||
let mut split_stake_account = Account::new_data_with_space(
|
let mut split_stake_account = Account::new_data_with_space(
|
||||||
0,
|
0,
|
||||||
|
@ -2370,7 +2425,10 @@ mod tests {
|
||||||
Ok(StakeState::Stake(
|
Ok(StakeState::Stake(
|
||||||
*meta,
|
*meta,
|
||||||
Stake {
|
Stake {
|
||||||
|
delegation: Delegation {
|
||||||
stake: stake_lamports / 2,
|
stake: stake_lamports / 2,
|
||||||
|
..stake.delegation
|
||||||
|
},
|
||||||
..*stake
|
..*stake
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
|
@ -2380,7 +2438,10 @@ mod tests {
|
||||||
Ok(StakeState::Stake(
|
Ok(StakeState::Stake(
|
||||||
*meta,
|
*meta,
|
||||||
Stake {
|
Stake {
|
||||||
|
delegation: Delegation {
|
||||||
stake: stake_lamports / 2,
|
stake: stake_lamports / 2,
|
||||||
|
..stake.delegation
|
||||||
|
},
|
||||||
..*stake
|
..*stake
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
|
@ -2467,7 +2528,7 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
||||||
assert_eq!(stake.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!(
|
||||||
|
|
|
@ -70,7 +70,7 @@ fn fill_epoch_with_votes(
|
||||||
fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
|
fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
|
||||||
let stake = StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap();
|
let stake = StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap();
|
||||||
|
|
||||||
stake.stake
|
stake.delegation.stake
|
||||||
== stake.stake(
|
== stake.stake(
|
||||||
bank.epoch(),
|
bank.epoch(),
|
||||||
Some(
|
Some(
|
||||||
|
@ -150,7 +150,7 @@ fn test_stake_account_lifetime() {
|
||||||
let account = bank.get_account(&stake_pubkey).expect("account not found");
|
let account = bank.get_account(&stake_pubkey).expect("account not found");
|
||||||
let stake_state = account.state().expect("couldn't unpack account data");
|
let stake_state = account.state().expect("couldn't unpack account data");
|
||||||
if let StakeState::Stake(_meta, stake) = stake_state {
|
if let StakeState::Stake(_meta, stake) = stake_state {
|
||||||
assert_eq!(stake.stake, 1_000_000);
|
assert_eq!(stake.delegation.stake, 1_000_000);
|
||||||
} else {
|
} else {
|
||||||
assert!(false, "wrong account type found")
|
assert!(false, "wrong account type found")
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ fn test_stake_account_lifetime() {
|
||||||
let account = bank.get_account(&stake_pubkey).expect("account not found");
|
let account = bank.get_account(&stake_pubkey).expect("account not found");
|
||||||
let stake_state = account.state().expect("couldn't unpack account data");
|
let stake_state = account.state().expect("couldn't unpack account data");
|
||||||
if let StakeState::Stake(_meta, stake) = stake_state {
|
if let StakeState::Stake(_meta, stake) = stake_state {
|
||||||
assert_eq!(stake.stake, 1_000_000);
|
assert_eq!(stake.delegation.stake, 1_000_000);
|
||||||
} else {
|
} else {
|
||||||
assert!(false, "wrong account type found")
|
assert!(false, "wrong account type found")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1558,11 +1558,6 @@ impl Bank {
|
||||||
self.stakes.read().unwrap().vote_accounts().clone()
|
self.stakes.read().unwrap().vote_accounts().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// current stake accounts for this bank
|
|
||||||
pub fn stake_accounts(&self) -> HashMap<Pubkey, Account> {
|
|
||||||
self.stakes.read().unwrap().stake_accounts().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// vote accounts for the specific epoch along with the stake
|
/// vote accounts for the specific epoch along with the stake
|
||||||
/// attributed to each account
|
/// attributed to each account
|
||||||
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
||||||
|
@ -1692,7 +1687,7 @@ mod tests {
|
||||||
sysvar::{fees::Fees, rewards::Rewards},
|
sysvar::{fees::Fees, rewards::Rewards},
|
||||||
timing::years_as_slots,
|
timing::years_as_slots,
|
||||||
};
|
};
|
||||||
use solana_stake_program::stake_state::Stake;
|
use solana_stake_program::stake_state::{Delegation, Stake};
|
||||||
use solana_vote_program::{
|
use solana_vote_program::{
|
||||||
vote_instruction,
|
vote_instruction,
|
||||||
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
|
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
|
||||||
|
@ -3277,8 +3272,11 @@ mod tests {
|
||||||
assert!(leader_stake > 0);
|
assert!(leader_stake > 0);
|
||||||
|
|
||||||
let leader_stake = Stake {
|
let leader_stake = Stake {
|
||||||
|
delegation: Delegation {
|
||||||
stake: leader_lamports,
|
stake: leader_lamports,
|
||||||
activation_epoch: std::u64::MAX, // bootstrap
|
activation_epoch: std::u64::MAX, // bootstrap
|
||||||
|
..Delegation::default()
|
||||||
|
},
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,29 @@ use solana_sdk::account::Account;
|
||||||
use solana_sdk::clock::Epoch;
|
use solana_sdk::clock::Epoch;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::sysvar::stake_history::StakeHistory;
|
use solana_sdk::sysvar::stake_history::StakeHistory;
|
||||||
use solana_stake_program::stake_state::{new_stake_history_entry, StakeState};
|
use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState};
|
||||||
use solana_vote_program::vote_state::VoteState;
|
use solana_vote_program::vote_state::VoteState;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct StakeDelegation {
|
||||||
|
/// to whom the stake is delegated
|
||||||
|
pub voter_pubkey: Pubkey,
|
||||||
|
/// activated stake amount, set at delegate_stake() time
|
||||||
|
pub stake: u64,
|
||||||
|
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
|
||||||
|
pub activation_epoch: Epoch,
|
||||||
|
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
||||||
|
pub deactivation_epoch: Epoch,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
|
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||||
pub struct Stakes {
|
pub struct Stakes {
|
||||||
/// vote accounts
|
/// vote accounts
|
||||||
vote_accounts: HashMap<Pubkey, (u64, Account)>,
|
vote_accounts: HashMap<Pubkey, (u64, Account)>,
|
||||||
|
|
||||||
/// stake_accounts
|
/// stake_delegations
|
||||||
stake_accounts: HashMap<Pubkey, Account>,
|
stake_delegations: HashMap<Pubkey, Delegation>,
|
||||||
|
|
||||||
/// unclaimed points.
|
/// unclaimed points.
|
||||||
// a point is a credit multiplied by the stake
|
// a point is a credit multiplied by the stake
|
||||||
|
@ -41,19 +53,15 @@ impl Stakes {
|
||||||
self.epoch,
|
self.epoch,
|
||||||
new_stake_history_entry(
|
new_stake_history_entry(
|
||||||
self.epoch,
|
self.epoch,
|
||||||
self.stake_accounts
|
self.stake_delegations
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(_pubkey, stake_account)| {
|
.map(|(_pubkey, stake_delegation)| stake_delegation),
|
||||||
StakeState::stake_from(stake_account)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.iter(),
|
|
||||||
Some(&self.stake_history),
|
Some(&self.stake_history),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Stakes {
|
Stakes {
|
||||||
stake_accounts: self.stake_accounts.clone(),
|
stake_delegations: self.stake_delegations.clone(),
|
||||||
points: self.points,
|
points: self.points,
|
||||||
epoch,
|
epoch,
|
||||||
vote_accounts: self
|
vote_accounts: self
|
||||||
|
@ -81,17 +89,15 @@ impl Stakes {
|
||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
stake_history: Option<&StakeHistory>,
|
stake_history: Option<&StakeHistory>,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
self.stake_accounts
|
self.stake_delegations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, stake_account)| {
|
.map(|(_, stake_delegation)| {
|
||||||
StakeState::stake_from(stake_account).map_or(0, |stake| {
|
if &stake_delegation.voter_pubkey == voter_pubkey {
|
||||||
if &stake.voter_pubkey == voter_pubkey {
|
stake_delegation.stake(epoch, stake_history)
|
||||||
stake.stake(epoch, stake_history)
|
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,20 +132,20 @@ impl Stakes {
|
||||||
}
|
}
|
||||||
} else if solana_stake_program::check_id(&account.owner) {
|
} else if solana_stake_program::check_id(&account.owner) {
|
||||||
// old_stake is stake lamports and voter_pubkey from the pre-store() version
|
// old_stake is stake lamports and voter_pubkey from the pre-store() version
|
||||||
let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| {
|
let old_stake = self.stake_delegations.get(pubkey).map(|delegation| {
|
||||||
StakeState::stake_from(old_account).map(|stake| {
|
|
||||||
(
|
(
|
||||||
stake.voter_pubkey,
|
delegation.voter_pubkey,
|
||||||
stake.stake(self.epoch, Some(&self.stake_history)),
|
delegation.stake(self.epoch, Some(&self.stake_history)),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let stake = StakeState::stake_from(account).map(|stake| {
|
let delegation = StakeState::delegation_from(account);
|
||||||
|
|
||||||
|
let stake = delegation.map(|delegation| {
|
||||||
(
|
(
|
||||||
stake.voter_pubkey,
|
delegation.voter_pubkey,
|
||||||
if account.lamports != 0 {
|
if account.lamports != 0 {
|
||||||
stake.stake(self.epoch, Some(&self.stake_history))
|
delegation.stake(self.epoch, Some(&self.stake_history))
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
|
@ -161,9 +167,9 @@ impl Stakes {
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.lamports == 0 {
|
if account.lamports == 0 {
|
||||||
self.stake_accounts.remove(pubkey);
|
self.stake_delegations.remove(pubkey);
|
||||||
} else {
|
} else if let Some(delegation) = delegation {
|
||||||
self.stake_accounts.insert(*pubkey, account.clone());
|
self.stake_delegations.insert(*pubkey, delegation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,19 +178,6 @@ impl Stakes {
|
||||||
&self.vote_accounts
|
&self.vote_accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stake_accounts(&self) -> &HashMap<Pubkey, Account> {
|
|
||||||
&self.stake_accounts
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rewards_pools(&self) -> impl Iterator<Item = (&Pubkey, &Account)> {
|
|
||||||
self.stake_accounts
|
|
||||||
.iter()
|
|
||||||
.filter(|(_key, account)| match StakeState::from(account) {
|
|
||||||
Some(StakeState::RewardsPool) => true,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn highest_staked_node(&self) -> Option<Pubkey> {
|
pub fn highest_staked_node(&self) -> Option<Pubkey> {
|
||||||
self.vote_accounts
|
self.vote_accounts
|
||||||
.iter()
|
.iter()
|
||||||
|
|
Loading…
Reference in New Issue