parent
a252acf539
commit
814af378a7
|
@ -166,7 +166,7 @@ pub(crate) mod tests {
|
||||||
let stake = BOOTSTRAP_LEADER_LAMPORTS * 100;
|
let stake = BOOTSTRAP_LEADER_LAMPORTS * 100;
|
||||||
let leader_stake = Stake {
|
let leader_stake = Stake {
|
||||||
stake: BOOTSTRAP_LEADER_LAMPORTS,
|
stake: BOOTSTRAP_LEADER_LAMPORTS,
|
||||||
activated: std::u64::MAX, // mark as bootstrap
|
activation_epoch: std::u64::MAX, // mark as bootstrap
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ pub(crate) mod tests {
|
||||||
// simulated stake
|
// simulated stake
|
||||||
let other_stake = Stake {
|
let other_stake = Stake {
|
||||||
stake,
|
stake,
|
||||||
activated: bank.epoch(),
|
activation_epoch: bank.epoch(),
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,9 @@ impl StakeState {
|
||||||
pub struct Stake {
|
pub struct Stake {
|
||||||
pub voter_pubkey: Pubkey,
|
pub voter_pubkey: Pubkey,
|
||||||
pub credits_observed: u64,
|
pub credits_observed: u64,
|
||||||
pub stake: u64, // stake amount activated
|
pub stake: u64, // stake amount activated
|
||||||
pub activated: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake
|
pub activation_epoch: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake
|
||||||
pub deactivated: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
pub deactivation_epoch: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ impl Default for Stake {
|
||||||
voter_pubkey: Pubkey::default(),
|
voter_pubkey: Pubkey::default(),
|
||||||
credits_observed: 0,
|
credits_observed: 0,
|
||||||
stake: 0,
|
stake: 0,
|
||||||
activated: 0,
|
activation_epoch: 0,
|
||||||
deactivated: std::u64::MAX,
|
deactivation_epoch: std::u64::MAX,
|
||||||
config: Config::default(),
|
config: Config::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,71 +74,128 @@ impl Default for Stake {
|
||||||
|
|
||||||
impl Stake {
|
impl Stake {
|
||||||
pub fn is_bootstrap(&self) -> bool {
|
pub fn is_bootstrap(&self) -> bool {
|
||||||
self.activated == std::u64::MAX
|
self.activation_epoch == std::u64::MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activating(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
pub fn activating(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
||||||
self.stake_and_activating(epoch, history).1
|
self.stake_activating_and_deactivating(epoch, history).1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivating(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
||||||
|
self.stake_activating_and_deactivating(epoch, history).2
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
||||||
self.stake_and_activating(epoch, history).0
|
self.stake_activating_and_deactivating(epoch, history).0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stake_and_activating(&self, epoch: Epoch, history: Option<&StakeHistory>) -> (u64, u64) {
|
pub fn stake_activating_and_deactivating(
|
||||||
if epoch >= self.deactivated {
|
&self,
|
||||||
(0, 0) // TODO cooldown
|
epoch: Epoch,
|
||||||
} else if self.is_bootstrap() {
|
history: Option<&StakeHistory>,
|
||||||
(self.stake, 0)
|
) -> (u64, u64, u64) {
|
||||||
} else if epoch > self.activated {
|
// first, calculate an effective stake and activating number
|
||||||
if let Some(history) = history {
|
let (stake, activating) = self.stake_and_activating(epoch, history);
|
||||||
if let Some(mut entry) = history.get(&self.activated) {
|
|
||||||
let mut effective_stake = 0;
|
|
||||||
let mut next_epoch = self.activated;
|
|
||||||
|
|
||||||
// loop from my activation epoch until the current epoch
|
// then de-activate some portion if necessary
|
||||||
// summing up my entitlement
|
if epoch < self.deactivation_epoch {
|
||||||
loop {
|
(stake, activating, 0) // not deactivated
|
||||||
if entry.activating == 0 {
|
} else if epoch == self.deactivation_epoch {
|
||||||
break;
|
(stake, 0, stake.min(self.stake)) // can only deactivate what's activated
|
||||||
}
|
} else if let Some((history, mut entry)) = history.and_then(|history| {
|
||||||
// how much of the growth in stake this account is
|
history
|
||||||
// entitled to take
|
.get(&self.deactivation_epoch)
|
||||||
let weight =
|
.map(|entry| (history, entry))
|
||||||
(self.stake - effective_stake) as f64 / entry.activating as f64;
|
}) {
|
||||||
|
// && epoch > self.deactivation_epoch
|
||||||
|
let mut effective_stake = stake;
|
||||||
|
let mut next_epoch = self.deactivation_epoch;
|
||||||
|
|
||||||
// portion of activating stake in this epoch I'm entitled to
|
// loop from my activation epoch until the current epoch
|
||||||
effective_stake +=
|
// summing up my entitlement
|
||||||
(weight * entry.effective as f64 * self.config.warmup_rate) as u64;
|
loop {
|
||||||
|
if entry.deactivating == 0 {
|
||||||
if effective_stake >= self.stake {
|
break;
|
||||||
effective_stake = self.stake;
|
}
|
||||||
break;
|
// I'm trying to get to zero, how much of the deactivation in stake
|
||||||
}
|
// this account is entitled to take
|
||||||
|
let weight = effective_stake as f64 / entry.deactivating as f64;
|
||||||
next_epoch += 1;
|
|
||||||
if next_epoch >= epoch {
|
// portion of activating stake in this epoch I'm entitled to
|
||||||
break;
|
effective_stake = effective_stake.saturating_sub(
|
||||||
}
|
((weight * entry.effective as f64 * self.config.cooldown_rate) as u64).max(1),
|
||||||
if let Some(next_entry) = history.get(&next_epoch) {
|
);
|
||||||
entry = next_entry;
|
|
||||||
} else {
|
if effective_stake == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
(effective_stake, self.stake - effective_stake)
|
next_epoch += 1;
|
||||||
} else {
|
if next_epoch >= epoch {
|
||||||
// I've dropped out of warmup history, so my stake must be the full amount
|
break;
|
||||||
(self.stake, 0)
|
}
|
||||||
|
if let Some(next_entry) = history.get(&next_epoch) {
|
||||||
|
entry = next_entry;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// no history, fully warmed up
|
|
||||||
(self.stake, 0)
|
|
||||||
}
|
}
|
||||||
} else if epoch == self.activated {
|
(effective_stake, 0, effective_stake)
|
||||||
(0, self.stake)
|
|
||||||
} else {
|
} else {
|
||||||
|
// no history or I've dropped out of history, so fully deactivated
|
||||||
|
(0, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stake_and_activating(&self, epoch: Epoch, history: Option<&StakeHistory>) -> (u64, u64) {
|
||||||
|
if self.is_bootstrap() {
|
||||||
|
(self.stake, 0)
|
||||||
|
} else if epoch == self.activation_epoch {
|
||||||
|
(0, self.stake)
|
||||||
|
} else if epoch < self.activation_epoch {
|
||||||
(0, 0)
|
(0, 0)
|
||||||
|
} else if let Some((history, mut entry)) = history.and_then(|history| {
|
||||||
|
history
|
||||||
|
.get(&self.activation_epoch)
|
||||||
|
.map(|entry| (history, entry))
|
||||||
|
}) {
|
||||||
|
// && !is_bootstrap() && epoch > self.activation_epoch
|
||||||
|
let mut effective_stake = 0;
|
||||||
|
let mut next_epoch = self.activation_epoch;
|
||||||
|
|
||||||
|
// loop from my activation epoch until the current epoch
|
||||||
|
// summing up my entitlement
|
||||||
|
loop {
|
||||||
|
if entry.activating == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// how much of the growth in stake this account is
|
||||||
|
// entitled to take
|
||||||
|
let weight = (self.stake - effective_stake) as f64 / entry.activating as f64;
|
||||||
|
|
||||||
|
// portion of activating stake in this epoch I'm entitled to
|
||||||
|
effective_stake +=
|
||||||
|
((weight * entry.effective as f64 * self.config.warmup_rate) as u64).max(1);
|
||||||
|
|
||||||
|
if effective_stake >= self.stake {
|
||||||
|
effective_stake = self.stake;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_epoch += 1;
|
||||||
|
if next_epoch >= epoch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Some(next_entry) = history.get(&next_epoch) {
|
||||||
|
entry = next_entry;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(effective_stake, self.stake - effective_stake)
|
||||||
|
} else {
|
||||||
|
// no history or I've dropped out of history, so assume fully activated
|
||||||
|
(self.stake, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +260,7 @@ impl Stake {
|
||||||
fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self {
|
fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stake,
|
stake,
|
||||||
activated: std::u64::MAX,
|
activation_epoch: std::u64::MAX,
|
||||||
voter_pubkey: *voter_pubkey,
|
voter_pubkey: *voter_pubkey,
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
|
@ -214,12 +271,12 @@ impl Stake {
|
||||||
stake: u64,
|
stake: u64,
|
||||||
voter_pubkey: &Pubkey,
|
voter_pubkey: &Pubkey,
|
||||||
vote_state: &VoteState,
|
vote_state: &VoteState,
|
||||||
activated: Epoch,
|
activation_epoch: Epoch,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stake,
|
stake,
|
||||||
activated,
|
activation_epoch,
|
||||||
voter_pubkey: *voter_pubkey,
|
voter_pubkey: *voter_pubkey,
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
config: *config,
|
config: *config,
|
||||||
|
@ -228,7 +285,7 @@ impl Stake {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(&mut self, epoch: u64) {
|
fn deactivate(&mut self, epoch: u64) {
|
||||||
self.deactivated = epoch;
|
self.deactivation_epoch = epoch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,14 +420,16 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||||
|
|
||||||
match self.state()? {
|
match self.state()? {
|
||||||
StakeState::Stake(stake) => {
|
StakeState::Stake(stake) => {
|
||||||
// if deactivated and in cooldown
|
// if we have a deactivation epoch and we're in cooldown
|
||||||
let staked = if clock.epoch >= stake.deactivated {
|
let staked = if clock.epoch >= stake.deactivation_epoch {
|
||||||
stake.stake(clock.epoch, Some(stake_history))
|
stake.stake(clock.epoch, Some(stake_history))
|
||||||
} else {
|
} else {
|
||||||
// Assume full stake if the stake is under warmup, or
|
// Assume full stake if the stake account hasn't been
|
||||||
// hasn't been de-activated
|
// de-activated, because in the future the exposeed stake
|
||||||
|
// might be higher than stake.stake(), 'cuz warmup
|
||||||
stake.stake
|
stake.stake
|
||||||
};
|
};
|
||||||
|
|
||||||
if lamports > self.account.lamports.saturating_sub(staked) {
|
if lamports > self.account.lamports.saturating_sub(staked) {
|
||||||
return Err(InstructionError::InsufficientFunds);
|
return Err(InstructionError::InsufficientFunds);
|
||||||
}
|
}
|
||||||
|
@ -402,17 +461,17 @@ where
|
||||||
{
|
{
|
||||||
// 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
|
||||||
let (effective, activating): (Vec<_>, Vec<_>) = stakes
|
fn add(a: (u64, u64, u64), b: (u64, u64, u64)) -> (u64, u64, u64) {
|
||||||
.map(|stake| stake.stake_and_activating(epoch, history))
|
(a.0 + b.0, a.1 + b.1, a.2 + b.2)
|
||||||
.unzip();
|
}
|
||||||
|
let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| {
|
||||||
let effective = effective.iter().sum();
|
add(sum, stake.stake_activating_and_deactivating(epoch, history))
|
||||||
let activating = activating.iter().sum();
|
});
|
||||||
|
|
||||||
StakeHistoryEntry {
|
StakeHistoryEntry {
|
||||||
effective,
|
effective,
|
||||||
activating,
|
activating,
|
||||||
..StakeHistoryEntry::default()
|
deactivating,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +511,7 @@ mod tests {
|
||||||
|
|
||||||
let bootstrap_stake = if let Some(bootstrap) = bootstrap {
|
let bootstrap_stake = if let Some(bootstrap) = bootstrap {
|
||||||
vec![Stake {
|
vec![Stake {
|
||||||
activated: std::u64::MAX,
|
activation_epoch: std::u64::MAX,
|
||||||
stake: bootstrap,
|
stake: bootstrap,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
}]
|
}]
|
||||||
|
@ -528,8 +587,8 @@ mod tests {
|
||||||
voter_pubkey: vote_keypair.pubkey(),
|
voter_pubkey: vote_keypair.pubkey(),
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
stake: stake_lamports,
|
stake: stake_lamports,
|
||||||
activated: clock.epoch,
|
activation_epoch: clock.epoch,
|
||||||
deactivated: std::u64::MAX,
|
deactivation_epoch: std::u64::MAX,
|
||||||
config: Config::default()
|
config: Config::default()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -569,39 +628,46 @@ mod tests {
|
||||||
fn test_stake_warmup() {
|
fn test_stake_warmup() {
|
||||||
let stakes = [
|
let stakes = [
|
||||||
Stake {
|
Stake {
|
||||||
|
// never deactivates
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activated: std::u64::MAX,
|
activation_epoch: std::u64::MAX,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Stake {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activated: 0,
|
activation_epoch: 0,
|
||||||
|
deactivation_epoch: 9,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Stake {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activated: 1,
|
activation_epoch: 1,
|
||||||
|
deactivation_epoch: 6,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Stake {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activated: 2,
|
activation_epoch: 2,
|
||||||
|
deactivation_epoch: 5,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Stake {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activated: 2,
|
activation_epoch: 2,
|
||||||
|
deactivation_epoch: 4,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
},
|
},
|
||||||
Stake {
|
Stake {
|
||||||
stake: 1_000,
|
stake: 1_000,
|
||||||
activated: 4,
|
activation_epoch: 4,
|
||||||
|
deactivation_epoch: 4,
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// chosen to ensure that the last activated stake (at 4) finishes warming up
|
// chosen to ensure that the last activated stake (at 4) finishes
|
||||||
// a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up
|
// warming up and cooling down
|
||||||
// all else equal, but the above overlap
|
// a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down
|
||||||
|
// 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_stakes(None, 0..epochs, &stakes);
|
||||||
|
@ -611,21 +677,31 @@ mod tests {
|
||||||
.map(|stake| stake.stake(0, Some(&stake_history)))
|
.map(|stake| stake.stake(0, Some(&stake_history)))
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
|
|
||||||
for epoch in 1.. {
|
// uncomment and add ! for fun with graphing
|
||||||
|
// eprintln("\n{:8} {:8} {:8}", " epoch", " total", " delta");
|
||||||
|
for epoch in 1..epochs {
|
||||||
let total_effective_stake = stakes
|
let total_effective_stake = stakes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stake| stake.stake(epoch, Some(&stake_history)))
|
.map(|stake| stake.stake(epoch, Some(&stake_history)))
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
|
|
||||||
let delta = total_effective_stake - prev_total_effective_stake;
|
let delta = if total_effective_stake > prev_total_effective_stake {
|
||||||
|
total_effective_stake - prev_total_effective_stake
|
||||||
|
} else {
|
||||||
|
prev_total_effective_stake - total_effective_stake
|
||||||
|
};
|
||||||
|
|
||||||
|
// uncomment and add ! for fun with graphing
|
||||||
|
//eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta);
|
||||||
|
//(0..(total_effective_stake as usize / (stakes.len() * 5))).for_each(|_| eprint("#"));
|
||||||
|
//eprintln();
|
||||||
|
|
||||||
if delta == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assert!(epoch < epochs); // should have warmed everything up by this time
|
|
||||||
assert!(
|
assert!(
|
||||||
delta as f64 / prev_total_effective_stake as f64 <= Config::default().warmup_rate
|
delta
|
||||||
|
<= ((prev_total_effective_stake as f64 * Config::default().warmup_rate) as u64)
|
||||||
|
.max(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
prev_total_effective_stake = total_effective_stake;
|
prev_total_effective_stake = total_effective_stake;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -765,7 +841,7 @@ mod tests {
|
||||||
),
|
),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
// reset
|
// reset balance
|
||||||
stake_account.lamports = total_lamports;
|
stake_account.lamports = total_lamports;
|
||||||
|
|
||||||
// withdrawal before deactivate fails if not in excess of stake
|
// withdrawal before deactivate fails if not in excess of stake
|
||||||
|
|
|
@ -2451,7 +2451,7 @@ mod tests {
|
||||||
|
|
||||||
let leader_stake = Stake {
|
let leader_stake = Stake {
|
||||||
stake: leader_lamports,
|
stake: leader_lamports,
|
||||||
activated: std::u64::MAX, // bootstrap
|
activation_epoch: std::u64::MAX, // bootstrap
|
||||||
..Stake::default()
|
..Stake::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -864,9 +864,15 @@ fn process_show_stake_account(
|
||||||
if stake.voter_pubkey != Pubkey::default() {
|
if stake.voter_pubkey != Pubkey::default() {
|
||||||
println!("delegated voter pubkey: {}", stake.voter_pubkey);
|
println!("delegated voter pubkey: {}", stake.voter_pubkey);
|
||||||
}
|
}
|
||||||
println!("stake activates at epoch: {}", stake.activated);
|
println!(
|
||||||
if stake.deactivated < std::u64::MAX {
|
"stake activates starting from epoch: {}",
|
||||||
println!("stake deactivates at epoch: {}", stake.deactivated);
|
stake.activation_epoch
|
||||||
|
);
|
||||||
|
if stake.deactivation_epoch < std::u64::MAX {
|
||||||
|
println!(
|
||||||
|
"stake deactivates starting from epoch: {}",
|
||||||
|
stake.deactivation_epoch
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue