Skip to content

Commit

Permalink
Add struct and convenience methods to track stake activation status (#…
Browse files Browse the repository at this point in the history
…20392)

* Add struct and convenience methods to track stake activation status

* fix nits

* rename
  • Loading branch information
jstarry authored Oct 4, 2021
1 parent 2b96720 commit 0ddb34a
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 98 deletions.
20 changes: 11 additions & 9 deletions cli/src/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@ use solana_sdk::{
stake::{
self,
instruction::{self as stake_instruction, LockupArgs, StakeError},
state::{Authorized, Lockup, Meta, StakeAuthorize, StakeState},
state::{Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeState},
},
stake_history::StakeHistory,
system_instruction::SystemError,
sysvar::{
clock,
stake_history::{self, StakeHistory},
},
sysvar::{clock, stake_history},
transaction::Transaction,
};
use solana_vote_program::vote_state::VoteState;
Expand Down Expand Up @@ -2013,7 +2011,11 @@ pub fn build_stake_state(
stake,
) => {
let current_epoch = clock.epoch;
let (active_stake, activating_stake, deactivating_stake) = stake
let StakeActivationStatus {
effective,
activating,
deactivating,
} = stake
.delegation
.stake_activating_and_deactivating(current_epoch, Some(stake_history));
let lockup = if lockup.is_in_force(clock, None) {
Expand Down Expand Up @@ -2048,9 +2050,9 @@ pub fn build_stake_state(
use_lamports_unit,
current_epoch,
rent_exempt_reserve: Some(*rent_exempt_reserve),
active_stake: u64_some_if_not_zero(active_stake),
activating_stake: u64_some_if_not_zero(activating_stake),
deactivating_stake: u64_some_if_not_zero(deactivating_stake),
active_stake: u64_some_if_not_zero(effective),
activating_stake: u64_some_if_not_zero(activating),
deactivating_stake: u64_some_if_not_zero(deactivating),
..CliStakeState::default()
}
}
Expand Down
7 changes: 3 additions & 4 deletions program-test/tests/warp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#![allow(clippy::integer_arithmetic)]
use {
assert_matches::assert_matches,
bincode::deserialize,
solana_banks_client::BanksClient,
solana_program_test::{processor, ProgramTest, ProgramTestContext, ProgramTestError},
Expand All @@ -15,7 +14,7 @@ use {
signature::{Keypair, Signer},
stake::{
instruction as stake_instruction,
state::{Authorized, Lockup, StakeState},
state::{Authorized, Lockup, StakeActivationStatus, StakeState},
},
system_instruction, system_program,
sysvar::{
Expand Down Expand Up @@ -298,11 +297,11 @@ async fn stake_rewards_from_warp() {
let stake_history: StakeHistory = deserialize(&stake_history_account.data).unwrap();
let clock: Clock = deserialize(&clock_account.data).unwrap();
let stake = stake_state.stake().unwrap();
assert_matches!(
assert_eq!(
stake
.delegation
.stake_activating_and_deactivating(clock.epoch, Some(&stake_history)),
(_, 0, 0)
StakeActivationStatus::with_effective(stake.delegation.stake),
);
}

Expand Down
130 changes: 62 additions & 68 deletions programs/stake/src/stake_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,12 +875,11 @@ impl MergeKind {
StakeState::Stake(meta, stake) => {
// stake must not be in a transient state. Transient here meaning
// activating or deactivating with non-zero effective stake.
match stake
let status = stake
.delegation
.stake_activating_and_deactivating(clock.epoch, Some(stake_history))
{
/*
(e, a, d): e - effective, a - activating, d - deactivating */
.stake_activating_and_deactivating(clock.epoch, Some(stake_history));

match (status.effective, status.activating, status.deactivating) {
(0, 0, 0) => Ok(Self::Inactive(meta, stake_keyed_account.lamports()?)),
(0, _, _) => Ok(Self::ActivationEpoch(meta, stake)),
(_, 0, 0) => Ok(Self::FullyActive(meta, stake)),
Expand Down Expand Up @@ -1192,20 +1191,9 @@ pub fn new_stake_history_entry<'a, I>(
where
I: Iterator<Item = &'a Delegation>,
{
// whatever the stake says they had for the epoch
// and whatever the were still waiting for
fn add(a: (u64, u64, u64), b: (u64, u64, u64)) -> (u64, u64, u64) {
(a.0 + b.0, a.1 + b.1, a.2 + b.2)
}
let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| {
add(sum, stake.stake_activating_and_deactivating(epoch, history))
});

StakeHistoryEntry {
effective,
activating,
deactivating,
}
stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
sum + stake.stake_activating_and_deactivating(epoch, history)
})
}

// genesis investor accounts
Expand Down Expand Up @@ -1769,41 +1757,40 @@ mod tests {
// assert that this stake follows step function if there's no history
assert_eq!(
stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history),),
(0, stake.stake, 0)
StakeActivationStatus::with_effective_and_activating(0, stake.stake),
);
for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)),
(stake.stake, 0, 0)
StakeActivationStatus::with_effective(stake.stake),
);
}
// assert that this stake is full deactivating
assert_eq!(
stake
.stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history),),
(stake.stake, 0, stake.stake)
StakeActivationStatus::with_deactivating(stake.stake),
);
// assert that this stake is fully deactivated if there's no history
assert_eq!(
stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1,
Some(&stake_history),
),
(0, 0, 0)
StakeActivationStatus::default(),
);

