Feature: filter stake by min delegation for rewards (#29618)

filter stake by min delegation for rewards
This commit is contained in:
HaoranYi 2023-01-11 08:55:18 -06:00 committed by GitHub
parent d351c58ff4
commit d693167dfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 167 additions and 6 deletions

1
Cargo.lock generated
View File

@ -6069,6 +6069,7 @@ dependencies = [
"solana-program-runtime",
"solana-runtime",
"solana-sdk 1.15.0",
"solana-stake-program",
"solana-vote-program",
"thiserror",
"tokio",

View File

@ -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" }

View File

@ -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,

View File

@ -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 =

View File

@ -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"),