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_address_lookup_table;
pub mod parse_bpf_loader;
#[allow(deprecated)]
pub mod parse_config;
pub mod parse_nonce;
pub mod parse_stake;

View File

@ -8,7 +8,9 @@ use {
solana_config_program::{get_config_data, ConfigKeys},
solana_sdk::{
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,
}
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiStakeConfig {

View File

@ -119,11 +119,16 @@ pub struct UiDelegation {
pub stake: StringAmount,
pub activation_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,
}
impl From<Delegation> for UiDelegation {
fn from(delegation: Delegation) -> Self {
#[allow(deprecated)]
Self {
voter: delegation.voter_pubkey.to_string(),
stake: delegation.stake.to_string(),
@ -139,6 +144,7 @@ mod test {
use {super::*, bincode::serialize, solana_sdk::stake::stake_flags::StakeFlags};
#[test]
#[allow(deprecated)]
fn test_parse_stake() {
let stake_state = StakeState::Uninitialized;
let stake_data = serialize(&stake_state).unwrap();

View File

@ -2,6 +2,7 @@ use {
crate::{
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
compute_unit_price::WithComputeUnitPrice,
feature::get_feature_activation_epoch,
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
},
clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand},
@ -43,6 +44,7 @@ use {
clock::{self, Clock, Slot},
commitment_config::CommitmentConfig,
epoch_schedule::Epoch,
feature_set,
hash::Hash,
message::Message,
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(|| {
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![];
for (stake_pubkey, stake_account) in all_stake_accounts {
@ -1815,6 +1819,7 @@ pub fn process_show_stakes(
use_lamports_unit,
&stake_history,
&clock,
new_rate_activation_epoch,
),
});
}
@ -1833,6 +1838,7 @@ pub fn process_show_stakes(
use_lamports_unit,
&stake_history,
&clock,
new_rate_activation_epoch,
),
});
}

View File

@ -22,6 +22,7 @@ use {
genesis_config::ClusterType,
message::Message,
pubkey::Pubkey,
stake_history::Epoch,
transaction::Transaction,
},
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(_))))
}
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(
rpc_client: &RpcClient,
config: &CliConfig,

View File

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

View File

@ -38,13 +38,13 @@ use {
pubkey::Pubkey,
signature::{Keypair, Signer},
stake::{
config as stake_config, instruction as stake_instruction,
instruction as stake_instruction,
state::{Authorized, Lockup},
},
system_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_tpu_client::tpu_client::{
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
@ -267,18 +267,6 @@ impl LocalCluster {
.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_contact_info = leader_node.info.clone();
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() {
solana_logger::setup_with_default(RUST_LOG_FILTER);
let validator_config = ValidatorConfig::default_for_test();
let slots_per_epoch = MINIMUM_SLOTS_PER_EPOCH;
let mut config = ClusterConfig {
cluster_lamports: DEFAULT_CLUSTER_LAMPORTS,
node_stakes: vec![DEFAULT_NODE_STAKE; 4],
validator_configs: make_identical_validator_configs(&validator_config, 4),
slots_per_epoch,
stakers_slot_offset: slots_per_epoch,
..ClusterConfig::default()
};
let cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified);

View File

@ -278,7 +278,7 @@ async fn stake_rewards_from_warp() {
assert_eq!(
stake
.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),
);
}
@ -394,7 +394,7 @@ async fn stake_rewards_filter_bench_core(num_stake_accounts: u64) {
assert_eq!(
stake
.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),
);
}

View File

@ -4,6 +4,8 @@ pub mod config_processor;
pub mod date_instruction;
pub use solana_sdk::config::program::id;
#[allow(deprecated)]
use solana_sdk::stake::config::Config as StakeConfig;
use {
bincode::{deserialize, serialize, serialized_size},
serde_derive::{Deserialize, Serialize},
@ -11,7 +13,6 @@ use {
account::{Account, AccountSharedData},
pubkey::Pubkey,
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
#[allow(deprecated)]
impl ConfigState for StakeConfig {
fn max_space() -> u64 {
serialized_size(&StakeConfig::default()).unwrap()

View File

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

View File

@ -28,17 +28,18 @@ use {
UiAccount, UiAccountEncoding,
},
solana_rpc_client_api::{
client_error::Result as ClientResult,
client_error::{Error as ClientError, ErrorKind, Result as ClientResult},
config::{RpcAccountInfoConfig, *},
request::{RpcRequest, TokenAccountsFilter},
response::*,
},
solana_sdk::{
account::Account,
account::{Account, ReadableAccount},
clock::{Epoch, Slot, UnixTimestamp},
commitment_config::CommitmentConfig,
epoch_info::EpochInfo,
epoch_schedule::EpochSchedule,
feature::Feature,
fee_calculator::{FeeCalculator, FeeRateGovernor},
hash::Hash,
message::{v0, Message as LegacyMessage},
@ -4027,6 +4028,18 @@ impl RpcClient {
(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> {
// `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

View File

@ -1760,12 +1760,17 @@ impl JsonRpcRequestProcessor {
let stake_history =
solana_sdk::account::from_account::<StakeHistory, _>(&stake_history_account)
.ok_or_else(Error::internal_error)?;
let new_rate_activation_epoch = bank.new_warmup_cooldown_rate_epoch();
let StakeActivationStatus {
effective,
activating,
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 {
StakeActivationState::Deactivating
} else if activating > 0 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,18 @@
//! config for staking
//! 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
crate::declare_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;
crate::declare_deprecated_id!("StakeConfig11111111111111111111111111111111");
#[deprecated(
since = "1.16.7",
note = "Please use `solana_sdk::stake::state::warmup_cooldown_rate()` instead"
)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Config {
/// 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 {
crate::{
clock::{Epoch, UnixTimestamp},
@ -5,7 +7,6 @@ use {
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
stake::{
config,
program::id,
state::{Authorized, Lockup, StakeAuthorize, StakeState},
},
@ -676,6 +677,7 @@ pub fn delegate_stake(
AccountMeta::new_readonly(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
#[allow(deprecated)]
AccountMeta::new_readonly(config::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];
@ -780,6 +782,7 @@ fn _redelegate(
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new(*uninitialized_stake_pubkey, false),
AccountMeta::new_readonly(*vote_pubkey, false),
#[allow(deprecated)]
AccountMeta::new_readonly(config::id(), false),
AccountMeta::new_readonly(*authorized_pubkey, true),
];

View File

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

View File

@ -1,11 +1,11 @@
#![allow(clippy::integer_arithmetic)]
use {
crate::{
clock::{Clock, Epoch, UnixTimestamp},
instruction::InstructionError,
pubkey::Pubkey,
stake::{
config::Config,
instruction::{LockupArgs, StakeError},
stake_flags::StakeFlags,
},
@ -17,6 +17,20 @@ use {
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)]
#[allow(clippy::large_enum_variant)]
pub enum StakeState {
@ -311,33 +325,32 @@ pub struct Delegation {
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
pub deactivation_epoch: Epoch,
/// 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,
}
impl Default for Delegation {
fn default() -> Self {
#[allow(deprecated)]
Self {
voter_pubkey: Pubkey::default(),
stake: 0,
activation_epoch: 0,
deactivation_epoch: std::u64::MAX,
warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
warmup_cooldown_rate: 0.0,
}
}
}
impl Delegation {
pub fn new(
voter_pubkey: &Pubkey,
stake: u64,
activation_epoch: Epoch,
warmup_cooldown_rate: f64,
) -> Self {
pub fn new(voter_pubkey: &Pubkey, stake: u64, activation_epoch: Epoch) -> Self {
Self {
voter_pubkey: *voter_pubkey,
stake,
activation_epoch,
warmup_cooldown_rate,
..Delegation::default()
}
}
@ -345,8 +358,13 @@ impl Delegation {
self.activation_epoch == std::u64::MAX
}
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
self.stake_activating_and_deactivating(epoch, history)
pub fn stake(
&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
}
@ -355,9 +373,11 @@ impl Delegation {
&self,
target_epoch: Epoch,
history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> StakeActivationStatus {
// 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
if target_epoch < self.deactivation_epoch {
@ -404,10 +424,12 @@ impl Delegation {
// this account is entitled to take
let weight =
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
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 =
((weight * newly_not_effective_cluster_stake) as u64).max(1);
@ -441,6 +463,7 @@ impl Delegation {
&self,
target_epoch: Epoch,
history: Option<&StakeHistory>,
new_rate_activation_epoch: Option<Epoch>,
) -> (u64, u64) {
let delegated_stake = self.stake;
@ -489,10 +512,12 @@ impl Delegation {
let remaining_activating_stake = delegated_stake - current_effective_stake;
let weight =
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
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 =
((weight * newly_effective_cluster_stake) as u64).max(1);
@ -544,8 +569,14 @@ pub struct Stake {
}
impl Stake {
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
self.delegation.stake(epoch, history)
pub fn stake(
&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(
@ -628,7 +659,7 @@ mod test {
stake: u64::MAX,
activation_epoch: Epoch::MAX,
deactivation_epoch: Epoch::MAX,
warmup_cooldown_rate: f64::MAX,
..Delegation::default()
},
credits_observed: 1,
},
@ -663,7 +694,7 @@ mod test {
stake: u64::MAX,
activation_epoch: Epoch::MAX,
deactivation_epoch: Epoch::MAX,
warmup_cooldown_rate: f64::MAX,
..Default::default()
},
credits_observed: 1,
},

View File

@ -672,6 +672,21 @@ pub mod last_restart_slot_sysvar {
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! {
/// Map of feature identifiers to user-visible description
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"),
(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"),
(reduce_stake_warmup_cooldown::id(), "reduce stake warmup cooldown from 25% to 9%"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()