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:
parent
5c86f89bc7
commit
fa3506631a
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue