From 982c1c73756d35658bea8d9ee0a0bc32d21edfbb Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 18 Dec 2025 10:54:02 +0100 Subject: [PATCH] SIMD-0391: Feature-gate fixed-point stake warmup/cooldown math --- Cargo.lock | 3 +- Cargo.toml | 1 + account-decoder/src/parse_stake.rs | 14 +- cli/Cargo.toml | 2 +- cli/src/cluster_query.rs | 9 + cli/src/stake.rs | 28 +- dev-bins/Cargo.toml | 3 +- feature-set/src/lib.rs | 8 + genesis/Cargo.toml | 2 +- program-test/tests/warp.rs | 4 +- programs/sbf/Cargo.toml | 5 +- runtime/src/bank.rs | 19 +- .../partitioned_epoch_rewards/calculation.rs | 6 + runtime/src/bank/tests.rs | 12 +- runtime/src/inflation_rewards/mod.rs | 46 ++- runtime/src/inflation_rewards/points.rs | 19 +- runtime/src/lib.rs | 1 + runtime/src/stake_delegation.rs | 60 +++ runtime/src/stakes.rs | 360 +++++++++++++++--- runtime/src/stakes/serde_stakes.rs | 20 +- 20 files changed, 515 insertions(+), 107 deletions(-) create mode 100644 runtime/src/stake_delegation.rs diff --git a/Cargo.lock b/Cargo.lock index 6fc892292b5805..4412f7fd3dec18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10823,8 +10823,7 @@ dependencies = [ [[package]] name = "solana-stake-interface" version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9bc26191b533f9a6e5a14cca05174119819ced680a80febff2f5051a713f0db" +source = "git+https://github.com/solana-program/stake?rev=5f3fe65d94213f7514a0cc45662a5fe0cc2dbe15#5f3fe65d94213f7514a0cc45662a5fe0cc2dbe15" dependencies = [ "borsh", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 7d6652de4dfc02..949ee80d09841d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -674,6 +674,7 @@ opt-level = 3 [patch.crates-io] # for details, see https://github.com/anza-xyz/crossbeam/commit/fd279d707025f0e60951e429bf778b4813d1b6bf crossbeam-epoch = { git = "https://github.com/anza-xyz/crossbeam", rev = "fd279d707025f0e60951e429bf778b4813d1b6bf" } +solana-stake-interface = { git = "https://github.com/solana-program/stake", rev = "5f3fe65d94213f7514a0cc45662a5fe0cc2dbe15" } # We include the following crates as our dependencies above from crates.io: # diff --git a/account-decoder/src/parse_stake.rs b/account-decoder/src/parse_stake.rs index 08a19b25229ff8..94a9d771ff806c 100644 --- a/account-decoder/src/parse_stake.rs +++ b/account-decoder/src/parse_stake.rs @@ -6,7 +6,9 @@ use { bincode::deserialize, serde::{Deserialize, Serialize}, solana_clock::{Epoch, UnixTimestamp}, - solana_stake_interface::state::{Authorized, Delegation, Lockup, Meta, Stake, StakeStateV2}, + solana_stake_interface::{ + state::{Authorized, Delegation, Lockup, Meta, Stake, StakeStateV2}, + }, }; pub fn parse_stake(data: &[u8]) -> Result { @@ -118,22 +120,15 @@ pub struct UiDelegation { pub stake: StringAmount, pub activation_epoch: StringAmount, pub deactivation_epoch: StringAmount, - #[deprecated( - since = "1.16.7", - note = "Please use `solana_stake_interface::state::warmup_cooldown_rate()` instead" - )] - pub warmup_cooldown_rate: f64, } impl From for UiDelegation { fn from(delegation: Delegation) -> Self { - #[allow(deprecated)] Self { voter: delegation.voter_pubkey.to_string(), stake: delegation.stake.to_string(), activation_epoch: delegation.activation_epoch.to_string(), deactivation_epoch: delegation.deactivation_epoch.to_string(), - warmup_cooldown_rate: delegation.warmup_cooldown_rate, } } } @@ -194,7 +189,7 @@ mod test { stake: 20, activation_epoch: 2, deactivation_epoch: u64::MAX, - warmup_cooldown_rate: 0.25, + _reserved: [0; 8], }, credits_observed: 10, }; @@ -222,7 +217,6 @@ mod test { stake: 20.to_string(), activation_epoch: 2.to_string(), deactivation_epoch: u64::MAX.to_string(), - warmup_cooldown_rate: 0.25, }, credits_observed: 10, }) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8b899e9154aa3c..714f07878c74e7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -90,7 +90,7 @@ solana-sdk-ids = "=3.0.0" solana-signature = { version = "=3.1.0", default-features = false } solana-signer = "=3.0.0" solana-slot-history = "=3.0.0" -solana-stake-interface = "=2.0.2" +solana-stake-interface = { workspace = true } solana-system-interface = { version = "=2.0", features = ["bincode"] } solana-sysvar = "=3.1.1" solana-tps-client = { workspace = true } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 22daafc9123f01..02fde5ea3b0729 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -1997,6 +1997,13 @@ pub async fn process_show_stakes( &agave_feature_set::reduce_stake_warmup_cooldown::id(), ) .await?; + let fixed_point_activation_epoch = get_feature_activation_epoch( + rpc_client, + &agave_feature_set::stake_program_fixed_point_warmup_cooldown::id(), + ) + .await?; + let use_fixed_point_stake_math = fixed_point_activation_epoch + .is_some_and(|activation_epoch| clock.epoch >= activation_epoch); stake_account_progress_bar.finish_and_clear(); let mut stake_accounts: Vec = vec![]; @@ -2018,6 +2025,7 @@ pub async fn process_show_stakes( &stake_history, &clock, new_rate_activation_epoch, + use_fixed_point_stake_math, false, ), }); @@ -2036,6 +2044,7 @@ pub async fn process_show_stakes( &stake_history, &clock, new_rate_activation_epoch, + use_fixed_point_stake_math, false, ), }); diff --git a/cli/src/stake.rs b/cli/src/stake.rs index d15d4ddfb92ab7..253b5374743cec 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -2436,6 +2436,7 @@ pub fn build_stake_state( stake_history: &StakeHistory, clock: &Clock, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, use_csv: bool, ) -> CliStakeState { match stake_state { @@ -2453,11 +2454,20 @@ pub fn build_stake_state( effective, activating, deactivating, - } = stake.delegation.stake_activating_and_deactivating( - current_epoch, - stake_history, - new_rate_activation_epoch, - ); + } = if use_fixed_point_stake_math { + stake.delegation.stake_activating_and_deactivating_v2( + current_epoch, + stake_history, + new_rate_activation_epoch, + ) + } else { + #[allow(deprecated)] + stake.delegation.stake_activating_and_deactivating( + current_epoch, + stake_history, + new_rate_activation_epoch, + ) + }; let lockup = if lockup.is_in_force(clock, None) { Some(lockup.into()) } else { @@ -2723,6 +2733,13 @@ pub async fn get_account_stake_state( &agave_feature_set::reduce_stake_warmup_cooldown::id(), ) .await?; + let fixed_point_activation_epoch = get_feature_activation_epoch( + rpc_client, + &agave_feature_set::stake_program_fixed_point_warmup_cooldown::id(), + ) + .await?; + let use_fixed_point_stake_math = fixed_point_activation_epoch + .is_some_and(|activation_epoch| clock.epoch >= activation_epoch); let mut state = build_stake_state( stake_account.lamports, @@ -2731,6 +2748,7 @@ pub async fn get_account_stake_state( &stake_history, &clock, new_rate_activation_epoch, + use_fixed_point_stake_math, use_csv, ); diff --git a/dev-bins/Cargo.toml b/dev-bins/Cargo.toml index b32bc73e943f00..696771fe56a6de 100644 --- a/dev-bins/Cargo.toml +++ b/dev-bins/Cargo.toml @@ -139,7 +139,7 @@ solana-sdk-ids = "3.0.0" solana-shred-version = "3.0.0" solana-signature = { version = "3.1.0", default-features = false } solana-signer = "3.0.0" -solana-stake-interface = "2.0.2" +solana-stake-interface = { workspace = true } solana-storage-bigtable = { path = "../storage-bigtable", version = "=4.0.0-alpha.0", features = ["agave-unstable-api"] } solana-streamer = { path = "../streamer", version = "=4.0.0-alpha.0", features = ["agave-unstable-api"] } solana-svm-callback = { path = "../svm-callback", version = "=4.0.0-alpha.0", features = ["agave-unstable-api"] } @@ -223,3 +223,4 @@ opt-level = 3 # There is a similar override in `programs/sbf/Cargo.toml`. Please keep both # comments and the overrides in sync. solana-curve25519 = { path = "../curves/curve25519" } +solana-stake-interface = { git = "https://github.com/solana-program/stake", rev = "5f3fe65d94213f7514a0cc45662a5fe0cc2dbe15" } diff --git a/feature-set/src/lib.rs b/feature-set/src/lib.rs index eeec75f5902950..140fa3f42a9a0d 100644 --- a/feature-set/src/lib.rs +++ b/feature-set/src/lib.rs @@ -829,6 +829,10 @@ pub mod reduce_stake_warmup_cooldown { solana_pubkey::declare_id!("GwtDQBghCTBgmX2cpEGNPxTEBUTQRaDMGTr5qychdGMj"); } +pub mod stake_program_fixed_point_warmup_cooldown { + solana_pubkey::declare_id!("FvA7BcL3RG2wL5QbDoRhFKi5o9nSxoarQrsLx6EHTcmC"); +} + pub mod revise_turbine_epoch_stakes { solana_pubkey::declare_id!("BTWmtJC8U5ZLMbBUUA1k6As62sYjPEjAiNAT55xYGdJU"); } @@ -1795,6 +1799,10 @@ pub static FEATURE_NAMES: LazyLock> = LazyLock::n reduce_stake_warmup_cooldown::id(), "reduce stake warmup cooldown from 25% to 9%", ), + ( + stake_program_fixed_point_warmup_cooldown::id(), + "SIMD-0391: use fixed-point stake warmup/cooldown math", + ), ( revise_turbine_epoch_stakes::id(), "revise turbine epoch stakes", diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index df9935ccdbb4e7..492f32cf7413a0 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -60,7 +60,7 @@ solana-rpc-client-api = { workspace = true } solana-runtime = { workspace = true } solana-sdk-ids = "=3.0.0" solana-signer = "=3.0.0" -solana-stake-interface = { version = "=2.0.2", features = ["borsh"] } +solana-stake-interface = { workspace = true, features = ["borsh"] } solana-time-utils = "3.0.0" solana-version = { workspace = true } solana-vote-program = { workspace = true } diff --git a/program-test/tests/warp.rs b/program-test/tests/warp.rs index 564d4c1f2ce14b..35201ce0cbdc4d 100644 --- a/program-test/tests/warp.rs +++ b/program-test/tests/warp.rs @@ -202,7 +202,7 @@ async fn stake_rewards_from_warp() { assert_eq!( stake .delegation - .stake_activating_and_deactivating(clock.epoch, &stake_history, None), + .stake_activating_and_deactivating_v2(clock.epoch, &stake_history, None), StakeActivationStatus::with_effective(stake.delegation.stake), ); } @@ -325,7 +325,7 @@ async fn stake_rewards_filter_bench_core(num_stake_accounts: u64) { assert_eq!( stake .delegation - .stake_activating_and_deactivating(clock.epoch, &stake_history, None), + .stake_activating_and_deactivating_v2(clock.epoch, &stake_history, None), StakeActivationStatus::with_effective(stake.delegation.stake), ); } diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 84786a5486b4d2..dcb4cf53284f6f 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -158,7 +158,7 @@ solana-sbpf = "=0.13.1" solana-sdk-ids = "=3.0.0" solana-secp256k1-recover = "=3.1.0" solana-sha256-hasher = { version = "=3.1.0", features = ["sha2"] } -solana-stake-interface = { version = "=2.0.2", features = ["bincode"] } +solana-stake-interface = { workspace = true, features = ["bincode"] } solana-svm = { path = "../../svm", version = "=4.0.0-alpha.0" } solana-svm-callback = { path = "../../svm-callback", version = "=4.0.0-alpha.0" } solana-svm-feature-set = { path = "../../svm-feature-set", version = "=4.0.0-alpha.0" } @@ -239,7 +239,7 @@ solana-sbf-rust-realloc-invoke-dep = { workspace = true } solana-sbpf = { workspace = true, features = ["jit"] } solana-sdk-ids = "3.0.0" solana-signer = "3.0.0" -solana-stake-interface = "2.0.2" +solana-stake-interface = { workspace = true } solana-svm = { workspace = true } solana-svm-callback = { workspace = true } solana-svm-feature-set = { workspace = true } @@ -310,3 +310,4 @@ name = "bpf_loader" # There is a similar override in `../../Cargo.toml`. Please keep both comments # and the overrides in sync. solana-curve25519 = { path = "../../curves/curve25519" } +solana-stake-interface = { git = "https://github.com/solana-program/stake", rev = "5f3fe65d94213f7514a0cc45662a5fe0cc2dbe15" } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 006b88dcd8c152..be7c0afc120a20 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1630,6 +1630,11 @@ impl Bank { } } + fn use_fixed_point_stake_math(&self) -> bool { + self.feature_set + .is_active(&agave_feature_set::stake_program_fixed_point_warmup_cooldown::id()) + } + /// Returns updated stake history and vote accounts that includes new /// activated stake from the last epoch. fn compute_new_epoch_caches_and_rewards( @@ -1649,7 +1654,8 @@ impl Bank { self.epoch(), thread_pool, self.new_warmup_cooldown_rate_epoch(), - &stake_delegations + &stake_delegations, + self.use_fixed_point_stake_math(), )); // Apply stake rewards and commission using new snapshots. @@ -4069,6 +4075,7 @@ impl Bank { assert!(!self.freeze_started()); let mut m = Measure::start("stakes_cache.check_and_store"); let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch(); + let use_fixed_point_stake_math = self.use_fixed_point_stake_math(); (0..accounts.len()).for_each(|i| { accounts.account(i, |account| { @@ -4076,6 +4083,7 @@ impl Bank { account.pubkey(), &account, new_warmup_cooldown_rate_epoch, + use_fixed_point_stake_math, ) }) }); @@ -5020,6 +5028,7 @@ impl Bank { ) { debug_assert_eq!(txs.len(), processing_results.len()); let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch(); + let use_fixed_point_stake_math = self.use_fixed_point_stake_math(); txs.iter() .zip(processing_results) .filter_map(|(tx, processing_result)| { @@ -5041,8 +5050,12 @@ impl Bank { .for_each(|(pubkey, account)| { // note that this could get timed to: self.rc.accounts.accounts_db.stats.stakes_cache_check_and_store_us, // but this code path is captured separately in ExecuteTimingType::UpdateStakesCacheUs - self.stakes_cache - .check_and_store(pubkey, account, new_warmup_cooldown_rate_epoch); + self.stakes_cache.check_and_store( + pubkey, + account, + new_warmup_cooldown_rate_epoch, + use_fixed_point_stake_math, + ); }); } diff --git a/runtime/src/bank/partitioned_epoch_rewards/calculation.rs b/runtime/src/bank/partitioned_epoch_rewards/calculation.rs index 10919250aa0f40..ac15de1e1d0c39 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/calculation.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/calculation.rs @@ -441,6 +441,7 @@ impl Bank { reward_calc_tracer: Option, new_rate_activation_epoch: Option, delay_commission_updates: bool, + use_fixed_point_stake_math: bool, ) -> Option { // curry closure to add the contextual stake_pubkey let reward_calc_tracer = reward_calc_tracer.as_ref().map(|outer| { @@ -488,6 +489,7 @@ impl Bank { stake_history, reward_calc_tracer, new_rate_activation_epoch, + use_fixed_point_stake_math, ) { Ok((stake_reward, vote_rewards, stake)) => { let stake_reward = PartitionedStakeReward { @@ -532,6 +534,7 @@ impl Bank { let delay_commission_updates = self .feature_set .is_active(&agave_feature_set::delay_commission_updates::id()); + let use_fixed_point_stake_math = self.use_fixed_point_stake_math(); let mut measure_redeem_rewards = Measure::start("redeem-rewards"); // For N stake delegations, where N is >1,000,000, we produce: @@ -562,6 +565,7 @@ impl Bank { reward_calc_tracer.as_ref(), new_warmup_cooldown_rate_epoch, delay_commission_updates, + use_fixed_point_stake_math, ) }); let (stake_reward, maybe_reward_record) = match maybe_reward_record { @@ -642,6 +646,7 @@ impl Bank { let solana_vote_program: Pubkey = solana_vote_program::id(); let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch(); + let use_fixed_point_stake_math = self.use_fixed_point_stake_math(); let (points, measure_us) = measure_us!(thread_pool.install(|| { stake_delegations .par_iter() @@ -662,6 +667,7 @@ impl Bank { DelegatedVoteState::from(vote_account.vote_state_view()), stake_history, new_warmup_cooldown_rate_epoch, + use_fixed_point_stake_math, ) .unwrap_or(0) }) diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 62caf8442067dd..e8fbc7184ab0e8 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -14,6 +14,7 @@ use { create_lockup_stake_account, genesis_sysvar_and_builtin_program_lamports, GenesisConfigInfo, ValidatorVoteKeypairs, }, + stake_delegation::stake_effective, stake_history::StakeHistory, stake_utils, stakes::InvalidCacheEntryReason, @@ -2960,8 +2961,9 @@ fn test_bank_update_sysvar_account() { } } -#[test] -fn test_bank_epoch_vote_accounts() { +#[test_case(false; "legacy_float")] +#[test_case(true; "fixed_point")] +fn test_bank_epoch_vote_accounts(use_fixed_point_stake_math: bool) { let leader_pubkey = solana_pubkey::new_rand(); let leader_lamports = 3; let mut genesis_config = @@ -3015,7 +3017,7 @@ fn test_bank_epoch_vote_accounts() { // epoch_stakes are a snapshot at the leader_schedule_slot_offset boundary // in the prior epoch (0 in this case) assert_eq!( - leader_stake.stake(0, &StakeHistory::default(), None), + stake_effective(&leader_stake, 0, &StakeHistory::default(), None, use_fixed_point_stake_math), vote_accounts.unwrap().get(&leader_vote_account).unwrap().0 ); @@ -3031,7 +3033,7 @@ fn test_bank_epoch_vote_accounts() { assert!(child.epoch_vote_accounts(epoch).is_some()); assert_eq!( - leader_stake.stake(child.epoch(), &StakeHistory::default(), None), + stake_effective(&leader_stake, child.epoch(), &StakeHistory::default(), None, use_fixed_point_stake_math), child .epoch_vote_accounts(epoch) .unwrap() @@ -3049,7 +3051,7 @@ fn test_bank_epoch_vote_accounts() { ); assert!(child.epoch_vote_accounts(epoch).is_some()); assert_eq!( - leader_stake.stake(child.epoch(), &StakeHistory::default(), None), + stake_effective(&leader_stake, child.epoch(), &StakeHistory::default(), None, use_fixed_point_stake_math), child .epoch_vote_accounts(epoch) .unwrap() diff --git a/runtime/src/inflation_rewards/mod.rs b/runtime/src/inflation_rewards/mod.rs index 3b74a04effd98b..4f2199467be374 100644 --- a/runtime/src/inflation_rewards/mod.rs +++ b/runtime/src/inflation_rewards/mod.rs @@ -5,6 +5,7 @@ use { calculate_stake_points_and_credits, CalculatedStakePoints, DelegatedVoteState, InflationPointCalculationEvent, PointValue, SkippedReason, }, + crate::stake_delegation::stake_effective, solana_clock::Epoch, solana_instruction::error::InstructionError, solana_stake_interface::{ @@ -37,14 +38,17 @@ pub(crate) fn redeem_rewards( stake_history: &StakeHistory, inflation_point_calc_tracer: Option, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> Result<(u64, u64, Stake), InstructionError> { if let StakeStateV2::Stake(meta, stake, _stake_flags) = stake_state { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { inflation_point_calc_tracer( - &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake( + &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake_effective( + stake, rewarded_epoch, stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, )), ); inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve( @@ -65,6 +69,7 @@ pub(crate) fn redeem_rewards( stake_history, inflation_point_calc_tracer, new_rate_activation_epoch, + use_fixed_point_stake_math, ) { Ok((stakers_reward, voters_reward, stake)) } else { @@ -84,6 +89,7 @@ fn redeem_stake_rewards( stake_history: &StakeHistory, inflation_point_calc_tracer: Option, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> Option<(u64, u64)> { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( @@ -100,6 +106,7 @@ fn redeem_stake_rewards( stake_history, inflation_point_calc_tracer.as_ref(), new_rate_activation_epoch, + use_fixed_point_stake_math, ) .map(|calculated_stake_rewards| { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { @@ -133,6 +140,7 @@ fn calculate_stake_rewards( stake_history: &StakeHistory, inflation_point_calc_tracer: Option, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> Option { // ensure to run to trigger (optional) inflation_point_calc_tracer let CalculatedStakePoints { @@ -145,6 +153,7 @@ fn calculate_stake_rewards( stake_history, inflation_point_calc_tracer.as_ref(), new_rate_activation_epoch, + use_fixed_point_stake_math, ); // Drive credits_observed forward unconditionally when rewards are disabled @@ -285,8 +294,9 @@ mod tests { } } - #[test] - fn test_stake_state_redeem_rewards() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stake_state_redeem_rewards(use_fixed_point: bool) { let mut vote_state = VoteStateV4::default(); // assume stake.stake() is right // bootstrap means fully-vested stake at epoch 0 @@ -308,6 +318,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -330,6 +341,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -340,8 +352,9 @@ mod tests { assert_eq!(stake.credits_observed, 2); } - #[test] - fn test_stake_state_calculate_rewards() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stake_state_calculate_rewards(use_fixed_point: bool) { let mut vote_state = VoteStateV4::default(); // assume stake.stake() is right // bootstrap means fully-vested stake at epoch 0 @@ -362,6 +375,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -388,6 +402,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -411,6 +426,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -437,6 +453,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -461,6 +478,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -487,6 +505,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -507,6 +526,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); vote_state.inflation_rewards_commission_bps = 9900; @@ -524,6 +544,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -548,6 +569,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -572,6 +594,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -586,7 +609,8 @@ mod tests { DelegatedVoteState::from(&vote_state), &StakeHistory::default(), null_tracer(), - None + None, + use_fixed_point, ) ); @@ -605,7 +629,8 @@ mod tests { DelegatedVoteState::from(&vote_state), &StakeHistory::default(), null_tracer(), - None + None, + use_fixed_point, ) ); // this is new behavior 2; don't hint when credits both from stake and vote are identical @@ -621,7 +646,8 @@ mod tests { DelegatedVoteState::from(&vote_state), &StakeHistory::default(), null_tracer(), - None + None, + use_fixed_point, ) ); @@ -647,6 +673,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); @@ -672,6 +699,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + use_fixed_point, ) ); } @@ -694,6 +722,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + false, ); } @@ -725,6 +754,7 @@ mod tests { &StakeHistory::default(), null_tracer(), None, + false, ) ); } diff --git a/runtime/src/inflation_rewards/points.rs b/runtime/src/inflation_rewards/points.rs index fde5e051fdad49..3bf28217d99522 100644 --- a/runtime/src/inflation_rewards/points.rs +++ b/runtime/src/inflation_rewards/points.rs @@ -1,6 +1,7 @@ //! Information about points calculation based on stake state. use { + crate::stake_delegation::stake_effective, solana_clock::Epoch, solana_instruction::error::InstructionError, solana_pubkey::Pubkey, @@ -85,6 +86,7 @@ pub(crate) fn calculate_points( vote_state: DelegatedVoteState, stake_history: &StakeHistory, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> Result { if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state { Ok(calculate_stake_points( @@ -93,6 +95,7 @@ pub(crate) fn calculate_points( stake_history, null_tracer(), new_rate_activation_epoch, + use_fixed_point_stake_math, )) } else { Err(InstructionError::InvalidAccountData) @@ -105,6 +108,7 @@ fn calculate_stake_points( stake_history: &StakeHistory, inflation_point_calc_tracer: Option, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> u128 { calculate_stake_points_and_credits( stake, @@ -112,6 +116,7 @@ fn calculate_stake_points( stake_history, inflation_point_calc_tracer, new_rate_activation_epoch, + use_fixed_point_stake_math, ) .points } @@ -125,6 +130,7 @@ pub(crate) fn calculate_stake_points_and_credits( stake_history: &StakeHistory, inflation_point_calc_tracer: Option, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> CalculatedStakePoints { let credits_in_stake = stake.credits_observed; let credits_in_vote = vote_state.credits; @@ -177,10 +183,12 @@ pub(crate) fn calculate_stake_points_and_credits( for epoch_credits_item in vote_state.epoch_credits_iter { let (epoch, final_epoch_credits, initial_epoch_credits) = epoch_credits_item; - let stake_amount = u128::from(stake.delegation.stake( + let stake_amount = u128::from(stake_effective( + stake, epoch, stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, )); // figure out how much this stake has seen that @@ -228,6 +236,7 @@ mod tests { super::*, solana_native_token::LAMPORTS_PER_SOL, solana_vote_program::vote_state::{handler::VoteStateHandle, VoteStateV4}, + test_case::test_case, }; impl<'a> From<&'a VoteStateV4> for DelegatedVoteState<'a> { @@ -251,8 +260,9 @@ mod tests { } } - #[test] - fn test_stake_state_calculate_points_with_typical_values() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stake_state_calculate_points_with_typical_values(use_fixed_point: bool) { let mut vote_state = VoteStateV4::default(); // bootstrap means fully-vested stake at epoch 0 with @@ -279,7 +289,8 @@ mod tests { DelegatedVoteState::from(&vote_state), &StakeHistory::default(), null_tracer(), - None + None, + use_fixed_point, ) ); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ad5f76c3555f65..09111e026ba07a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -37,6 +37,7 @@ pub mod snapshot_minimizer; pub mod snapshot_package; pub mod snapshot_utils; mod stake_account; +pub mod stake_delegation; pub mod stake_history; pub mod stake_utils; pub mod stake_weighted_timestamp; diff --git a/runtime/src/stake_delegation.rs b/runtime/src/stake_delegation.rs new file mode 100644 index 00000000000000..775c4725ec0b63 --- /dev/null +++ b/runtime/src/stake_delegation.rs @@ -0,0 +1,60 @@ +//! Temporary dispatch helpers for stake delegation math. +//! +//! Selects between legacy floating-point and fixed-point implementations +//! based on a caller-provided feature flag. + +use { + solana_clock::Epoch, + solana_stake_interface::{ + stake_history::StakeHistoryGetEntry, + state::{Delegation, Stake, StakeActivationStatus}, + }, +}; + +#[inline] +pub fn delegation_effective( + delegation: &Delegation, + epoch: Epoch, + history: &T, + new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, +) -> u64 { + if use_fixed_point_stake_math { + delegation.stake_v2(epoch, history, new_rate_activation_epoch) + } else { + #[allow(deprecated)] + delegation.stake(epoch, history, new_rate_activation_epoch) + } +} + +#[inline] +pub fn delegation_status( + delegation: &Delegation, + epoch: Epoch, + history: &T, + new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, +) -> StakeActivationStatus { + if use_fixed_point_stake_math { + delegation.stake_activating_and_deactivating_v2(epoch, history, new_rate_activation_epoch) + } else { + #[allow(deprecated)] + delegation.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch) + } +} + +#[inline] +pub fn stake_effective( + stake: &Stake, + epoch: Epoch, + history: &T, + new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, +) -> u64 { + if use_fixed_point_stake_math { + stake.stake_v2(epoch, history, new_rate_activation_epoch) + } else { + #[allow(deprecated)] + stake.stake(epoch, history, new_rate_activation_epoch) + } +} diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index d1a25b157ba032..74b98e5e92e6c0 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -3,7 +3,11 @@ #[cfg(feature = "dev-context-only-utils")] use solana_stake_interface::state::Stake; use { - crate::{stake_account, stake_history::StakeHistory}, + crate::{ + stake_account, + stake_delegation::{delegation_effective, delegation_status}, + stake_history::StakeHistory, + }, im::HashMap as ImHashMap, log::error, num_derive::ToPrimitive, @@ -74,6 +78,7 @@ impl StakesCache { pubkey: &Pubkey, account: &impl ReadableAccount, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) { // TODO: If the account is already cached as a vote or stake account // but the owner changes, then this needs to evict the account from @@ -90,7 +95,11 @@ impl StakesCache { }; } else if stake_program::check_id(owner) { let mut stakes = self.0.write().unwrap(); - stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch); + stakes.remove_stake_delegation( + pubkey, + new_rate_activation_epoch, + use_fixed_point_stake_math, + ); } return; } @@ -106,6 +115,7 @@ impl StakesCache { pubkey, vote_account, new_rate_activation_epoch, + use_fixed_point_stake_math, ) }; } @@ -132,11 +142,16 @@ impl StakesCache { *pubkey, stake_account, new_rate_activation_epoch, + use_fixed_point_stake_math, ); } Err(_) => { let mut stakes = self.0.write().unwrap(); - stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch); + stakes.remove_stake_delegation( + pubkey, + new_rate_activation_epoch, + use_fixed_point_stake_math, + ); } } } @@ -288,6 +303,7 @@ impl Stakes { thread_pool: &ThreadPool, new_rate_activation_epoch: Option, stake_delegations: &[(&Pubkey, &StakeAccount)], + use_fixed_point_stake_math: bool, ) -> (StakeHistory, VoteAccounts) { // Wrap up the prev epoch by adding new stake history entry for the // prev epoch. @@ -298,10 +314,12 @@ impl Stakes { StakeActivationStatus::default, |acc, (_stake_pubkey, stake_account)| { let delegation = stake_account.delegation(); - acc + delegation.stake_activating_and_deactivating( + acc + delegation_status( + delegation, self.epoch, &self.stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, ) }, ) @@ -318,6 +336,7 @@ impl Stakes { stake_delegations, &stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, ); (stake_history, vote_accounts) } @@ -340,12 +359,21 @@ impl Stakes { epoch: Epoch, stake_history: &StakeHistory, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> u64 { stake_delegations .values() .map(StakeAccount::delegation) .filter(|delegation| &delegation.voter_pubkey == voter_pubkey) - .map(|delegation| delegation.stake(epoch, stake_history, new_rate_activation_epoch)) + .map(|delegation| { + delegation_effective( + delegation, + epoch, + stake_history, + new_rate_activation_epoch, + use_fixed_point_stake_math, + ) + }) .sum() } @@ -357,13 +385,16 @@ impl Stakes { &mut self, stake_pubkey: &Pubkey, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) { if let Some(stake_account) = self.stake_delegations.remove(stake_pubkey) { let removed_delegation = stake_account.delegation(); - let removed_stake = removed_delegation.stake( + let removed_stake = delegation_effective( + removed_delegation, self.epoch, &self.stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, ); self.vote_accounts .sub_stake(&removed_delegation.voter_pubkey, removed_stake); @@ -375,6 +406,7 @@ impl Stakes { vote_pubkey: &Pubkey, vote_account: VoteAccount, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> Option { debug_assert_ne!(vote_account.lamports(), 0u64); @@ -386,6 +418,7 @@ impl Stakes { self.epoch, &self.stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, ) }) } @@ -395,20 +428,29 @@ impl Stakes { stake_pubkey: Pubkey, stake_account: StakeAccount, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) { debug_assert_ne!(stake_account.lamports(), 0u64); let delegation = stake_account.delegation(); let voter_pubkey = delegation.voter_pubkey; - let stake = delegation.stake(self.epoch, &self.stake_history, new_rate_activation_epoch); + let stake = delegation_effective( + delegation, + self.epoch, + &self.stake_history, + new_rate_activation_epoch, + use_fixed_point_stake_math, + ); match self.stake_delegations.insert(stake_pubkey, stake_account) { None => self.vote_accounts.add_stake(&voter_pubkey, stake), Some(old_stake_account) => { let old_delegation = old_stake_account.delegation(); let old_voter_pubkey = old_delegation.voter_pubkey; - let old_stake = old_delegation.stake( + let old_stake = delegation_effective( + old_delegation, self.epoch, &self.stake_history, new_rate_activation_epoch, + use_fixed_point_stake_math, ); if voter_pubkey != old_voter_pubkey || stake != old_stake { self.vote_accounts.sub_stake(&old_voter_pubkey, old_stake); @@ -522,6 +564,7 @@ fn refresh_vote_accounts( stake_delegations: &[(&Pubkey, &StakeAccount)], stake_history: &StakeHistory, new_rate_activation_epoch: Option, + use_fixed_point_stake_math: bool, ) -> VoteAccounts { type StakesHashMap = HashMap; fn merge(mut stakes: StakesHashMap, other: StakesHashMap) -> StakesHashMap { @@ -541,7 +584,13 @@ fn refresh_vote_accounts( |mut delegated_stakes, (_stake_pubkey, stake_account)| { let delegation = stake_account.delegation(); let entry = delegated_stakes.entry(delegation.voter_pubkey).or_default(); - *entry += delegation.stake(epoch, stake_history, new_rate_activation_epoch); + *entry += delegation_effective( + delegation, + epoch, + stake_history, + new_rate_activation_epoch, + use_fixed_point_stake_math, + ); delegated_stakes }, ) @@ -563,7 +612,7 @@ fn refresh_vote_accounts( pub(crate) mod tests { use { super::*, - crate::stake_utils, + crate::{stake_delegation::stake_effective, stake_utils}, rayon::ThreadPoolBuilder, solana_account::WritableAccount, solana_pubkey::Pubkey, @@ -571,6 +620,7 @@ pub(crate) mod tests { solana_stake_interface::{self as stake, state::StakeStateV2}, solana_vote_interface::state::VoteStateV4, solana_vote_program::vote_state, + test_case::test_case, }; // set up some dummies for a staked node (( vote ) ( stake )) @@ -620,8 +670,9 @@ pub(crate) mod tests { ) } - #[test] - fn test_stakes_basic() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stakes_basic(use_fixed_point_stake_math: bool) { for i in 0..4 { let stakes_cache = StakesCache::new(Stakes { epoch: i, @@ -631,8 +682,19 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); + let stake = stake_account .deserialize_data::() .unwrap() @@ -644,26 +706,48 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(i, &StakeHistory::default(), None) + stake_effective( + &stake, + i, + &StakeHistory::default(), + None, + use_fixed_point_stake_math + ), ); } stake_account.set_lamports(42); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); let vote_accounts = stakes.vote_accounts(); assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(i, &StakeHistory::default(), None) + stake_effective( + &stake, + i, + &StakeHistory::default(), + None, + use_fixed_point_stake_math + ), ); // stays old stake, because only 10 is activated } // activate more let mut stake_account = create_stake_account(42, &vote_pubkey, &solana_pubkey::new_rand()); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); let stake = stake_account .deserialize_data::() .unwrap() @@ -675,12 +759,23 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(i, &StakeHistory::default(), None) + stake_effective( + &stake, + i, + &StakeHistory::default(), + None, + use_fixed_point_stake_math + ), ); // now stake of 42 is activated } stake_account.set_lamports(0); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); let vote_accounts = stakes.vote_accounts(); @@ -690,8 +785,9 @@ pub(crate) mod tests { } } - #[test] - fn test_stakes_highest() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stakes_highest(use_fixed_point_stake_math: bool) { let stakes_cache = StakesCache::default(); assert_eq!(stakes_cache.stakes().highest_staked_node(), None); @@ -699,14 +795,34 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) = create_staked_node_accounts(20); - stakes_cache.check_and_store(&vote11_pubkey, &vote11_account, None); - stakes_cache.check_and_store(&stake11_pubkey, &stake11_account, None); + stakes_cache.check_and_store( + &vote11_pubkey, + &vote11_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake11_pubkey, + &stake11_account, + None, + use_fixed_point_stake_math, + ); let vote11_node_pubkey = VoteStateV4::deserialize(vote11_account.data(), &vote11_pubkey) .unwrap() @@ -716,8 +832,9 @@ pub(crate) mod tests { assert_eq!(highest_staked_node, Some(vote11_node_pubkey)); } - #[test] - fn test_stakes_vote_account_disappear_reappear() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stakes_vote_account_disappear_reappear(use_fixed_point_stake_math: bool) { let stakes_cache = StakesCache::new(Stakes { epoch: 4, ..Stakes::default() @@ -726,8 +843,18 @@ pub(crate) mod tests { let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -737,7 +864,12 @@ pub(crate) mod tests { } vote_account.set_lamports(0); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -747,7 +879,12 @@ pub(crate) mod tests { } vote_account.set_lamports(1); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -761,7 +898,12 @@ pub(crate) mod tests { let mut pushed = vote_account.data().to_vec(); pushed.push(0); vote_account.set_data(pushed); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -772,7 +914,12 @@ pub(crate) mod tests { // Vote account uninitialized vote_account.set_data(vec![0; VoteStateV4::size_of()]); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -782,7 +929,12 @@ pub(crate) mod tests { } vote_account.set_data(cache_data); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -792,8 +944,9 @@ pub(crate) mod tests { } } - #[test] - fn test_stakes_change_delegate() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stakes_change_delegate(use_fixed_point_stake_math: bool) { let stakes_cache = StakesCache::new(Stakes { epoch: 4, ..Stakes::default() @@ -805,11 +958,26 @@ pub(crate) mod tests { let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - stakes_cache.check_and_store(&vote_pubkey2, &vote_account2, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &vote_pubkey2, + &vote_account2, + None, + use_fixed_point_stake_math, + ); // delegates to vote_pubkey - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); let stake = stake_account .deserialize_data::() @@ -823,14 +991,25 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(stakes.epoch, &stakes.stake_history, None) + stake_effective( + &stake, + stakes.epoch, + &stakes.stake_history, + None, + use_fixed_point_stake_math + ), ); assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey2), 0); } // delegates to vote_pubkey2 - stakes_cache.check_and_store(&stake_pubkey, &stake_account2, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account2, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -840,12 +1019,19 @@ pub(crate) mod tests { assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey2), - stake.stake(stakes.epoch, &stakes.stake_history, None) + stake_effective( + &stake, + stakes.epoch, + &stakes.stake_history, + None, + use_fixed_point_stake_math + ), ); } } - #[test] - fn test_stakes_multiple_stakers() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stakes_multiple_stakers(use_fixed_point_stake_math: bool) { let stakes_cache = StakesCache::new(Stakes { epoch: 4, ..Stakes::default() @@ -857,11 +1043,26 @@ pub(crate) mod tests { let stake_pubkey2 = solana_pubkey::new_rand(); let stake_account2 = create_stake_account(10, &vote_pubkey, &stake_pubkey2); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); // delegates to vote_pubkey - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); - stakes_cache.check_and_store(&stake_pubkey2, &stake_account2, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake_pubkey2, + &stake_account2, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -871,15 +1072,26 @@ pub(crate) mod tests { } } - #[test] - fn test_activate_epoch() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_activate_epoch(use_fixed_point_stake_math: bool) { let stakes_cache = StakesCache::default(); let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); let stake = stake_account .deserialize_data::() .unwrap() @@ -891,7 +1103,13 @@ pub(crate) mod tests { let vote_accounts = stakes.vote_accounts(); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(stakes.epoch, &stakes.stake_history, None) + stake_effective( + &stake, + stakes.epoch, + &stakes.stake_history, + None, + use_fixed_point_stake_math + ), ); } let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap(); @@ -899,7 +1117,13 @@ pub(crate) mod tests { let (stake_history, vote_accounts) = { let stakes = stakes_cache.stakes(); let stake_delegations = stakes.stake_delegations_vec(); - stakes.calculate_activated_stake(next_epoch, &thread_pool, None, &stake_delegations) + stakes.calculate_activated_stake( + next_epoch, + &thread_pool, + None, + &stake_delegations, + use_fixed_point_stake_math, + ) }; stakes_cache.activate_epoch(next_epoch, stake_history, vote_accounts); { @@ -907,13 +1131,20 @@ pub(crate) mod tests { let vote_accounts = stakes.vote_accounts(); assert_eq!( vote_accounts.get_delegated_stake(&vote_pubkey), - stake.stake(stakes.epoch, &stakes.stake_history, None) + stake_effective( + &stake, + stakes.epoch, + &stakes.stake_history, + None, + use_fixed_point_stake_math + ), ); } } - #[test] - fn test_stakes_not_delegate() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_stakes_not_delegate(use_fixed_point_stake_math: bool) { let stakes_cache = StakesCache::new(Stakes { epoch: 4, ..Stakes::default() @@ -922,8 +1153,18 @@ pub(crate) mod tests { let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = create_staked_node_accounts(10); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); { let stakes = stakes_cache.stakes(); @@ -937,6 +1178,7 @@ pub(crate) mod tests { &stake_pubkey, &AccountSharedData::new(1, 0, &stake::program::id()), None, + use_fixed_point_stake_math, ); { let stakes = stakes_cache.stakes(); diff --git a/runtime/src/stakes/serde_stakes.rs b/runtime/src/stakes/serde_stakes.rs index 2f3fea1a80c984..64f7dfe24b3986 100644 --- a/runtime/src/stakes/serde_stakes.rs +++ b/runtime/src/stakes/serde_stakes.rs @@ -193,6 +193,7 @@ mod tests { solana_rent::Rent, solana_stake_interface::state::Delegation, solana_vote_program::vote_state, + test_case::test_case, }; #[test] @@ -234,8 +235,9 @@ mod tests { assert_eq!(expected_stake_stakes, stake_stakes); } - #[test] - fn test_serde_stakes_to_delegation_format() { + #[test_case(false; "legacy_float")] + #[test_case(true; "fixed_point")] + fn test_serde_stakes_to_delegation_format(use_fixed_point_stake_math: bool) { #[derive(Debug, Serialize)] struct SerializableDummy { head: String, @@ -280,7 +282,12 @@ mod tests { commission_bps, rng.random_range(0..1_000_000), // lamports ); - stakes_cache.check_and_store(&vote_pubkey, &vote_account, None); + stakes_cache.check_and_store( + &vote_pubkey, + &vote_account, + None, + use_fixed_point_stake_math, + ); for _ in 0..rng.random_range(10usize..20) { let stake_pubkey = solana_pubkey::new_rand(); let rent = Rent::with_slots_per_epoch(rng.random()); @@ -291,7 +298,12 @@ mod tests { &rent, rng.random_range(0..1_000_000), // lamports ); - stakes_cache.check_and_store(&stake_pubkey, &stake_account, None); + stakes_cache.check_and_store( + &stake_pubkey, + &stake_account, + None, + use_fixed_point_stake_math, + ); } } let stakes: Stakes = stakes_cache.stakes().clone();