stake: deprecate on chain warmup/cooldown rate and config (#32723)

* stake: deprecate on chain warmup/cooldown rate and config

* Pr feedback: Deprecate since 1.16.7

Co-authored-by: Jon Cinque <me@jonc.dev>

---------

Co-authored-by: Jon Cinque <me@jonc.dev>
This commit is contained in:
Ashwin Sekar 2023-08-07 13:23:24 -07:00 committed by GitHub
parent 5c86f89bc7
commit fa3506631a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 826 additions and 358 deletions

View File

@ -7,6 +7,7 @@ extern crate serde_derive;
pub mod parse_account_data; pub mod parse_account_data;
pub mod parse_address_lookup_table; pub mod parse_address_lookup_table;
pub mod parse_bpf_loader; pub mod parse_bpf_loader;
#[allow(deprecated)]
pub mod parse_config; pub mod parse_config;
pub mod parse_nonce; pub mod parse_nonce;
pub mod parse_stake; pub mod parse_stake;

View File

@ -8,7 +8,9 @@ use {
solana_config_program::{get_config_data, ConfigKeys}, solana_config_program::{get_config_data, ConfigKeys},
solana_sdk::{ solana_sdk::{
pubkey::Pubkey, pubkey::Pubkey,
stake::config::{self as stake_config, Config as StakeConfig}, stake::config::{
Config as StakeConfig, {self as stake_config},
},
}, },
}; };
@ -66,6 +68,10 @@ pub struct UiConfigKey {
pub signer: bool, pub signer: bool,
} }
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
)]
#[derive(Debug, Serialize, Deserialize, PartialEq)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UiStakeConfig { pub struct UiStakeConfig {

View File

