Feature: filter stake by min delegation for rewards (#29618)
filter stake by min delegation for rewards
This commit is contained in:
parent
d351c58ff4
commit
d693167dfb
|
@ -6069,6 +6069,7 @@ dependencies = [
|
|||
"solana-program-runtime",
|
||||
"solana-runtime",
|
||||
"solana-sdk 1.15.0",
|
||||
"solana-stake-program",
|
||||
"solana-vote-program",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
|
|
@ -27,3 +27,6 @@ solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
|||
solana-vote-program = { path = "../programs/vote", version = "=1.15.0" }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.15.0" }
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
bincode::deserialize,
|
||||
log::debug,
|
||||
solana_banks_client::BanksClient,
|
||||
solana_program_test::{
|
||||
processor, ProgramTest, ProgramTestBanksClientExt, ProgramTestContext, ProgramTestError,
|
||||
},
|
||||
solana_sdk::{
|
||||
account::Account,
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
|
@ -26,9 +28,10 @@ use {
|
|||
},
|
||||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
solana_stake_program::stake_state,
|
||||
solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{VoteInit, VoteState},
|
||||
vote_state::{self, VoteInit, VoteState},
|
||||
},
|
||||
std::convert::TryInto,
|
||||
};
|
||||
|
@ -276,6 +279,122 @@ async fn stake_rewards_from_warp() {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stake_rewards_filter_bench_100() {
|
||||
stake_rewards_filter_bench_core(100).await;
|
||||
}
|
||||
|
||||
async fn stake_rewards_filter_bench_core(num_stake_accounts: u64) {
|
||||
// Initialize and start the test network
|
||||
let mut program_test = ProgramTest::default();
|
||||
|
||||
// create vote account
|
||||
let vote_address = Pubkey::new_unique();
|
||||
let node_address = Pubkey::new_unique();
|
||||
|
||||
let vote_account = vote_state::create_account(&vote_address, &node_address, 0, 1_000_000_000);
|
||||
program_test.add_account(vote_address, vote_account.clone().into());
|
||||
|
||||
// create stake accounts with 0.9 sol to test min-stake filtering
|
||||
const TEST_FILTER_STAKE: u64 = 900_000_000; // 0.9 sol
|
||||
let mut to_filter = vec![];
|
||||
for i in 0..num_stake_accounts {
|
||||
let stake_pubkey = Pubkey::new_unique();
|
||||
let stake_account = Account::from(stake_state::create_account(
|
||||
&stake_pubkey,
|
||||
&vote_address,
|
||||
&vote_account,
|
||||
&Rent::default(),
|
||||
TEST_FILTER_STAKE,
|
||||
));
|
||||
program_test.add_account(stake_pubkey, stake_account);
|
||||
to_filter.push(stake_pubkey);
|
||||
if i % 100 == 0 {
|
||||
debug!("create stake account {} {}", i, stake_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
let mut context = program_test.start_with_context().await;
|
||||
|
||||
let stake_lamports = 2_000_000_000_000;
|
||||
|
||||
let user_keypair = Keypair::new();
|
||||
let stake_address =
|
||||
setup_stake(&mut context, &user_keypair, &vote_address, stake_lamports).await;
|
||||
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
assert_eq!(account.lamports, stake_lamports);
|
||||
|
||||
// warp one epoch forward for normal inflation, no rewards collected
|
||||
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
|
||||
context.warp_to_slot(first_normal_slot).unwrap();
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
assert_eq!(account.lamports, stake_lamports);
|
||||
|
||||
context.increment_vote_account_credits(&vote_address, 100);
|
||||
|
||||
// go forward and see that rewards have been distributed
|
||||
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||
context
|
||||
.warp_to_slot(first_normal_slot + slots_per_epoch)
|
||||
.unwrap();
|
||||
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
assert!(account.lamports > stake_lamports);
|
||||
|
||||
// check that filtered stake accounts are excluded from receiving epoch rewards
|
||||
for stake_address in to_filter {
|
||||
let account = context
|
||||
.banks_client
|
||||
.get_account(stake_address)
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
assert_eq!(account.lamports, TEST_FILTER_STAKE);
|
||||
}
|
||||
|
||||
// check that stake is fully active
|
||||
let stake_history_account = context
|
||||
.banks_client
|
||||
.get_account(stake_history::id())
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
|
||||
let clock_account = context
|
||||
.banks_client
|
||||
.get_account(clock::id())
|
||||
.await
|
||||
.expect("account exists")
|
||||
.unwrap();
|
||||
|
||||
let stake_state: StakeState = deserialize(&account.data).unwrap();
|
||||
let stake_history: StakeHistory = deserialize(&stake_history_account.data).unwrap();
|
||||
let clock: Clock = deserialize(&clock_account.data).unwrap();
|
||||
let stake = stake_state.stake().unwrap();
|
||||
assert_eq!(
|
||||
stake
|
||||
.delegation
|
||||
.stake_activating_and_deactivating(clock.epoch, Some(&stake_history)),
|
||||
StakeActivationStatus::with_effective(stake.delegation.stake),
|
||||
);
|
||||
}
|
||||
|
||||
async fn check_credits_observed(
|
||||
banks_client: &mut BanksClient,
|
||||
stake_address: Pubkey,
|
||||
|
|
|
@ -133,7 +133,7 @@ use {
|
|||
lamports::LamportsError,
|
||||
message::{AccountKeys, SanitizedMessage},
|
||||
native_loader,
|
||||
native_token::sol_to_lamports,
|
||||
native_token::{sol_to_lamports, LAMPORTS_PER_SOL},
|
||||
nonce::{self, state::DurableNonce, NONCED_TX_MARKER_IX_INDEX},
|
||||
nonce_account,
|
||||
packet::PACKET_DATA_SIZE,
|
||||
|
@ -2526,7 +2526,7 @@ impl Bank {
|
|||
let invalid_cached_vote_accounts = AtomicUsize::default();
|
||||
let invalid_cached_stake_accounts_rent_epoch = AtomicUsize::default();
|
||||
|
||||
let stake_delegations: Vec<_> = stakes.stake_delegations().iter().collect();
|
||||
let stake_delegations = self.filter_stake_delegations(&stakes);
|
||||
thread_pool.install(|| {
|
||||
stake_delegations
|
||||
.into_par_iter()
|
||||
|
@ -2666,6 +2666,39 @@ impl Bank {
|
|||
}
|
||||
}
|
||||
|
||||
fn filter_stake_delegations<'a>(
|
||||
&self,
|
||||
stakes: &'a Stakes<StakeAccount<Delegation>>,
|
||||
) -> Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)> {
|
||||
if self
|
||||
.feature_set
|
||||
.is_active(&feature_set::stake_minimum_delegation_for_rewards::id())
|
||||
{
|
||||
let num_stake_delegations = stakes.stake_delegations().len();
|
||||
let min_stake_delegation =
|
||||
solana_stake_program::get_minimum_delegation(&self.feature_set)
|
||||
.max(LAMPORTS_PER_SOL);
|
||||
|
||||
let (stake_delegations, filter_timer) = measure!(stakes
|
||||
.stake_delegations()
|
||||
.iter()
|
||||
.filter(|(_stake_pubkey, cached_stake_account)| {
|
||||
cached_stake_account.delegation().stake >= min_stake_delegation
|
||||
})
|
||||
.collect::<Vec<_>>());
|
||||
|
||||
datapoint_info!(
|
||||
"stake_account_filter_time",
|
||||
("filter_time_us", filter_timer.as_us(), i64),
|
||||
("num_stake_delegations_before", num_stake_delegations, i64),
|
||||
("num_stake_delegations_after", stake_delegations.len(), i64)
|
||||
);
|
||||
stake_delegations
|
||||
} else {
|
||||
stakes.stake_delegations().iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn load_vote_and_stake_accounts<F>(
|
||||
&self,
|
||||
thread_pool: &ThreadPool,
|
||||
|
@ -2675,7 +2708,8 @@ impl Bank {
|
|||
F: Fn(&RewardCalculationEvent) + Send + Sync,
|
||||
{
|
||||
let stakes = self.stakes_cache.stakes();
|
||||
let stake_delegations: Vec<_> = stakes.stake_delegations().iter().collect();
|
||||
let stake_delegations = self.filter_stake_delegations(&stakes);
|
||||
|
||||
// Obtain all unique voter pubkeys from stake delegations.
|
||||
fn merge(mut acc: HashSet<Pubkey>, other: HashSet<Pubkey>) -> HashSet<Pubkey> {
|
||||
if acc.len() < other.len() {
|
||||
|
@ -7988,7 +8022,6 @@ pub(crate) mod tests {
|
|||
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
||||
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
|
||||
message::{Message, MessageHeader},
|
||||
native_token::LAMPORTS_PER_SOL,
|
||||
nonce,
|
||||
poh_config::PohConfig,
|
||||
program::MAX_RETURN_DATA,
|
||||
|
@ -17520,7 +17553,7 @@ pub(crate) mod tests {
|
|||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
|
||||
1_000_000_000,
|
||||
&validator_keypairs,
|
||||
vec![10_000; 2],
|
||||
vec![LAMPORTS_PER_SOL; 2],
|
||||
);
|
||||
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
|
||||
let vote_and_stake_accounts =
|
||||
|
|
|
@ -398,6 +398,10 @@ pub mod stake_raise_minimum_delegation_to_1_sol {
|
|||
solana_sdk::declare_id!("4xmyBuR2VCXzy9H6qYpH9ckfgnTuMDQFPFBfTs4eBCY1");
|
||||
}
|
||||
|
||||
pub mod stake_minimum_delegation_for_rewards {
|
||||
solana_sdk::declare_id!("ELjxSXwNsyXGfAh8TqX8ih22xeT8huF6UngQirbLKYKH");
|
||||
}
|
||||
|
||||
pub mod add_set_compute_unit_price_ix {
|
||||
solana_sdk::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g");
|
||||
}
|
||||
|
@ -671,6 +675,7 @@ lazy_static! {
|
|||
(stake_allow_zero_undelegated_amount::id(), "Allow zero-lamport undelegated amount for initialized stakes #24670"),
|
||||
(require_static_program_ids_in_transaction::id(), "require static program ids in versioned transactions"),
|
||||
(stake_raise_minimum_delegation_to_1_sol::id(), "Raise minimum stake delegation to 1.0 SOL #24357"),
|
||||
(stake_minimum_delegation_for_rewards::id(), "stakes must be at least the minimum delegation to earn rewards"),
|
||||
(add_set_compute_unit_price_ix::id(), "add compute budget ix for setting a compute unit price"),
|
||||
(disable_deploy_of_alloc_free_syscall::id(), "disable new deployments of deprecated sol_alloc_free_ syscall"),
|
||||
(include_account_index_in_rent_error::id(), "include account index in rent tx error #25190"),
|
||||
|
|
Loading…
Reference in New Issue