stake_history.add(
0u64, // entry for zero doesn't have my activating amount
StakeHistoryEntry {
effective: 1_000,
activating: 0,
..StakeHistoryEntry::default()
},
);
// assert that this stake is broken, because above setup is broken
assert_eq!(
stake.stake_activating_and_deactivating(1, Some(&stake_history)),
(0, stake.stake, 0)
StakeActivationStatus::with_effective_and_activating(0, stake.stake),
);

stake_history.add(
Expand All @@ -1818,7 +1805,10 @@ mod tests {
// assert that this stake is broken, because above setup is broken
assert_eq!(
stake.stake_activating_and_deactivating(2, Some(&stake_history)),
(increment, stake.stake - increment, 0)
StakeActivationStatus::with_effective_and_activating(
increment,
stake.stake - increment
),
);

// start over, test deactivation edge cases
Expand All @@ -1828,7 +1818,6 @@ mod tests {
stake.deactivation_epoch, // entry for zero doesn't have my de-activating amount
StakeHistoryEntry {
effective: 1_000,
activating: 0,
..StakeHistoryEntry::default()
},
);
Expand All @@ -1838,7 +1827,7 @@ mod tests {
stake.deactivation_epoch + 1,
Some(&stake_history),
),
(stake.stake, 0, stake.stake) // says "I'm still waiting for deactivation"
StakeActivationStatus::with_deactivating(stake.stake),
);

// put in my initial deactivating amount, but don't put in an entry for next
Expand All @@ -1856,7 +1845,8 @@ mod tests {
stake.deactivation_epoch + 2,
Some(&stake_history),
),
(stake.stake - increment, 0, stake.stake - increment) // hung, should be lower
// hung, should be lower
StakeActivationStatus::with_deactivating(stake.stake - increment),
);
}

Expand All @@ -1868,7 +1858,10 @@ mod tests {
Slow,
}