@ -119,11 +119,16 @@ pub struct UiDelegation {
pub stake: StringAmount, pub stake: StringAmount,
pub activation_epoch: StringAmount, pub activation_epoch: StringAmount,
pub deactivation_epoch: StringAmount, pub deactivation_epoch: StringAmount,
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::stake::warmup_cooldown_rate()` instead"
)]
pub warmup_cooldown_rate: f64, pub warmup_cooldown_rate: f64,
} }
impl From<Delegation> for UiDelegation { impl From<Delegation> for UiDelegation {
fn from(delegation: Delegation) -> Self { fn from(delegation: Delegation) -> Self {
#[allow(deprecated)]
Self { Self {
voter: delegation.voter_pubkey.to_string(), voter: delegation.voter_pubkey.to_string(),
stake: delegation.stake.to_string(), stake: delegation.stake.to_string(),
@ -139,6 +144,7 @@ mod test {
use {super::*, bincode::serialize, solana_sdk::stake::stake_flags::StakeFlags}; use {super::*, bincode::serialize, solana_sdk::stake::stake_flags::StakeFlags};
#[test] #[test]
#[allow(deprecated)]
fn test_parse_stake() { fn test_parse_stake() {
let stake_state = StakeState::Uninitialized; let stake_state = StakeState::Uninitialized;
let stake_data = serialize(&stake_state).unwrap(); let stake_data = serialize(&stake_state).unwrap();

View File

@ -2,6 +2,7 @@ use {
crate::{ crate::{
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}, cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
compute_unit_price::WithComputeUnitPrice, compute_unit_price::WithComputeUnitPrice,
feature::get_feature_activation_epoch,
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
}, },
clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}, clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand},
@ -43,6 +44,7 @@ use {
clock::{self, Clock, Slot}, clock::{self, Clock, Slot},
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
epoch_schedule::Epoch, epoch_schedule::Epoch,
feature_set,
hash::Hash, hash::Hash,
message::Message, message::Message,
native_token::lamports_to_sol, native_token::lamports_to_sol,
@ -1800,6 +1802,8 @@ pub fn process_show_stakes(
let stake_history = from_account(&stake_history_account).ok_or_else(|| { let stake_history = from_account(&stake_history_account).ok_or_else(|| {
CliError::RpcRequestError("Failed to deserialize stake history".to_string()) CliError::RpcRequestError("Failed to deserialize stake history".to_string())
})?; })?;
let new_rate_activation_epoch =
get_feature_activation_epoch(rpc_client, &feature_set::reduce_stake_warmup_cooldown::id())?;
let mut stake_accounts: Vec<CliKeyedStakeState> = vec![]; let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
for (stake_pubkey, stake_account) in all_stake_accounts { for (stake_pubkey, stake_account) in all_stake_accounts {
@ -1815,6 +1819,7 @@ pub fn process_show_stakes(
use_lamports_unit, use_lamports_unit,
&stake_history, &stake_history,
&clock, &clock,
new_rate_activation_epoch,
), ),
}); });
} }
@ -1833,6 +1838,7 @@ pub fn process_show_stakes(
use_lamports_unit, use_lamports_unit,
&stake_history, &stake_history,
&clock, &clock,
new_rate_activation_epoch,
), ),
}); });
} }

View File

@ -22,6 +22,7 @@ use {
genesis_config::ClusterType, genesis_config::ClusterType,
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
stake_history::Epoch,
transaction::Transaction, transaction::Transaction,
}, },
std::{cmp::Ordering, collections::HashMap, fmt, str::FromStr, sync::Arc}, std::{cmp::Ordering, collections::HashMap, fmt, str::FromStr, sync::Arc},
@ -817,6 +818,22 @@ pub fn get_feature_is_active(
.map(|status| matches!(status, Some(CliFeatureStatus::Active(_)))) .map(|status| matches!(status, Some(CliFeatureStatus::Active(_))))
} }
pub fn get_feature_activation_epoch(
rpc_client: &RpcClient,
feature_id: &Pubkey,
) -> Result<Option<Epoch>, ClientError> {
rpc_client
.get_feature_activation_slot(feature_id)
.and_then(|activation_slot: Option<Slot>| {
rpc_client
.get_epoch_schedule()
.map(|epoch_schedule| (activation_slot, epoch_schedule))
})
.map(|(activation_slot, epoch_schedule)| {
activation_slot.map(|slot| epoch_schedule.get_epoch(slot))
})
}
fn process_status( fn process_status(
rpc_client: &RpcClient, rpc_client: &RpcClient,
config: &CliConfig, config: &CliConfig,

View File

@ -6,6 +6,7 @@ use {
ProcessResult, ProcessResult,
}, },
compute_unit_price::WithComputeUnitPrice, compute_unit_price::WithComputeUnitPrice,
feature::get_feature_activation_epoch,
memo::WithMemo, memo::WithMemo,
nonce::check_nonce_account, nonce::check_nonce_account,
spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
@ -40,6 +41,7 @@ use {
clock::{Clock, UnixTimestamp, SECONDS_PER_DAY}, clock::{Clock, UnixTimestamp, SECONDS_PER_DAY},
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
epoch_schedule::EpochSchedule, epoch_schedule::EpochSchedule,
feature_set,
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
stake::{ stake::{
@ -48,7 +50,7 @@ use {
state::{Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeState}, state::{Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeState},
tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent}, tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
}, },
stake_history::StakeHistory, stake_history::{Epoch, StakeHistory},
system_instruction::SystemError, system_instruction::SystemError,
sysvar::{clock, stake_history}, sysvar::{clock, stake_history},
transaction::Transaction, transaction::Transaction,
@ -2186,6 +2188,7 @@ pub fn build_stake_state(
use_lamports_unit: bool, use_lamports_unit: bool,
stake_history: &StakeHistory, stake_history: &StakeHistory,
clock: &Clock, clock: &Clock,
new_rate_activation_epoch: Option<Epoch>,
) -> CliStakeState { ) -> CliStakeState {
match stake_state { match stake_state {
StakeState::Stake( StakeState::Stake(
@ -2202,9 +2205,11 @@ pub fn build_stake_state(
effective, effective,
activating, activating,
deactivating, deactivating,
} = stake } = stake.delegation.stake_activating_and_deactivating(
.delegation current_epoch,
.stake_activating_and_deactivating(current_epoch, Some(stake_history)); Some(stake_history),
new_rate_activation_epoch,
);
let lockup = if lockup.is_in_force(clock, None) { let lockup = if lockup.is_in_force(clock, None) {
Some(lockup.into()) Some(lockup.into())
} else { } else {
@ -2424,6 +2429,10 @@ pub fn process_show_stake_account(
let clock: Clock = from_account(&clock_account).ok_or_else(|| { let clock: Clock = from_account(&clock_account).ok_or_else(|| {
CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string()) CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
})?; })?;
let new_rate_activation_epoch = get_feature_activation_epoch(
rpc_client,
&feature_set::reduce_stake_warmup_cooldown::id(),
)?;
let mut state = build_stake_state( let mut state = build_stake_state(
stake_account.lamports, stake_account.lamports,
@ -2431,6 +2440,7 @@ pub fn process_show_stake_account(
use_lamports_unit, use_lamports_unit,
&stake_history, &stake_history,
&clock, &clock,
new_rate_activation_epoch,
); );
if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() { if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() {

View File

@ -38,13 +38,13 @@ use {
pubkey::Pubkey, pubkey::Pubkey,
signature::{Keypair, Signer}, signature::{Keypair, Signer},
stake::{ stake::{
config as stake_config, instruction as stake_instruction, instruction as stake_instruction,
state::{Authorized, Lockup}, state::{Authorized, Lockup},
}, },
system_transaction, system_transaction,
transaction::Transaction, transaction::Transaction,
}, },
solana_stake_program::{config::create_account as create_stake_config_account, stake_state}, solana_stake_program::stake_state,
solana_streamer::socket::SocketAddrSpace, solana_streamer::socket::SocketAddrSpace,
solana_tpu_client::tpu_client::{ solana_tpu_client::tpu_client::{
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC, DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
@ -267,18 +267,6 @@ impl LocalCluster {
.native_instruction_processors .native_instruction_processors
.extend_from_slice(&config.native_instruction_processors); .extend_from_slice(&config.native_instruction_processors);
// Replace staking config
genesis_config.add_account(
stake_config::id(),
create_stake_config_account(
1,
&stake_config::Config {
warmup_cooldown_rate: std::f64::MAX,
slash_penalty: std::u8::MAX,
},
),
);
let (leader_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config); let (leader_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config);
let leader_contact_info = leader_node.info.clone(); let leader_contact_info = leader_node.info.clone();
let mut leader_config = safe_clone_config(&config.validator_configs[0]); let mut leader_config = safe_clone_config(&config.validator_configs[0]);

View File

@ -1535,10 +1535,13 @@ fn test_fake_shreds_broadcast_leader() {
fn test_wait_for_max_stake() { fn test_wait_for_max_stake() {
solana_logger::setup_with_default(RUST_LOG_FILTER); solana_logger::setup_with_default(RUST_LOG_FILTER);
let validator_config = ValidatorConfig::default_for_test(); let validator_config = ValidatorConfig::default_for_test();
let slots_per_epoch = MINIMUM_SLOTS_PER_EPOCH;
let mut config = ClusterConfig { let mut config = ClusterConfig {
cluster_lamports: DEFAULT_CLUSTER_LAMPORTS, cluster_lamports: DEFAULT_CLUSTER_LAMPORTS,
node_stakes: vec![DEFAULT_NODE_STAKE; 4], node_stakes: vec![DEFAULT_NODE_STAKE; 4],
validator_configs: make_identical_validator_configs(&validator_config, 4), validator_configs: make_identical_validator_configs(&validator_config, 4),
slots_per_epoch,
stakers_slot_offset: slots_per_epoch,
..ClusterConfig::default() ..ClusterConfig::default()
}; };
let cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified); let cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified);

View File

@ -278,7 +278,7 @@ async fn stake_rewards_from_warp() {
assert_eq!( assert_eq!(
stake stake
.delegation .delegation
.stake_activating_and_deactivating(clock.epoch, Some(&stake_history)), .stake_activating_and_deactivating(clock.epoch, Some(&stake_history), None),
StakeActivationStatus::with_effective(stake.delegation.stake), StakeActivationStatus::with_effective(stake.delegation.stake),
); );
} }
@ -394,7 +394,7 @@ async fn stake_rewards_filter_bench_core(num_stake_accounts: u64) {
assert_eq!( assert_eq!(
stake stake
.delegation .delegation
.stake_activating_and_deactivating(clock.epoch, Some(&stake_history)), .stake_activating_and_deactivating(clock.epoch, Some(&stake_history), None),
StakeActivationStatus::with_effective(stake.delegation.stake), StakeActivationStatus::with_effective(stake.delegation.stake),
); );
} }

View File

@ -4,6 +4,8 @@ pub mod config_processor;
pub mod date_instruction; pub mod date_instruction;
pub use solana_sdk::config::program::id; pub use solana_sdk::config::program::id;
#[allow(deprecated)]
use solana_sdk::stake::config::Config as StakeConfig;
use { use {
bincode::{deserialize, serialize, serialized_size}, bincode::{deserialize, serialize, serialized_size},
serde_derive::{Deserialize, Serialize}, serde_derive::{Deserialize, Serialize},
@ -11,7 +13,6 @@ use {
account::{Account, AccountSharedData}, account::{Account, AccountSharedData},
pubkey::Pubkey, pubkey::Pubkey,
short_vec, short_vec,
stake::config::Config as StakeConfig,
}, },
}; };
@ -21,6 +22,7 @@ pub trait ConfigState: serde::Serialize + Default {
} }
// TODO move ConfigState into `solana_program` to implement trait locally // TODO move ConfigState into `solana_program` to implement trait locally
#[allow(deprecated)]
impl ConfigState for StakeConfig { impl ConfigState for StakeConfig {
fn max_space() -> u64 { fn max_space() -> u64 {
serialized_size(&StakeConfig::default()).unwrap() serialized_size(&StakeConfig::default()).unwrap()

View File

@ -5,27 +5,31 @@
note = "Please use `solana_sdk::stake::config` or `solana_program::stake::config` instead" note = "Please use `solana_sdk::stake::config` or `solana_program::stake::config` instead"
)] )]
pub use solana_sdk::stake::config::*; pub use solana_sdk::stake::config::*;
#[allow(deprecated)]
use solana_sdk::stake::config::{self, Config};
use { use {
bincode::deserialize, bincode::deserialize,
solana_config_program::{create_config_account, get_config_data}, solana_config_program::{create_config_account, get_config_data},
solana_sdk::{ solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount}, account::{AccountSharedData, ReadableAccount, WritableAccount},
genesis_config::GenesisConfig, genesis_config::GenesisConfig,
stake::config::{self, Config},
transaction_context::BorrowedAccount, transaction_context::BorrowedAccount,
}, },
}; };
#[allow(deprecated)]
pub fn from(account: &BorrowedAccount) -> Option<Config> { pub fn from(account: &BorrowedAccount) -> Option<Config> {
get_config_data(account.get_data()) get_config_data(account.get_data())
.ok() .ok()
.and_then(|data| deserialize(data).ok()) .and_then(|data| deserialize(data).ok())
} }
#[allow(deprecated)]
pub fn create_account(lamports: u64, config: &Config) -> AccountSharedData { pub fn create_account(lamports: u64, config: &Config) -> AccountSharedData {
create_config_account(vec![], config, lamports) create_config_account(vec![], config, lamports)
} }
#[allow(deprecated)]
pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 { pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 {
let mut account = create_config_account(vec![], &Config::default(), 0); let mut account = create_config_account(vec![], &Config::default(), 0);
let lamports = genesis_config.rent.minimum_balance(account.data().len()); let lamports = genesis_config.rent.minimum_balance(account.data().len());

File diff suppressed because it is too large Load Diff

View File

@ -15,14 +15,14 @@ use {
account_utils::StateMut, account_utils::StateMut,
clock::{Clock, Epoch}, clock::{Clock, Epoch},
feature_set::{ feature_set::{
self, clean_up_delegation_errors, stake_merge_with_unmatched_credits_observed, self, clean_up_delegation_errors,
FeatureSet, reduce_stake_warmup_cooldown::NewWarmupCooldownRateEpoch,
stake_merge_with_unmatched_credits_observed, FeatureSet,
}, },
instruction::{checked_add, InstructionError}, instruction::{checked_add, InstructionError},
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
stake::{ stake::{
config::Config,
instruction::{LockupArgs, StakeError}, instruction::{LockupArgs, StakeError},
program::id, program::id,
stake_flags::StakeFlags, stake_flags::StakeFlags,
@ -97,6 +97,16 @@ pub fn meta_from(account: &AccountSharedData) -> Option<Meta> {
from(account).and_then(|state: StakeState| state.meta()) from(account).and_then(|state: StakeState| state.meta())
} }
pub(crate) fn new_warmup_cooldown_rate_epoch(invoke_context: &InvokeContext) -> Option<Epoch> {
let epoch_schedule = invoke_context
.get_sysvar_cache()
.get_epoch_schedule()
.unwrap();
invoke_context
.feature_set
.new_warmup_cooldown_rate_epoch(epoch_schedule.as_ref())
}
fn redelegate_stake( fn redelegate_stake(
invoke_context: &InvokeContext, invoke_context: &InvokeContext,
stake: &mut Stake, stake: &mut Stake,
@ -105,10 +115,10 @@ fn redelegate_stake(
vote_state: &VoteState, vote_state: &VoteState,
clock: &Clock, clock: &Clock,
stake_history: &StakeHistory, stake_history: &StakeHistory,
config: &Config,
) -> Result<(), StakeError> { ) -> Result<(), StakeError> {
let new_rate_activation_epoch = new_warmup_cooldown_rate_epoch(invoke_context);
// If stake is currently active: // If stake is currently active:
if stake.stake(clock.epoch, Some(stake_history)) != 0 { if stake.stake(clock.epoch, Some(stake_history), new_rate_activation_epoch) != 0 {
let stake_lamports_ok = if invoke_context let stake_lamports_ok = if invoke_context
.feature_set .feature_set
.is_active(&feature_set::stake_redelegate_instruction::id()) .is_active(&feature_set::stake_redelegate_instruction::id())
@ -144,7 +154,6 @@ fn redelegate_stake(
stake.delegation.activation_epoch = clock.epoch; stake.delegation.activation_epoch = clock.epoch;
stake.delegation.deactivation_epoch = std::u64::MAX; stake.delegation.deactivation_epoch = std::u64::MAX;
stake.delegation.voter_pubkey = *voter_pubkey; stake.delegation.voter_pubkey = *voter_pubkey;
stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
stake.credits_observed = vote_state.credits(); stake.credits_observed = vote_state.credits();
Ok(()) Ok(())
} }
@ -154,15 +163,9 @@ pub(crate) fn new_stake(
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
vote_state: &VoteState, vote_state: &VoteState,
activation_epoch: Epoch, activation_epoch: Epoch,
config: &Config,
) -> Stake { ) -> Stake {
Stake { Stake {
delegation: Delegation::new( delegation: Delegation::new(voter_pubkey, stake, activation_epoch),
voter_pubkey,
stake,
activation_epoch,
config.warmup_cooldown_rate,
),
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
} }
} }
@ -184,6 +187,7 @@ fn redeem_stake_rewards(
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Option<(u64, u64)> { ) -> Option<(u64, u64)> {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
@ -198,6 +202,7 @@ fn redeem_stake_rewards(
vote_state, vote_state,
stake_history, stake_history,
inflation_point_calc_tracer.as_ref(), inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
) )
.map(|calculated_stake_rewards| { .map(|calculated_stake_rewards| {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
@ -220,12 +225,14 @@ fn calculate_stake_points(
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> u128 { ) -> u128 {
calculate_stake_points_and_credits( calculate_stake_points_and_credits(
stake, stake,
vote_state, vote_state,
stake_history, stake_history,
inflation_point_calc_tracer, inflation_point_calc_tracer,
new_rate_activation_epoch,
) )
.points .points
} }
@ -245,6 +252,7 @@ fn calculate_stake_points_and_credits(
new_vote_state: &VoteState, new_vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> CalculatedStakePoints { ) -> CalculatedStakePoints {
let credits_in_stake = stake.credits_observed; let credits_in_stake = stake.credits_observed;
let credits_in_vote = new_vote_state.credits(); let credits_in_vote = new_vote_state.credits();
@ -298,7 +306,11 @@ fn calculate_stake_points_and_credits(
for (epoch, final_epoch_credits, initial_epoch_credits) in for (epoch, final_epoch_credits, initial_epoch_credits) in
new_vote_state.epoch_credits().iter().copied() new_vote_state.epoch_credits().iter().copied()
{ {
let stake_amount = u128::from(stake.delegation.stake(epoch, stake_history)); let stake_amount = u128::from(stake.delegation.stake(
epoch,
stake_history,
new_rate_activation_epoch,
));
// figure out how much this stake has seen that // figure out how much this stake has seen that
// for which the vote account has a record // for which the vote account has a record
@ -359,6 +371,7 @@ fn calculate_stake_rewards(
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Option<CalculatedStakeRewards> { ) -> Option<CalculatedStakeRewards> {
// ensure to run to trigger (optional) inflation_point_calc_tracer // ensure to run to trigger (optional) inflation_point_calc_tracer
let CalculatedStakePoints { let CalculatedStakePoints {
@ -370,6 +383,7 @@ fn calculate_stake_rewards(
vote_state, vote_state,
stake_history, stake_history,
inflation_point_calc_tracer.as_ref(), inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
); );
// Drive credits_observed forward unconditionally when rewards are disabled // Drive credits_observed forward unconditionally when rewards are disabled
@ -564,7 +578,6 @@ pub fn delegate(
vote_account_index: IndexOfAccount, vote_account_index: IndexOfAccount,
clock: &Clock, clock: &Clock,
stake_history: &StakeHistory, stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
feature_set: &FeatureSet, feature_set: &FeatureSet,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
@ -589,7 +602,6 @@ pub fn delegate(
&vote_pubkey, &vote_pubkey,
&vote_state?.convert_to_current(), &vote_state?.convert_to_current(),
clock.epoch, clock.epoch,
config,
); );
stake_account.set_state(&StakeState::Stake(meta, stake, StakeFlags::empty())) stake_account.set_state(&StakeState::Stake(meta, stake, StakeFlags::empty()))
} }
@ -605,7 +617,6 @@ pub fn delegate(
&vote_state?.convert_to_current(), &vote_state?.convert_to_current(),
clock, clock,
stake_history, stake_history,
config,
)?; )?;
stake_account.set_state(&StakeState::Stake(meta, stake, stake_flags)) stake_account.set_state(&StakeState::Stake(meta, stake, stake_flags))
} }
@ -875,7 +886,6 @@ pub fn redelegate(
stake_account: &mut BorrowedAccount, stake_account: &mut BorrowedAccount,
uninitialized_stake_account_index: IndexOfAccount, uninitialized_stake_account_index: IndexOfAccount,
vote_account_index: IndexOfAccount, vote_account_index: IndexOfAccount,
config: &Config,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let clock = invoke_context.get_sysvar_cache().get_clock()?; let clock = invoke_context.get_sysvar_cache().get_clock()?;
@ -930,9 +940,11 @@ pub fn redelegate(
let (stake_meta, effective_stake) = let (stake_meta, effective_stake) =
if let StakeState::Stake(meta, stake, _stake_flags) = stake_account.get_state()? { if let StakeState::Stake(meta, stake, _stake_flags) = stake_account.get_state()? {
let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?; let stake_history = invoke_context.get_sysvar_cache().get_stake_history()?;
let status = stake let status = stake.delegation.stake_activating_and_deactivating(
.delegation clock.epoch,
.stake_activating_and_deactivating(clock.epoch, Some(&stake_history)); Some(&stake_history),
new_warmup_cooldown_rate_epoch(invoke_context),
);
if status.effective == 0 || status.activating != 0 || status.deactivating != 0 { if status.effective == 0 || status.activating != 0 || status.deactivating != 0 {
ic_msg!(invoke_context, "stake is not active"); ic_msg!(invoke_context, "stake is not active");
return Err(StakeError::RedelegateTransientOrInactiveStake.into()); return Err(StakeError::RedelegateTransientOrInactiveStake.into());
@ -982,7 +994,6 @@ pub fn redelegate(
&vote_pubkey, &vote_pubkey,
&vote_state.convert_to_current(), &vote_state.convert_to_current(),
clock.epoch, clock.epoch,
config,
), ),
StakeFlags::empty(), StakeFlags::empty(),
))?; ))?;
@ -1001,6 +1012,7 @@ pub fn withdraw(
stake_history: &StakeHistory, stake_history: &StakeHistory,
withdraw_authority_index: IndexOfAccount, withdraw_authority_index: IndexOfAccount,
custodian_index: Option<IndexOfAccount>, custodian_index: Option<IndexOfAccount>,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index( let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index(
instruction_context instruction_context
@ -1020,7 +1032,9 @@ pub fn withdraw(
.check(&signers, StakeAuthorize::Withdrawer)?; .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.delegation.deactivation_epoch { let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.delegation.stake(clock.epoch, Some(stake_history)) stake
.delegation
.stake(clock.epoch, Some(stake_history), new_rate_activation_epoch)
} 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
@ -1307,9 +1321,11 @@ impl MergeKind {
StakeState::Stake(meta, stake, stake_flags) => { StakeState::Stake(meta, stake, stake_flags) => {
// stake must not be in a transient state. Transient here meaning // stake must not be in a transient state. Transient here meaning
// activating or deactivating with non-zero effective stake. // activating or deactivating with non-zero effective stake.
let status = stake let status = stake.delegation.stake_activating_and_deactivating(
.delegation clock.epoch,
.stake_activating_and_deactivating(clock.epoch, Some(stake_history)); Some(stake_history),
new_warmup_cooldown_rate_epoch(invoke_context),
);
match (status.effective, status.activating, status.deactivating) { match (status.effective, status.activating, status.deactivating) {
(0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports, *stake_flags)), (0, 0, 0) => Ok(Self::Inactive(*meta, stake_lamports, *stake_flags)),
@ -1359,9 +1375,7 @@ impl MergeKind {
if stake.voter_pubkey != source.voter_pubkey { if stake.voter_pubkey != source.voter_pubkey {
ic_msg!(invoke_context, "Unable to merge due to voter mismatch"); ic_msg!(invoke_context, "Unable to merge due to voter mismatch");
Err(StakeError::MergeMismatch.into()) Err(StakeError::MergeMismatch.into())
} else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON } else if stake.deactivation_epoch == Epoch::MAX && source.deactivation_epoch == Epoch::MAX
&& stake.deactivation_epoch == Epoch::MAX
&& source.deactivation_epoch == Epoch::MAX
{ {
Ok(()) Ok(())
} else { } else {
@ -1547,13 +1561,16 @@ pub fn redeem_rewards(
point_value: &PointValue, point_value: &PointValue,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<(u64, u64), InstructionError> { ) -> Result<(u64, u64), InstructionError> {
if let StakeState::Stake(meta, mut stake, stake_flags) = stake_state { if let StakeState::Stake(meta, mut stake, stake_flags) = stake_state {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer( inflation_point_calc_tracer(
&InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch( &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake(
stake.stake(rewarded_epoch, stake_history), rewarded_epoch,
), stake_history,
new_rate_activation_epoch,
)),
); );
inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve( inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
meta.rent_exempt_reserve, meta.rent_exempt_reserve,
@ -1570,6 +1587,7 @@ pub fn redeem_rewards(
vote_state, vote_state,
stake_history, stake_history,
inflation_point_calc_tracer, inflation_point_calc_tracer,
new_rate_activation_epoch,
) { ) {
stake_account.checked_add_lamports(stakers_reward)?; stake_account.checked_add_lamports(stakers_reward)?;
stake_account.set_state(&StakeState::Stake(meta, stake, stake_flags))?; stake_account.set_state(&StakeState::Stake(meta, stake, stake_flags))?;
@ -1589,6 +1607,7 @@ pub fn calculate_points(
stake_state: &StakeState, stake_state: &StakeState,
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<u128, InstructionError> { ) -> Result<u128, InstructionError> {
if let StakeState::Stake(_meta, stake, _stake_flags) = stake_state { if let StakeState::Stake(_meta, stake, _stake_flags) = stake_state {
Ok(calculate_stake_points( Ok(calculate_stake_points(
@ -1596,6 +1615,7 @@ pub fn calculate_points(
vote_state, vote_state,
stake_history, stake_history,
null_tracer(), null_tracer(),
new_rate_activation_epoch,
)) ))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
@ -1609,12 +1629,13 @@ pub fn new_stake_history_entry<'a, I>(
epoch: Epoch, epoch: Epoch,
stakes: I, stakes: I,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> StakeHistoryEntry ) -> StakeHistoryEntry
where where
I: Iterator<Item = &'a Delegation>, I: Iterator<Item = &'a Delegation>,
{ {
stakes.fold(StakeHistoryEntry::default(), |sum, stake| { stakes.fold(StakeHistoryEntry::default(), |sum, stake| {
sum + stake.stake_activating_and_deactivating(epoch, history) sum + stake.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
}) })
} }
@ -1623,6 +1644,7 @@ pub fn create_stake_history_from_delegations(
bootstrap: Option<u64>, bootstrap: Option<u64>,
epochs: std::ops::Range<Epoch>, epochs: std::ops::Range<Epoch>,
delegations: &[Delegation], delegations: &[Delegation],
new_rate_activation_epoch: Option<Epoch>,
) -> StakeHistory { ) -> StakeHistory {
let mut stake_history = StakeHistory::default(); let mut stake_history = StakeHistory::default();
@ -1641,6 +1663,7 @@ pub fn create_stake_history_from_delegations(
epoch, epoch,
delegations.iter().chain(bootstrap_delegation.iter()), delegations.iter().chain(bootstrap_delegation.iter()),
Some(&stake_history), Some(&stake_history),
new_rate_activation_epoch,
); );
stake_history.add(epoch, entry); stake_history.add(epoch, entry);
} }
@ -1737,7 +1760,6 @@ fn do_create_account(
voter_pubkey, voter_pubkey,
&vote_state, &vote_state,
activation_epoch, activation_epoch,
&Config::default(),
), ),
StakeFlags::empty(), StakeFlags::empty(),
)) ))
@ -1754,10 +1776,13 @@ mod tests {
solana_program_runtime::with_mock_invoke_context, solana_program_runtime::with_mock_invoke_context,
solana_sdk::{ solana_sdk::{
account::{create_account_shared_data_for_test, AccountSharedData}, account::{create_account_shared_data_for_test, AccountSharedData},
epoch_schedule::EpochSchedule,
native_token, native_token,
pubkey::Pubkey, pubkey::Pubkey,
sysvar::SysvarId, stake::state::warmup_cooldown_rate,
sysvar::{epoch_schedule, SysvarId},
}, },
test_case::test_case,
}; };
#[test] #[test]
@ -1933,24 +1958,31 @@ mod tests {
}; };
// 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_f64 * stake.warmup_cooldown_rate) as u64; let increment = (1_000_f64 * warmup_cooldown_rate(0, None)) 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
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history),), stake.stake_activating_and_deactivating(
stake.activation_epoch,
Some(&stake_history),
None
),
StakeActivationStatus::with_effective_and_activating(0, stake.stake), StakeActivationStatus::with_effective_and_activating(0, stake.stake),
); );
for epoch in stake.activation_epoch + 1..stake.deactivation_epoch { for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(epoch, Some(&stake_history), None),
StakeActivationStatus::with_effective(stake.stake), StakeActivationStatus::with_effective(stake.stake),
); );
} }
// assert that this stake is full deactivating // assert that this stake is full deactivating
assert_eq!( assert_eq!(
stake stake.stake_activating_and_deactivating(
.stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history),), stake.deactivation_epoch,
Some(&stake_history),
None
),
StakeActivationStatus::with_deactivating(stake.stake), StakeActivationStatus::with_deactivating(stake.stake),
); );
// assert that this stake is fully deactivated if there's no history // assert that this stake is fully deactivated if there's no history
@ -1958,6 +1990,7 @@ mod tests {
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1, stake.deactivation_epoch + 1,
Some(&stake_history), Some(&stake_history),
None
), ),
StakeActivationStatus::default(), StakeActivationStatus::default(),
); );
@ -1971,7 +2004,7 @@ mod tests {
); );
// assert that this stake is broken, because above setup is broken // assert that this stake is broken, because above setup is broken
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(1, Some(&stake_history)), stake.stake_activating_and_deactivating(1, Some(&stake_history), None),
StakeActivationStatus::with_effective_and_activating(0, stake.stake), StakeActivationStatus::with_effective_and_activating(0, stake.stake),
); );
@ -1986,7 +2019,7 @@ mod tests {
); );
// assert that this stake is broken, because above setup is broken // assert that this stake is broken, because above setup is broken
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(2, Some(&stake_history)), stake.stake_activating_and_deactivating(2, Some(&stake_history), None),
StakeActivationStatus::with_effective_and_activating( StakeActivationStatus::with_effective_and_activating(
increment, increment,
stake.stake - increment stake.stake - increment
@ -2008,6 +2041,7 @@ mod tests {
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1, stake.deactivation_epoch + 1,
Some(&stake_history), Some(&stake_history),
None,
), ),
StakeActivationStatus::with_deactivating(stake.stake), StakeActivationStatus::with_deactivating(stake.stake),
); );
@ -2026,6 +2060,7 @@ mod tests {
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 2, stake.deactivation_epoch + 2,
Some(&stake_history), Some(&stake_history),
None,
), ),
// hung, should be lower // hung, should be lower
StakeActivationStatus::with_deactivating(stake.stake - increment), StakeActivationStatus::with_deactivating(stake.stake - increment),
@ -2092,8 +2127,11 @@ mod tests {
assert_eq!( assert_eq!(
expected_stakes, expected_stakes,
(0..expected_stakes.len()) (0..expected_stakes.len())
.map(|epoch| stake .map(|epoch| stake.stake_activating_and_deactivating(
.stake_activating_and_deactivating(epoch as u64, Some(&stake_history),)) epoch as u64,
Some(&stake_history),
None,
))
.collect::<Vec<_>>() .collect::<Vec<_>>()
); );
} }
@ -2220,7 +2258,11 @@ mod tests {
let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> { let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> {
(0..epoch_count) (0..epoch_count)
.map(|epoch| { .map(|epoch| {
stake.stake_activating_and_deactivating(epoch as u64, Some(&stake_history)) stake.stake_activating_and_deactivating(
epoch as u64,
Some(&stake_history),
None,
)
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
@ -2319,7 +2361,7 @@ mod tests {
}, },
); );
let effective_rate_limited = (effective as f64 * stake.warmup_cooldown_rate) as u64; let effective_rate_limited = (effective as f64 * warmup_cooldown_rate(0, None)) as u64;
if epoch < stake.deactivation_epoch { if epoch < stake.deactivation_epoch {
effective += effective_rate_limited.min(activating); effective += effective_rate_limited.min(activating);
other_activations.push(0); other_activations.push(0);
@ -2340,7 +2382,7 @@ mod tests {
(0, history.deactivating) (0, history.deactivating)
}; };
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(epoch, Some(&stake_history), None),
StakeActivationStatus { StakeActivationStatus {
effective: expected_stake, effective: expected_stake,
activating: expected_activating, activating: expected_activating,
@ -2362,16 +2404,16 @@ mod tests {
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 = (delegations[0].warmup_cooldown_rate * 100.0 / 2.0) as u64; let bootstrap = (warmup_cooldown_rate(0, None) * 100.0 / 2.0) as u64;
let stake_history = let stake_history =
create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations); create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations, None);
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 = delegations let stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history), None))
.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);
@ -2380,8 +2422,13 @@ mod tests {
assert_eq!(min_stake, 0); assert_eq!(min_stake, 0);
} }
#[test] #[test_case(None ; "old rate")]
fn test_stake_warmup_cooldown() { #[test_case(Some(1) ; "new rate activated in epoch 1")]
#[test_case(Some(10) ; "new rate activated in epoch 10")]
#[test_case(Some(30) ; "new rate activated in epoch 30")]
#[test_case(Some(50) ; "new rate activated in epoch 50")]
#[test_case(Some(60) ; "new rate activated in epoch 60")]
fn test_stake_warmup_cooldown(new_rate_activation_epoch: Option<Epoch>) {
let delegations = [ let delegations = [
Delegation { Delegation {
// never deactivates // never deactivates
@ -2424,13 +2471,18 @@ mod tests {
// warming up and cooling down // warming up and cooling down
// a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down // 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 // when all alone, but the above overlap a lot
let epochs = 20; let epochs = 60;
let stake_history = create_stake_history_from_delegations(None, 0..epochs, &delegations); let stake_history = create_stake_history_from_delegations(
None,
0..epochs,
&delegations,
new_rate_activation_epoch,
);
let mut prev_total_effective_stake = delegations let mut prev_total_effective_stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(0, Some(&stake_history))) .map(|delegation| delegation.stake(0, Some(&stake_history), new_rate_activation_epoch))
.sum::<u64>(); .sum::<u64>();
// uncomment and add ! for fun with graphing // uncomment and add ! for fun with graphing
@ -2438,7 +2490,9 @@ mod tests {
for epoch in 1..epochs { for epoch in 1..epochs {
let total_effective_stake = delegations let total_effective_stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(epoch, Some(&stake_history))) .map(|delegation| {
delegation.stake(epoch, Some(&stake_history), new_rate_activation_epoch)
})
.sum::<u64>(); .sum::<u64>();
let delta = if total_effective_stake > prev_total_effective_stake { let delta = if total_effective_stake > prev_total_effective_stake {
@ -2448,13 +2502,15 @@ mod tests {
}; };
// uncomment and add ! for fun with graphing // uncomment and add ! for fun with graphing
//eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta); // eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta);
//(0..(total_effective_stake as usize / (stakes.len() * 5))).for_each(|_| eprint("#")); // (0..(total_effective_stake as usize / (delegations.len() * 5))).for_each(|_| eprint("#"));
//eprintln(); // eprintln();
assert!( assert!(
delta delta
<= ((prev_total_effective_stake as f64 * Config::default().warmup_cooldown_rate) as u64) <= ((prev_total_effective_stake as f64
* warmup_cooldown_rate(epoch, new_rate_activation_epoch))
as u64)
.max(1) .max(1)
); );
@ -2473,7 +2529,6 @@ mod tests {
&Pubkey::default(), &Pubkey::default(),
&vote_state, &vote_state,
std::u64::MAX, std::u64::MAX,
&Config::default(),
); );
// this one can't collect now, credits_observed == vote_state.credits() // this one can't collect now, credits_observed == vote_state.credits()
@ -2489,6 +2544,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2509,6 +2565,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2530,7 +2587,6 @@ mod tests {
&Pubkey::default(), &Pubkey::default(),
&vote_state, &vote_state,
std::u64::MAX, std::u64::MAX,
&Config::default(),
); );
// this one can't collect now, credits_observed == vote_state.credits() // this one can't collect now, credits_observed == vote_state.credits()
@ -2546,6 +2602,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2559,7 +2616,7 @@ mod tests {
// no overflow on points // no overflow on points
assert_eq!( assert_eq!(
u128::from(stake.delegation.stake) * epoch_slots, u128::from(stake.delegation.stake) * epoch_slots,
calculate_stake_points(&stake, &vote_state, None, null_tracer()) calculate_stake_points(&stake, &vote_state, None, null_tracer(), None)
); );
} }
@ -2568,13 +2625,7 @@ mod tests {
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
// assume stake.stake() is right // assume stake.stake() is right
// bootstrap means fully-vested stake at epoch 0 // bootstrap means fully-vested stake at epoch 0
let mut stake = new_stake( let mut stake = new_stake(1, &Pubkey::default(), &vote_state, std::u64::MAX);
1,
&Pubkey::default(),
&vote_state,
std::u64::MAX,
&Config::default(),
);
// this one can't collect now, credits_observed == vote_state.credits() // this one can't collect now, credits_observed == vote_state.credits()
assert_eq!( assert_eq!(
@ -2589,6 +2640,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2613,6 +2665,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2634,6 +2687,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2658,6 +2712,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2680,6 +2735,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2704,6 +2760,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2722,6 +2779,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
vote_state.commission = 99; vote_state.commission = 99;
@ -2737,6 +2795,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2759,6 +2818,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2781,6 +2841,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2790,7 +2851,7 @@ mod tests {
new_credits_observed: 4, new_credits_observed: 4,
force_credits_update_with_skipped_reward: false, force_credits_update_with_skipped_reward: false,
}, },
calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer()) calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), None)
); );
// credits_observed is auto-rewinded when vote_state credits are assumed to have been // credits_observed is auto-rewinded when vote_state credits are assumed to have been
@ -2803,7 +2864,7 @@ mod tests {
new_credits_observed: 4, new_credits_observed: 4,
force_credits_update_with_skipped_reward: true, force_credits_update_with_skipped_reward: true,
}, },
calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer()) calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), None)
); );
// this is new behavior 2; don't hint when credits both from stake and vote are identical // this is new behavior 2; don't hint when credits both from stake and vote are identical
stake.credits_observed = 4; stake.credits_observed = 4;
@ -2813,7 +2874,7 @@ mod tests {
new_credits_observed: 4, new_credits_observed: 4,
force_credits_update_with_skipped_reward: false, force_credits_update_with_skipped_reward: false,
}, },
calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer()) calculate_stake_points_and_credits(&stake, &vote_state, None, null_tracer(), None)
); );
// get rewards and credits observed when not the activation epoch // get rewards and credits observed when not the activation epoch
@ -2836,6 +2897,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
@ -2859,6 +2921,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
null_tracer(), null_tracer(),
None,
) )
); );
} }
@ -2993,23 +3056,6 @@ mod tests {
) )
.is_err()); .is_err());
let bad_warmup_cooldown_rate = Delegation {
warmup_cooldown_rate: good_delegation.warmup_cooldown_rate + f64::EPSILON,
..good_delegation
};
assert!(MergeKind::active_delegations_can_merge(
&invoke_context,
&good_delegation,
&bad_warmup_cooldown_rate
)
.is_err());
assert!(MergeKind::active_delegations_can_merge(
&invoke_context,
&bad_warmup_cooldown_rate,
&good_delegation
)
.is_err());
let bad_deactivation_epoch = Delegation { let bad_deactivation_epoch = Delegation {
deactivation_epoch: 43, deactivation_epoch: 43,
..good_delegation ..good_delegation
@ -3176,12 +3222,17 @@ mod tests {
#[test] #[test]
fn test_merge_kind_get_if_mergeable() { fn test_merge_kind_get_if_mergeable() {
with_mock_invoke_context!(invoke_context, transaction_context, Vec::new()); let transaction_accounts = vec![(
epoch_schedule::id(),
create_account_shared_data_for_test(&EpochSchedule::default()),
)];
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
let authority_pubkey = Pubkey::new_unique(); let authority_pubkey = Pubkey::new_unique();
let initial_lamports = 4242424242; let initial_lamports = 4242424242;
let rent = Rent::default(); let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of()); let rent_exempt_reserve = rent.minimum_balance(StakeState::size_of());
let stake_lamports = rent_exempt_reserve + initial_lamports; let stake_lamports = rent_exempt_reserve + initial_lamports;
let new_rate_activation_epoch = Some(0);
let meta = Meta { let meta = Meta {
rent_exempt_reserve, rent_exempt_reserve,
@ -3268,7 +3319,7 @@ mod tests {
delegation: Delegation { delegation: Delegation {
stake: initial_lamports, stake: initial_lamports,
activation_epoch: 1, activation_epoch: 1,
deactivation_epoch: 5, deactivation_epoch: 9,
..Delegation::default() ..Delegation::default()
}, },
..Stake::default() ..Stake::default()
@ -3292,8 +3343,10 @@ mod tests {
// all paritially activated, transient epochs fail // all paritially activated, transient epochs fail
loop { loop {
clock.epoch += 1; clock.epoch += 1;
let delta = let delta = activating.min(
activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); (effective as f64 * warmup_cooldown_rate(clock.epoch, new_rate_activation_epoch))
as u64,
);
effective += delta; effective += delta;
activating -= delta; activating -= delta;
stake_history.add( stake_history.add(
@ -3370,8 +3423,10 @@ mod tests {
// all transient, deactivating epochs fail // all transient, deactivating epochs fail
loop { loop {
clock.epoch += 1; clock.epoch += 1;
let delta = let delta = deactivating.min(
deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64); (effective as f64 * warmup_cooldown_rate(clock.epoch, new_rate_activation_epoch))
as u64,
);
effective -= delta; effective -= delta;
deactivating -= delta; deactivating -= delta;
stake_history.add( stake_history.add(

View File

@ -28,17 +28,18 @@ use {
UiAccount, UiAccountEncoding, UiAccount, UiAccountEncoding,
}, },
solana_rpc_client_api::{ solana_rpc_client_api::{
client_error::Result as ClientResult, client_error::{Error as ClientError, ErrorKind, Result as ClientResult},
config::{RpcAccountInfoConfig, *}, config::{RpcAccountInfoConfig, *},
request::{RpcRequest, TokenAccountsFilter}, request::{RpcRequest, TokenAccountsFilter},
response::*, response::*,
}, },
solana_sdk::{ solana_sdk::{
account::Account, account::{Account, ReadableAccount},
clock::{Epoch, Slot, UnixTimestamp}, clock::{Epoch, Slot, UnixTimestamp},
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
epoch_info::EpochInfo, epoch_info::EpochInfo,
epoch_schedule::EpochSchedule, epoch_schedule::EpochSchedule,
feature::Feature,
fee_calculator::{FeeCalculator, FeeRateGovernor}, fee_calculator::{FeeCalculator, FeeRateGovernor},
hash::Hash, hash::Hash,
message::{v0, Message as LegacyMessage}, message::{v0, Message as LegacyMessage},
@ -4027,6 +4028,18 @@ impl RpcClient {
(self.rpc_client.as_ref()).get_transport_stats() (self.rpc_client.as_ref()).get_transport_stats()
} }
pub fn get_feature_activation_slot(&self, feature_id: &Pubkey) -> ClientResult<Option<Slot>> {
self.get_account(feature_id)
.and_then(|feature_account| {
bincode::deserialize(feature_account.data()).map_err(|_| {
ClientError::from(ErrorKind::Custom(
"Failed to deserialize feature account".to_string(),
))
})
})
.map(|feature: Feature| feature.activated_at)
}
fn invoke<T, F: std::future::Future<Output = ClientResult<T>>>(&self, f: F) -> ClientResult<T> { fn invoke<T, F: std::future::Future<Output = ClientResult<T>>>(&self, f: F) -> ClientResult<T> {
// `block_on()` panics if called within an asynchronous execution context. Whereas // `block_on()` panics if called within an asynchronous execution context. Whereas
// `block_in_place()` only panics if called from a current_thread runtime, which is the // `block_in_place()` only panics if called from a current_thread runtime, which is the

View File

@ -1760,12 +1760,17 @@ impl JsonRpcRequestProcessor {
let stake_history = let stake_history =
solana_sdk::account::from_account::<StakeHistory, _>(&stake_history_account) solana_sdk::account::from_account::<StakeHistory, _>(&stake_history_account)
.ok_or_else(Error::internal_error)?; .ok_or_else(Error::internal_error)?;
let new_rate_activation_epoch = bank.new_warmup_cooldown_rate_epoch();
let StakeActivationStatus { let StakeActivationStatus {
effective, effective,
activating, activating,
deactivating, deactivating,
} = delegation.stake_activating_and_deactivating(epoch, Some(&stake_history)); } = delegation.stake_activating_and_deactivating(
epoch,
Some(&stake_history),
new_rate_activation_epoch,
);
let stake_activation_state = if deactivating > 0 { let stake_activation_state = if deactivating > 0 {
StakeActivationState::Deactivating StakeActivationState::Deactivating
} else if activating > 0 { } else if activating > 0 {

View File

@ -135,6 +135,7 @@ use {
self, add_set_tx_loaded_accounts_data_size_instruction, self, add_set_tx_loaded_accounts_data_size_instruction,
enable_early_verification_of_account_modifications, enable_early_verification_of_account_modifications,
include_loaded_accounts_data_size_in_fee_calculation, include_loaded_accounts_data_size_in_fee_calculation,
reduce_stake_warmup_cooldown::NewWarmupCooldownRateEpoch,
remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix, remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix,
FeatureSet, FeatureSet,
}, },
@ -1525,6 +1526,12 @@ impl Bank {
new new
} }
/// Epoch in which the new cooldown warmup rate for stake was activated
pub fn new_warmup_cooldown_rate_epoch(&self) -> Option<Epoch> {
self.feature_set
.new_warmup_cooldown_rate_epoch(&self.epoch_schedule)
}
/// process for the start of a new epoch /// process for the start of a new epoch
fn process_new_epoch( fn process_new_epoch(
&mut self, &mut self,
@ -1549,7 +1556,11 @@ impl Bank {
// update vote accounts with warmed up stakes before saving a // update vote accounts with warmed up stakes before saving a
// snapshot of stakes in epoch stakes // snapshot of stakes in epoch stakes
let (_, activate_epoch_time) = measure!( let (_, activate_epoch_time) = measure!(
self.stakes_cache.activate_epoch(epoch, &thread_pool), self.stakes_cache.activate_epoch(
epoch,
&thread_pool,
self.new_warmup_cooldown_rate_epoch()
),
"activate_epoch", "activate_epoch",
); );
@ -3010,6 +3021,7 @@ impl Bank {
stake_account.stake_state(), stake_account.stake_state(),
vote_state, vote_state,
Some(stake_history), Some(stake_history),
self.new_warmup_cooldown_rate_epoch(),
) )
.unwrap_or(0) .unwrap_or(0)
}) })
@ -3045,6 +3057,7 @@ impl Bank {
stake_account.stake_state(), stake_account.stake_state(),
vote_state, vote_state,
Some(stake_history), Some(stake_history),
self.new_warmup_cooldown_rate_epoch(),
) )
.unwrap_or(0) .unwrap_or(0)
}) })
@ -3131,6 +3144,7 @@ impl Bank {
&point_value, &point_value,
Some(stake_history), Some(stake_history),
reward_calc_tracer.as_ref(), reward_calc_tracer.as_ref(),
self.new_warmup_cooldown_rate_epoch(),
); );
let post_lamport = stake_account.lamports(); let post_lamport = stake_account.lamports();
@ -3248,6 +3262,7 @@ impl Bank {
&point_value, &point_value,
Some(stake_history), Some(stake_history),
reward_calc_tracer.as_ref(), reward_calc_tracer.as_ref(),
self.new_warmup_cooldown_rate_epoch(),
); );
if let Ok((stakers_reward, voters_reward)) = redeemed { if let Ok((stakers_reward, voters_reward)) = redeemed {
// track voter rewards // track voter rewards
@ -3298,8 +3313,11 @@ impl Bank {
let now = Instant::now(); let now = Instant::now();
let slot = self.slot(); let slot = self.slot();
let include_slot_in_hash = self.include_slot_in_hash(); let include_slot_in_hash = self.include_slot_in_hash();
self.stakes_cache self.stakes_cache.update_stake_accounts(
.update_stake_accounts(thread_pool, stake_rewards); thread_pool,
stake_rewards,
self.new_warmup_cooldown_rate_epoch(),
);
assert!(!self.freeze_started()); assert!(!self.freeze_started());
thread_pool.install(|| { thread_pool.install(|| {
stake_rewards.par_chunks(512).for_each(|chunk| { stake_rewards.par_chunks(512).for_each(|chunk| {
@ -6518,8 +6536,11 @@ impl Bank {
assert!(!self.freeze_started()); assert!(!self.freeze_started());
let mut m = Measure::start("stakes_cache.check_and_store"); let mut m = Measure::start("stakes_cache.check_and_store");
(0..accounts.len()).for_each(|i| { (0..accounts.len()).for_each(|i| {
self.stakes_cache self.stakes_cache.check_and_store(
.check_and_store(accounts.pubkey(i), accounts.account(i)) accounts.pubkey(i),
accounts.account(i),
self.new_warmup_cooldown_rate_epoch(),
)
}); });
self.rc.accounts.store_accounts_cached(accounts); self.rc.accounts.store_accounts_cached(accounts);
m.stop(); m.stop();
@ -7637,7 +7658,11 @@ impl Bank {
.for_each(|(pubkey, account)| { .for_each(|(pubkey, account)| {
// note that this could get timed to: self.rc.accounts.accounts_db.stats.stakes_cache_check_and_store_us, // 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 // but this code path is captured separately in ExecuteTimingType::UpdateStakesCacheUs
self.stakes_cache.check_and_store(pubkey, account); self.stakes_cache.check_and_store(
pubkey,
account,
self.new_warmup_cooldown_rate_epoch(),
);
}); });
} }

View File

@ -4152,7 +4152,7 @@ fn test_bank_epoch_vote_accounts() {
// epoch_stakes are a snapshot at the leader_schedule_slot_offset boundary // epoch_stakes are a snapshot at the leader_schedule_slot_offset boundary
// in the prior epoch (0 in this case) // in the prior epoch (0 in this case)
assert_eq!( assert_eq!(
leader_stake.stake(0, None), leader_stake.stake(0, None, None),
vote_accounts.unwrap().get(&leader_vote_account).unwrap().0 vote_accounts.unwrap().get(&leader_vote_account).unwrap().0
); );
@ -4168,7 +4168,7 @@ fn test_bank_epoch_vote_accounts() {
assert!(child.epoch_vote_accounts(epoch).is_some()); assert!(child.epoch_vote_accounts(epoch).is_some());
assert_eq!( assert_eq!(
leader_stake.stake(child.epoch(), None), leader_stake.stake(child.epoch(), None, None),
child child
.epoch_vote_accounts(epoch) .epoch_vote_accounts(epoch)
.unwrap() .unwrap()
@ -4186,7 +4186,7 @@ fn test_bank_epoch_vote_accounts() {
); );
assert!(child.epoch_vote_accounts(epoch).is_some()); assert!(child.epoch_vote_accounts(epoch).is_some());
assert_eq!( assert_eq!(
leader_stake.stake(child.epoch(), None), leader_stake.stake(child.epoch(), None, None),
child child
.epoch_vote_accounts(epoch) .epoch_vote_accounts(epoch)
.unwrap() .unwrap()

View File

@ -65,7 +65,12 @@ impl StakesCache {
self.0.read().unwrap() self.0.read().unwrap()
} }
pub(crate) fn check_and_store(&self, pubkey: &Pubkey, account: &impl ReadableAccount) { pub(crate) fn check_and_store(
&self,
pubkey: &Pubkey,
account: &impl ReadableAccount,
new_rate_activation_epoch: Option<Epoch>,
) {
// TODO: If the account is already cached as a vote or stake account // 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 // but the owner changes, then this needs to evict the account from
// the cache. see: // the cache. see:
@ -79,7 +84,7 @@ impl StakesCache {
stakes.remove_vote_account(pubkey); stakes.remove_vote_account(pubkey);
} else if solana_stake_program::check_id(owner) { } else if solana_stake_program::check_id(owner) {
let mut stakes = self.0.write().unwrap(); let mut stakes = self.0.write().unwrap();
stakes.remove_stake_delegation(pubkey); stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch);
} }
return; return;
} }
@ -93,7 +98,7 @@ impl StakesCache {
let _res = vote_account.vote_state(); let _res = vote_account.vote_state();
} }
let mut stakes = self.0.write().unwrap(); let mut stakes = self.0.write().unwrap();
stakes.upsert_vote_account(pubkey, vote_account); stakes.upsert_vote_account(pubkey, vote_account, new_rate_activation_epoch);
} }
Err(_) => { Err(_) => {
let mut stakes = self.0.write().unwrap(); let mut stakes = self.0.write().unwrap();
@ -108,30 +113,41 @@ impl StakesCache {
match StakeAccount::try_from(account.to_account_shared_data()) { match StakeAccount::try_from(account.to_account_shared_data()) {
Ok(stake_account) => { Ok(stake_account) => {
let mut stakes = self.0.write().unwrap(); let mut stakes = self.0.write().unwrap();
stakes.upsert_stake_delegation(*pubkey, stake_account); stakes.upsert_stake_delegation(
*pubkey,
stake_account,
new_rate_activation_epoch,
);
} }
Err(_) => { Err(_) => {
let mut stakes = self.0.write().unwrap(); let mut stakes = self.0.write().unwrap();
stakes.remove_stake_delegation(pubkey); stakes.remove_stake_delegation(pubkey, new_rate_activation_epoch);
} }
} }
} }
} }
pub(crate) fn activate_epoch(&self, next_epoch: Epoch, thread_pool: &ThreadPool) { pub(crate) fn activate_epoch(
&self,
next_epoch: Epoch,
thread_pool: &ThreadPool,
new_rate_activation_epoch: Option<Epoch>,
) {
let mut stakes = self.0.write().unwrap(); let mut stakes = self.0.write().unwrap();
stakes.activate_epoch(next_epoch, thread_pool) stakes.activate_epoch(next_epoch, thread_pool, new_rate_activation_epoch)
} }
pub(crate) fn update_stake_accounts( pub(crate) fn update_stake_accounts(
&self, &self,
thread_pool: &ThreadPool, thread_pool: &ThreadPool,
stake_rewards: &[StakeReward], stake_rewards: &[StakeReward],
new_rate_activation_epoch: Option<Epoch>,
) { ) {
self.0 self.0.write().unwrap().update_stake_accounts(
.write() thread_pool,
.unwrap() stake_rewards,
.update_stake_accounts(thread_pool, stake_rewards) new_rate_activation_epoch,
)
} }
pub(crate) fn handle_invalid_keys( pub(crate) fn handle_invalid_keys(
@ -272,7 +288,12 @@ impl Stakes<StakeAccount> {
&self.stake_history &self.stake_history
} }
fn activate_epoch(&mut self, next_epoch: Epoch, thread_pool: &ThreadPool) { fn activate_epoch(
&mut self,
next_epoch: Epoch,
thread_pool: &ThreadPool,
new_rate_activation_epoch: Option<Epoch>,
) {
let stake_delegations: Vec<_> = self.stake_delegations.values().collect(); let stake_delegations: Vec<_> = self.stake_delegations.values().collect();
// Wrap up the prev epoch by adding new stake history entry for the // Wrap up the prev epoch by adding new stake history entry for the
// prev epoch. // prev epoch.
@ -281,8 +302,11 @@ impl Stakes<StakeAccount> {
.par_iter() .par_iter()
.fold(StakeActivationStatus::default, |acc, stake_account| { .fold(StakeActivationStatus::default, |acc, stake_account| {
let delegation = stake_account.delegation(); let delegation = stake_account.delegation();
acc + delegation acc + delegation.stake_activating_and_deactivating(
.stake_activating_and_deactivating(self.epoch, Some(&self.stake_history)) self.epoch,
Some(&self.stake_history),
new_rate_activation_epoch,
)
}) })
.reduce(StakeActivationStatus::default, Add::add) .reduce(StakeActivationStatus::default, Add::add)
}); });
@ -296,6 +320,7 @@ impl Stakes<StakeAccount> {
&self.vote_accounts, &self.vote_accounts,
&stake_delegations, &stake_delegations,
&self.stake_history, &self.stake_history,
new_rate_activation_epoch,
); );
} }
@ -305,12 +330,15 @@ impl Stakes<StakeAccount> {
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
epoch: Epoch, epoch: Epoch,
stake_history: &StakeHistory, stake_history: &StakeHistory,
new_rate_activation_epoch: Option<Epoch>,
) -> u64 { ) -> u64 {
self.stake_delegations self.stake_delegations
.values() .values()
.map(StakeAccount::delegation) .map(StakeAccount::delegation)
.filter(|delegation| &delegation.voter_pubkey == voter_pubkey) .filter(|delegation| &delegation.voter_pubkey == voter_pubkey)
.map(|delegation| delegation.stake(epoch, Some(stake_history))) .map(|delegation| {
delegation.stake(epoch, Some(stake_history), new_rate_activation_epoch)
})
.sum() .sum()
} }
@ -327,40 +355,71 @@ impl Stakes<StakeAccount> {
self.vote_accounts.remove(vote_pubkey); self.vote_accounts.remove(vote_pubkey);
} }
fn remove_stake_delegation(&mut self, stake_pubkey: &Pubkey) { fn remove_stake_delegation(
&mut self,
stake_pubkey: &Pubkey,
new_rate_activation_epoch: Option<Epoch>,
) {
if let Some(stake_account) = self.stake_delegations.remove(stake_pubkey) { if let Some(stake_account) = self.stake_delegations.remove(stake_pubkey) {
let removed_delegation = stake_account.delegation(); let removed_delegation = stake_account.delegation();
let removed_stake = removed_delegation.stake(self.epoch, Some(&self.stake_history)); let removed_stake = removed_delegation.stake(
self.epoch,
Some(&self.stake_history),
new_rate_activation_epoch,
);
self.vote_accounts self.vote_accounts
.sub_stake(&removed_delegation.voter_pubkey, removed_stake); .sub_stake(&removed_delegation.voter_pubkey, removed_stake);
} }
} }
fn upsert_vote_account(&mut self, vote_pubkey: &Pubkey, vote_account: VoteAccount) { fn upsert_vote_account(
&mut self,
vote_pubkey: &Pubkey,
vote_account: VoteAccount,
new_rate_activation_epoch: Option<Epoch>,
) {
debug_assert_ne!(vote_account.lamports(), 0u64); debug_assert_ne!(vote_account.lamports(), 0u64);
debug_assert!(vote_account.is_deserialized()); debug_assert!(vote_account.is_deserialized());
// unconditionally remove existing at first; there is no dependent calculated state for // unconditionally remove existing at first; there is no dependent calculated state for
// votes, not like stakes (stake codepath maintains calculated stake value grouped by // votes, not like stakes (stake codepath maintains calculated stake value grouped by
// delegated vote pubkey) // delegated vote pubkey)
let stake = match self.vote_accounts.remove(vote_pubkey) { let stake = match self.vote_accounts.remove(vote_pubkey) {
None => self.calculate_stake(vote_pubkey, self.epoch, &self.stake_history), None => self.calculate_stake(
vote_pubkey,
self.epoch,
&self.stake_history,
new_rate_activation_epoch,
),
Some((stake, _)) => stake, Some((stake, _)) => stake,
}; };
let entry = (stake, vote_account); let entry = (stake, vote_account);
self.vote_accounts.insert(*vote_pubkey, entry); self.vote_accounts.insert(*vote_pubkey, entry);
} }
fn upsert_stake_delegation(&mut self, stake_pubkey: Pubkey, stake_account: StakeAccount) { fn upsert_stake_delegation(
&mut self,
stake_pubkey: Pubkey,
stake_account: StakeAccount,
new_rate_activation_epoch: Option<Epoch>,
) {
debug_assert_ne!(stake_account.lamports(), 0u64); debug_assert_ne!(stake_account.lamports(), 0u64);
let delegation = stake_account.delegation(); let delegation = stake_account.delegation();
let voter_pubkey = delegation.voter_pubkey; let voter_pubkey = delegation.voter_pubkey;
let stake = delegation.stake(self.epoch, Some(&self.stake_history)); let stake = delegation.stake(
self.epoch,
Some(&self.stake_history),
new_rate_activation_epoch,
);
match self.stake_delegations.insert(stake_pubkey, stake_account) { match self.stake_delegations.insert(stake_pubkey, stake_account) {
None => self.vote_accounts.add_stake(&voter_pubkey, stake), None => self.vote_accounts.add_stake(&voter_pubkey, stake),
Some(old_stake_account) => { Some(old_stake_account) => {
let old_delegation = old_stake_account.delegation(); let old_delegation = old_stake_account.delegation();
let old_voter_pubkey = old_delegation.voter_pubkey; let old_voter_pubkey = old_delegation.voter_pubkey;
let old_stake = old_delegation.stake(self.epoch, Some(&self.stake_history)); let old_stake = old_delegation.stake(
self.epoch,
Some(&self.stake_history),
new_rate_activation_epoch,
);
if voter_pubkey != old_voter_pubkey || stake != old_stake { if voter_pubkey != old_voter_pubkey || stake != old_stake {
self.vote_accounts.sub_stake(&old_voter_pubkey, old_stake); self.vote_accounts.sub_stake(&old_voter_pubkey, old_stake);
self.vote_accounts.add_stake(&voter_pubkey, stake); self.vote_accounts.add_stake(&voter_pubkey, stake);
@ -369,7 +428,12 @@ impl Stakes<StakeAccount> {
} }
} }
fn update_stake_accounts(&mut self, thread_pool: &ThreadPool, stake_rewards: &[StakeReward]) { fn update_stake_accounts(
&mut self,
thread_pool: &ThreadPool,
stake_rewards: &[StakeReward],
new_rate_activation_epoch: Option<Epoch>,
) {
let stake_delegations: Vec<_> = thread_pool.install(|| { let stake_delegations: Vec<_> = thread_pool.install(|| {
stake_rewards stake_rewards
.into_par_iter() .into_par_iter()
@ -393,6 +457,7 @@ impl Stakes<StakeAccount> {
&self.vote_accounts, &self.vote_accounts,
&stake_delegations, &stake_delegations,
&self.stake_history, &self.stake_history,
new_rate_activation_epoch,
); );
} }
@ -509,6 +574,7 @@ fn refresh_vote_accounts(
vote_accounts: &VoteAccounts, vote_accounts: &VoteAccounts,
stake_delegations: &[&StakeAccount], stake_delegations: &[&StakeAccount],
stake_history: &StakeHistory, stake_history: &StakeHistory,
new_rate_activation_epoch: Option<Epoch>,
) -> VoteAccounts { ) -> VoteAccounts {
type StakesHashMap = HashMap</*voter:*/ Pubkey, /*stake:*/ u64>; type StakesHashMap = HashMap</*voter:*/ Pubkey, /*stake:*/ u64>;
fn merge(mut stakes: StakesHashMap, other: StakesHashMap) -> StakesHashMap { fn merge(mut stakes: StakesHashMap, other: StakesHashMap) -> StakesHashMap {
@ -527,7 +593,7 @@ fn refresh_vote_accounts(
.fold(HashMap::default, |mut delegated_stakes, stake_account| { .fold(HashMap::default, |mut delegated_stakes, stake_account| {
let delegation = stake_account.delegation(); let delegation = stake_account.delegation();
let entry = delegated_stakes.entry(delegation.voter_pubkey).or_default(); let entry = delegated_stakes.entry(delegation.voter_pubkey).or_default();
*entry += delegation.stake(epoch, stake_history); *entry += delegation.stake(epoch, stake_history, new_rate_activation_epoch);
delegated_stakes delegated_stakes
}) })
.reduce(HashMap::default, merge) .reduce(HashMap::default, merge)
@ -631,8 +697,8 @@ pub(crate) mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
let stake = stake_state::stake_from(&stake_account).unwrap(); let stake = stake_state::stake_from(&stake_account).unwrap();
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -640,26 +706,26 @@ pub(crate) mod tests {
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey), vote_accounts.get_delegated_stake(&vote_pubkey),
stake.stake(i, None) stake.stake(i, None, None)
); );
} }
stake_account.set_lamports(42); stake_account.set_lamports(42);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey), vote_accounts.get_delegated_stake(&vote_pubkey),
stake.stake(i, None) stake.stake(i, None, None)
); // stays old stake, because only 10 is activated ); // stays old stake, because only 10 is activated
} }
// activate more // activate more
let mut stake_account = let mut stake_account =
create_stake_account(42, &vote_pubkey, &solana_sdk::pubkey::new_rand()); create_stake_account(42, &vote_pubkey, &solana_sdk::pubkey::new_rand());
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
let stake = stake_state::stake_from(&stake_account).unwrap(); let stake = stake_state::stake_from(&stake_account).unwrap();
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -667,12 +733,12 @@ pub(crate) mod tests {
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey), vote_accounts.get_delegated_stake(&vote_pubkey),
stake.stake(i, None) stake.stake(i, None, None)
); // now stake of 42 is activated ); // now stake of 42 is activated
} }
stake_account.set_lamports(0); stake_account.set_lamports(0);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -691,14 +757,14 @@ pub(crate) mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) = let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) =
create_staked_node_accounts(20); create_staked_node_accounts(20);
stakes_cache.check_and_store(&vote11_pubkey, &vote11_account); stakes_cache.check_and_store(&vote11_pubkey, &vote11_account, None);
stakes_cache.check_and_store(&stake11_pubkey, &stake11_account); stakes_cache.check_and_store(&stake11_pubkey, &stake11_account, None);
let vote11_node_pubkey = vote_state::from(&vote11_account).unwrap().node_pubkey; let vote11_node_pubkey = vote_state::from(&vote11_account).unwrap().node_pubkey;
@ -716,8 +782,8 @@ pub(crate) mod tests {
let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -727,7 +793,7 @@ pub(crate) mod tests {
} }
vote_account.set_lamports(0); vote_account.set_lamports(0);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -737,7 +803,7 @@ pub(crate) mod tests {
} }
vote_account.set_lamports(1); vote_account.set_lamports(1);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -751,7 +817,7 @@ pub(crate) mod tests {
let mut pushed = vote_account.data().to_vec(); let mut pushed = vote_account.data().to_vec();
pushed.push(0); pushed.push(0);
vote_account.set_data(pushed); vote_account.set_data(pushed);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -764,7 +830,7 @@ pub(crate) mod tests {
let default_vote_state = VoteState::default(); let default_vote_state = VoteState::default();
let versioned = VoteStateVersions::new_current(default_vote_state); let versioned = VoteStateVersions::new_current(default_vote_state);
vote_state::to(&versioned, &mut vote_account).unwrap(); vote_state::to(&versioned, &mut vote_account).unwrap();
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -774,7 +840,7 @@ pub(crate) mod tests {
} }
vote_account.set_data(cache_data); vote_account.set_data(cache_data);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -797,11 +863,11 @@ pub(crate) mod tests {
let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) = let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&vote_pubkey2, &vote_account2); stakes_cache.check_and_store(&vote_pubkey2, &vote_account2, None);
// delegates to vote_pubkey // delegates to vote_pubkey
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
let stake = stake_state::stake_from(&stake_account).unwrap(); let stake = stake_state::stake_from(&stake_account).unwrap();
@ -811,14 +877,14 @@ pub(crate) mod tests {
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey), vote_accounts.get_delegated_stake(&vote_pubkey),
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), None)
); );
assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey2), 0); assert_eq!(vote_accounts.get_delegated_stake(&vote_pubkey2), 0);
} }
// delegates to vote_pubkey2 // delegates to vote_pubkey2
stakes_cache.check_and_store(&stake_pubkey, &stake_account2); stakes_cache.check_and_store(&stake_pubkey, &stake_account2, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -828,7 +894,7 @@ pub(crate) mod tests {
assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey2), vote_accounts.get_delegated_stake(&vote_pubkey2),
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), None)
); );
} }
} }
@ -845,11 +911,11 @@ pub(crate) mod tests {
let stake_pubkey2 = solana_sdk::pubkey::new_rand(); let stake_pubkey2 = solana_sdk::pubkey::new_rand();
let stake_account2 = create_stake_account(10, &vote_pubkey, &stake_pubkey2); let stake_account2 = create_stake_account(10, &vote_pubkey, &stake_pubkey2);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
// delegates to vote_pubkey // delegates to vote_pubkey
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
stakes_cache.check_and_store(&stake_pubkey2, &stake_account2); stakes_cache.check_and_store(&stake_pubkey2, &stake_account2, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -866,8 +932,8 @@ pub(crate) mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
let stake = stake_state::stake_from(&stake_account).unwrap(); let stake = stake_state::stake_from(&stake_account).unwrap();
{ {
@ -875,17 +941,17 @@ pub(crate) mod tests {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey), vote_accounts.get_delegated_stake(&vote_pubkey),
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), None)
); );
} }
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap(); let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
stakes_cache.activate_epoch(3, &thread_pool); stakes_cache.activate_epoch(3, &thread_pool, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert_eq!( assert_eq!(
vote_accounts.get_delegated_stake(&vote_pubkey), vote_accounts.get_delegated_stake(&vote_pubkey),
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), None)
); );
} }
} }
@ -900,8 +966,8 @@ pub(crate) mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -914,6 +980,7 @@ pub(crate) mod tests {
stakes_cache.check_and_store( stakes_cache.check_and_store(
&stake_pubkey, &stake_pubkey,
&AccountSharedData::new(1, 0, &stake::program::id()), &AccountSharedData::new(1, 0, &stake::program::id()),
None,
); );
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -951,8 +1018,8 @@ pub(crate) mod tests {
let genesis_epoch = 0; let genesis_epoch = 0;
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_warming_staked_node_accounts(10, genesis_epoch); create_warming_staked_node_accounts(10, genesis_epoch);
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
{ {
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -962,7 +1029,7 @@ pub(crate) mod tests {
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap(); let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
for (epoch, expected_warmed_stake) in ((genesis_epoch + 1)..=3).zip(&[2, 3, 4]) { for (epoch, expected_warmed_stake) in ((genesis_epoch + 1)..=3).zip(&[2, 3, 4]) {
stakes_cache.activate_epoch(epoch, &thread_pool); stakes_cache.activate_epoch(epoch, &thread_pool, None);
// vote_balance_and_staked() always remain to return same lamports // vote_balance_and_staked() always remain to return same lamports
// while vote_balance_and_warmed_staked() gradually increases // while vote_balance_and_warmed_staked() gradually increases
let stakes = stakes_cache.stakes(); let stakes = stakes_cache.stakes();
@ -997,7 +1064,7 @@ pub(crate) mod tests {
rng.gen_range(0, 101), // commission rng.gen_range(0, 101), // commission
rng.gen_range(0, 1_000_000), // lamports rng.gen_range(0, 1_000_000), // lamports
); );
stakes_cache.check_and_store(&vote_pubkey, &vote_account); stakes_cache.check_and_store(&vote_pubkey, &vote_account, None);
for _ in 0..rng.gen_range(10usize, 20) { for _ in 0..rng.gen_range(10usize, 20) {
let stake_pubkey = solana_sdk::pubkey::new_rand(); let stake_pubkey = solana_sdk::pubkey::new_rand();
let rent = Rent::with_slots_per_epoch(rng.gen()); let rent = Rent::with_slots_per_epoch(rng.gen());
@ -1008,7 +1075,7 @@ pub(crate) mod tests {
&rent, &rent,
rng.gen_range(0, 1_000_000), // lamports rng.gen_range(0, 1_000_000), // lamports
); );
stakes_cache.check_and_store(&stake_pubkey, &stake_account); stakes_cache.check_and_store(&stake_pubkey, &stake_account, None);
} }
} }
let stakes: Stakes<StakeAccount> = stakes_cache.stakes().clone(); let stakes: Stakes<StakeAccount> = stakes_cache.stakes().clone();

View File

@ -1,4 +1,5 @@
#![allow(clippy::integer_arithmetic)] #![allow(clippy::integer_arithmetic)]
use { use {
solana_runtime::{ solana_runtime::{
bank::Bank, bank::Bank,
@ -10,6 +11,7 @@ use {
account::from_account, account::from_account,
account_utils::StateMut, account_utils::StateMut,
client::SyncClient, client::SyncClient,
epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH},
hash::Hash, hash::Hash,
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
@ -98,6 +100,7 @@ fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
) )
.unwrap(), .unwrap(),
), ),
bank.new_warmup_cooldown_rate_epoch(),
) )
} }
@ -112,6 +115,7 @@ fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 {
) )
.unwrap(), .unwrap(),
), ),
bank.new_warmup_cooldown_rate_epoch(),
) )
} }
@ -292,6 +296,7 @@ fn test_stake_account_lifetime() {
&solana_sdk::pubkey::new_rand(), &solana_sdk::pubkey::new_rand(),
2_000_000_000, 2_000_000_000,
); );
genesis_config.epoch_schedule = EpochSchedule::new(MINIMUM_SLOTS_PER_EPOCH);
genesis_config.rent = Rent::default(); genesis_config.rent = Rent::default();
let bank = Bank::new_for_tests(&genesis_config); let bank = Bank::new_for_tests(&genesis_config);
let mint_pubkey = mint_keypair.pubkey(); let mint_pubkey = mint_keypair.pubkey();

View File

@ -90,8 +90,8 @@ fn deprecated_id_to_tokens(
#[cfg(test)] #[cfg(test)]
#[test] #[test]
fn test_id() { #[allow(deprecated)]
#[allow(deprecated)] fn test_id() {
assert!(check_id(&id())); assert!(check_id(&id()));
} }
}); });

View File

@ -575,6 +575,7 @@ pub mod sdk_ids {
vote::program::id(), vote::program::id(),
feature::id(), feature::id(),
bpf_loader_deprecated::id(), bpf_loader_deprecated::id(),
#[allow(deprecated)]
stake::config::id(), stake::config::id(),
]; ];
sdk_ids.extend(sysvar::ALL_IDS.iter()); sdk_ids.extend(sysvar::ALL_IDS.iter());

View File

@ -1,15 +1,18 @@
//! config for staking //! config for staking
//! carries variables that the stake program cares about //! carries variables that the stake program cares about
use serde_derive::{Deserialize, Serialize};
use {
super::state::{DEFAULT_SLASH_PENALTY, DEFAULT_WARMUP_COOLDOWN_RATE},
serde_derive::{Deserialize, Serialize},
};
// stake config ID // stake config ID
crate::declare_id!("StakeConfig11111111111111111111111111111111"); crate::declare_deprecated_id!("StakeConfig11111111111111111111111111111111");
// means that no more than RATE of current effective stake may be added or subtracted per
// epoch
pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
)]
#[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/deactivate 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

View File

@ -1,3 +1,5 @@
#[allow(deprecated)]
use crate::stake::config;
use { use {
crate::{ crate::{
clock::{Epoch, UnixTimestamp}, clock::{Epoch, UnixTimestamp},
@ -5,7 +7,6 @@ use {
instruction::{AccountMeta, Instruction}, instruction::{AccountMeta, Instruction},
pubkey::Pubkey, pubkey::Pubkey,
stake::{ stake::{
config,
program::id, program::id,
state::{Authorized, Lockup, StakeAuthorize, StakeState}, state::{Authorized, Lockup, StakeAuthorize, StakeState},
}, },
@ -676,6 +677,7 @@ pub fn delegate_stake(
AccountMeta::new_readonly(*vote_pubkey, false), AccountMeta::new_readonly(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false),
#[allow(deprecated)]
AccountMeta::new_readonly(config::id(), false), AccountMeta::new_readonly(config::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true), AccountMeta::new_readonly(*authorized_pubkey, true),
]; ];
@ -780,6 +782,7 @@ fn _redelegate(
AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*stake_pubkey, false),
AccountMeta::new(*uninitialized_stake_pubkey, false), AccountMeta::new(*uninitialized_stake_pubkey, false),
AccountMeta::new_readonly(*vote_pubkey, false), AccountMeta::new_readonly(*vote_pubkey, false),
#[allow(deprecated)]
AccountMeta::new_readonly(config::id(), false), AccountMeta::new_readonly(config::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true), AccountMeta::new_readonly(*authorized_pubkey, true),
]; ];

View File

@ -2,6 +2,7 @@
//! //!
//! [np]: https://docs.solana.com/developing/runtime-facilities/sysvars#stakehistory //! [np]: https://docs.solana.com/developing/runtime-facilities/sysvars#stakehistory
#[allow(deprecated)]
pub mod config; pub mod config;
pub mod instruction; pub mod instruction;
pub mod stake_flags; pub mod stake_flags;

View File

@ -1,11 +1,11 @@
#![allow(clippy::integer_arithmetic)] #![allow(clippy::integer_arithmetic)]
use { use {
crate::{ crate::{
clock::{Clock, Epoch, UnixTimestamp}, clock::{Clock, Epoch, UnixTimestamp},
instruction::InstructionError, instruction::InstructionError,
pubkey::Pubkey, pubkey::Pubkey,
stake::{ stake::{
config::Config,
instruction::{LockupArgs, StakeError}, instruction::{LockupArgs, StakeError},
stake_flags::StakeFlags, stake_flags::StakeFlags,
}, },
@ -17,6 +17,20 @@ use {
pub type StakeActivationStatus = StakeHistoryEntry; pub type StakeActivationStatus = StakeHistoryEntry;
// means that no more than RATE of current effective stake may be added or subtracted per
// epoch
pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
pub const NEW_WARMUP_COOLDOWN_RATE: f64 = 0.09;
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
pub fn warmup_cooldown_rate(current_epoch: Epoch, new_rate_activation_epoch: Option<Epoch>) -> f64 {
if current_epoch < new_rate_activation_epoch.unwrap_or(u64::MAX) {
DEFAULT_WARMUP_COOLDOWN_RATE
} else {
NEW_WARMUP_COOLDOWN_RATE
}
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum StakeState { pub enum StakeState {
@ -311,33 +325,32 @@ pub struct Delegation {
/// 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,
/// how much stake we can activate per-epoch as a fraction of currently effective stake /// how much stake we can activate per-epoch as a fraction of currently effective stake
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
)]
pub warmup_cooldown_rate: f64, pub warmup_cooldown_rate: f64,
} }
impl Default for Delegation { impl Default for Delegation {
fn default() -> Self { fn default() -> Self {
#[allow(deprecated)]
Self { Self {
voter_pubkey: Pubkey::default(), voter_pubkey: Pubkey::default(),
stake: 0, stake: 0,
activation_epoch: 0, activation_epoch: 0,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
warmup_cooldown_rate: Config::default().warmup_cooldown_rate, warmup_cooldown_rate: 0.0,
} }
} }
} }
impl Delegation { impl Delegation {
pub fn new( pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self {
voter_pubkey: &Pubkey,
stake: u64,
activation_epoch: Epoch,
warmup_cooldown_rate: f64,
) -> Self {
Self { Self {
voter_pubkey: *voter_pubkey, voter_pubkey: *voter_pubkey,
stake, stake,
activation_epoch, activation_epoch,
warmup_cooldown_rate,
..Delegation::default() ..Delegation::default()
} }
} }
@ -345,8 +358,13 @@ impl Delegation {
self.activation_epoch == std::u64::MAX self.activation_epoch == std::u64::MAX
} }
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { pub fn stake(
self.stake_activating_and_deactivating(epoch, history) &self,
epoch: Epoch,
history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> u64 {
self.stake_activating_and_deactivating(epoch, history, new_rate_activation_epoch)
.effective .effective
} }
@ -355,9 +373,11 @@ impl Delegation {
&self, &self,
target_epoch: Epoch, target_epoch: Epoch,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> StakeActivationStatus { ) -> StakeActivationStatus {
// first, calculate an effective and activating stake // first, calculate an effective and activating stake
let (effective_stake, activating_stake) = self.stake_and_activating(target_epoch, history); let (effective_stake, activating_stake) =
self.stake_and_activating(target_epoch, history, new_rate_activation_epoch);
// then de-activate some portion if necessary // then de-activate some portion if necessary
if target_epoch < self.deactivation_epoch { if target_epoch < self.deactivation_epoch {
@ -404,10 +424,12 @@ impl Delegation {
// this account is entitled to take // this account is entitled to take
let weight = let weight =
current_effective_stake as f64 / prev_cluster_stake.deactivating as f64; current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
let warmup_cooldown_rate =
warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
// portion of newly not-effective cluster stake I'm entitled to at current epoch // portion of newly not-effective cluster stake I'm entitled to at current epoch
let newly_not_effective_cluster_stake = let newly_not_effective_cluster_stake =
prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
let newly_not_effective_stake = let newly_not_effective_stake =
((weight * newly_not_effective_cluster_stake) as u64).max(1); ((weight * newly_not_effective_cluster_stake) as u64).max(1);
@ -441,6 +463,7 @@ impl Delegation {
&self, &self,
target_epoch: Epoch, target_epoch: Epoch,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> (u64, u64) { ) -> (u64, u64) {
let delegated_stake = self.stake; let delegated_stake = self.stake;
@ -489,10 +512,12 @@ impl Delegation {
let remaining_activating_stake = delegated_stake - current_effective_stake; let remaining_activating_stake = delegated_stake - current_effective_stake;
let weight = let weight =
remaining_activating_stake as f64 / prev_cluster_stake.activating as f64; remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
let warmup_cooldown_rate =
warmup_cooldown_rate(current_epoch, new_rate_activation_epoch);
// portion of newly effective cluster stake I'm entitled to at current epoch // portion of newly effective cluster stake I'm entitled to at current epoch
let newly_effective_cluster_stake = let newly_effective_cluster_stake =
prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; prev_cluster_stake.effective as f64 * warmup_cooldown_rate;
let newly_effective_stake = let newly_effective_stake =
((weight * newly_effective_cluster_stake) as u64).max(1); ((weight * newly_effective_cluster_stake) as u64).max(1);
@ -544,8 +569,14 @@ pub struct Stake {
} }
impl Stake { impl Stake {
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { pub fn stake(
self.delegation.stake(epoch, history) &self,
epoch: Epoch,
history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> u64 {
self.delegation
.stake(epoch, history, new_rate_activation_epoch)
} }
pub fn split( pub fn split(
@ -628,7 +659,7 @@ mod test {
stake: u64::MAX, stake: u64::MAX,
activation_epoch: Epoch::MAX, activation_epoch: Epoch::MAX,
deactivation_epoch: Epoch::MAX, deactivation_epoch: Epoch::MAX,
warmup_cooldown_rate: f64::MAX, ..Delegation::default()
}, },
credits_observed: 1, credits_observed: 1,
}, },
@ -663,7 +694,7 @@ mod test {
stake: u64::MAX, stake: u64::MAX,
activation_epoch: Epoch::MAX, activation_epoch: Epoch::MAX,
deactivation_epoch: Epoch::MAX, deactivation_epoch: Epoch::MAX,
warmup_cooldown_rate: f64::MAX, ..Default::default()
}, },
credits_observed: 1, credits_observed: 1,
}, },

View File

@ -672,6 +672,21 @@ pub mod last_restart_slot_sysvar {
solana_sdk::declare_id!("HooKD5NC9QNxk25QuzCssB8ecrEzGt6eXEPBUxWp1LaR"); solana_sdk::declare_id!("HooKD5NC9QNxk25QuzCssB8ecrEzGt6eXEPBUxWp1LaR");
} }
pub mod reduce_stake_warmup_cooldown {
use solana_program::{epoch_schedule::EpochSchedule, stake_history::Epoch};
solana_sdk::declare_id!("GwtDQBghCTBgmX2cpEGNPxTEBUTQRaDMGTr5qychdGMj");
pub trait NewWarmupCooldownRateEpoch {
fn new_warmup_cooldown_rate_epoch(&self, epoch_schedule: &EpochSchedule) -> Option<Epoch>;
}
impl NewWarmupCooldownRateEpoch for super::FeatureSet {
fn new_warmup_cooldown_rate_epoch(&self, epoch_schedule: &EpochSchedule) -> Option<Epoch> {
self.activated_slot(&id())
.map(|slot| epoch_schedule.get_epoch(slot))
}
}
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -835,6 +850,7 @@ lazy_static! {
(checked_arithmetic_in_fee_validation::id(), "checked arithmetic in fee validation #31273"), (checked_arithmetic_in_fee_validation::id(), "checked arithmetic in fee validation #31273"),
(bpf_account_data_direct_mapping::id(), "use memory regions to map account data into the rbpf vm instead of copying the data"), (bpf_account_data_direct_mapping::id(), "use memory regions to map account data into the rbpf vm instead of copying the data"),
(last_restart_slot_sysvar::id(), "enable new sysvar last_restart_slot"), (last_restart_slot_sysvar::id(), "enable new sysvar last_restart_slot"),
(reduce_stake_warmup_cooldown::id(), "reduce stake warmup cooldown from 25% to 9%"),
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()