fn do_test(old_behavior: OldDeactivationBehavior, expected_stakes: &[(u64, u64, u64)]) {
fn do_test(
old_behavior: OldDeactivationBehavior,
expected_stakes: &[StakeActivationStatus],
) {
let cluster_stake = 1_000;
let activating_stake = 10_000;
let some_stake = 700;
Expand Down Expand Up @@ -1931,13 +1924,13 @@ mod tests {
do_test(
OldDeactivationBehavior::Slow,
&[
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
],
);
}
Expand All @@ -1950,13 +1943,13 @@ mod tests {
do_test(
OldDeactivationBehavior::Stuck,
&[
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
StakeActivationStatus::default(),
],
);
}
Expand Down Expand Up @@ -2049,37 +2042,34 @@ mod tests {
})
.collect::<Vec<_>>()
};
let adjust_staking_status = |rate: f64, status: &Vec<_>| {
let adjust_staking_status = |rate: f64, status: &[StakeActivationStatus]| {
status
.clone()
.into_iter()
.map(|(a, b, c)| {
(
(a as f64 * rate) as u64,
(b as f64 * rate) as u64,
(c as f64 * rate) as u64,
)
.iter()
.map(|entry| StakeActivationStatus {
effective: (entry.effective as f64 * rate) as u64,
activating: (entry.activating as f64 * rate) as u64,
deactivating: (entry.deactivating as f64 * rate) as u64,
})
.collect::<Vec<_>>()
};

let expected_staking_status_transition = vec![
(0, 700, 0),
(250, 450, 0),
(562, 138, 0),
(700, 0, 0),
(700, 0, 700),
(275, 0, 275),
(0, 0, 0),
StakeActivationStatus::with_effective_and_activating(0, 700),
StakeActivationStatus::with_effective_and_activating(250, 450),
StakeActivationStatus::with_effective_and_activating(562, 138),
StakeActivationStatus::with_effective(700),
StakeActivationStatus::with_deactivating(700),
StakeActivationStatus::with_deactivating(275),
StakeActivationStatus::default(),
];
let expected_staking_status_transition_base = vec![
(0, 700, 0),
(250, 450, 0),
(562, 138 + 1, 0), // +1 is needed for rounding
(700, 0, 0),
(700, 0, 700),
(275 + 1, 0, 275 + 1), // +1 is needed for rounding
(0, 0, 0),
StakeActivationStatus::with_effective_and_activating(0, 700),
StakeActivationStatus::with_effective_and_activating(250, 450),
StakeActivationStatus::with_effective_and_activating(562, 138 + 1), // +1 is needed for rounding
StakeActivationStatus::with_effective(700),
StakeActivationStatus::with_deactivating(700),
StakeActivationStatus::with_deactivating(275 + 1), // +1 is needed for rounding
StakeActivationStatus::default(),
];

// normal stake activating and deactivating transition test, just in case
Expand Down Expand Up @@ -2170,7 +2160,11 @@ mod tests {
};
assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)),
(expected_stake, expected_activating, expected_deactivating)
StakeActivationStatus {
effective: expected_stake,
activating: expected_activating,
deactivating: expected_deactivating,
},
);
}
}
Expand Down
15 changes: 9 additions & 6 deletions rpc/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ use {
message::{Message, SanitizedMessage},
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
stake::state::StakeState,
stake::state::{StakeActivationStatus, StakeState},
stake_history::StakeHistory,
system_instruction,
sysvar::stake_history,
Expand Down Expand Up @@ -1573,26 +1573,29 @@ impl JsonRpcRequestProcessor {
solana_sdk::account::from_account::<StakeHistory, _>(&stake_history_account)
.ok_or_else(Error::internal_error)?;

let (active, activating, deactivating) =
delegation.stake_activating_and_deactivating(epoch, Some(&stake_history));
let StakeActivationStatus {
effective,
activating,
deactivating,
} = delegation.stake_activating_and_deactivating(epoch, Some(&stake_history));
let stake_activation_state = if deactivating > 0 {
StakeActivationState::Deactivating
} else if activating > 0 {
StakeActivationState::Activating
} else if active > 0 {
} else if effective > 0 {
StakeActivationState::Active
} else {
StakeActivationState::Inactive
};
let inactive_stake = match stake_activation_state {
StakeActivationState::Activating => activating,
StakeActivationState::Active => 0,
StakeActivationState::Deactivating => delegation.stake.saturating_sub(active),
StakeActivationState::Deactivating => delegation.stake.saturating_sub(effective),
StakeActivationState::Inactive => delegation.stake,
};
Ok(RpcStakeActivation {
state: stake_activation_state,
active,
active: effective,
inactive: inactive_stake,
})
}
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/stakes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {
self,
state::{Delegation, StakeState},
},
sysvar::stake_history::StakeHistory,
stake_history::StakeHistory,
},
solana_stake_program::stake_state,
solana_vote_program::vote_state::VoteState,
Expand Down
Loading

0 comments on commit 0ddb34a

Please sign in to comment.