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-program-runtime",
|
||||||
"solana-runtime",
|
"solana-runtime",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
|
"solana-stake-program",
|
||||||
"solana-vote-program",
|
"solana-vote-program",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -27,3 +27,6 @@ solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
||||||
solana-vote-program = { path = "../programs/vote", version = "=1.15.0" }
|
solana-vote-program = { path = "../programs/vote", version = "=1.15.0" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
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)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
use {
|
use {
|
||||||
bincode::deserialize,
|
bincode::deserialize,
|
||||||
|
log::debug,
|
||||||
solana_banks_client::BanksClient,
|
solana_banks_client::BanksClient,
|
||||||
solana_program_test::{
|
solana_program_test::{
|
||||||
processor, ProgramTest, ProgramTestBanksClientExt, ProgramTestContext, ProgramTestError,
|
processor, ProgramTest, ProgramTestBanksClientExt, ProgramTestContext, ProgramTestError,
|
||||||
},
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
account::Account,
|
||||||
account_info::{next_account_info, AccountInfo},
|
account_info::{next_account_info, AccountInfo},
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
entrypoint::ProgramResult,
|
entrypoint::ProgramResult,
|
||||||
|
@ -26,9 +28,10 @@ use {
|
||||||
},
|
},
|
||||||
transaction::{Transaction, TransactionError},
|
transaction::{Transaction, TransactionError},
|
||||||
},
|
},
|
||||||
|
solana_stake_program::stake_state,
|
||||||
solana_vote_program::{
|
solana_vote_program::{
|
||||||
vote_instruction,
|
vote_instruction,
|
||||||
vote_state::{VoteInit, VoteState},
|
vote_state::{self, VoteInit, VoteState},
|
||||||
},
|
},
|
||||||
std::convert::TryInto,
|
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(
|
async fn check_credits_observed(
|
||||||
banks_client: &mut BanksClient,
|
banks_client: &mut BanksClient,
|
||||||
stake_address: Pubkey,
|
stake_address: Pubkey,
|
||||||
|
|
|
@ -133,7 +133,7 @@ use {
|
||||||
lamports::LamportsError,
|
lamports::LamportsError,
|
||||||
message::{AccountKeys, SanitizedMessage},
|
message::{AccountKeys, SanitizedMessage},
|
||||||
native_loader,
|
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::{self, state::DurableNonce, NONCED_TX_MARKER_IX_INDEX},
|
||||||
nonce_account,
|
nonce_account,
|
||||||
packet::PACKET_DATA_SIZE,
|
packet::PACKET_DATA_SIZE,
|
||||||
|
@ -2526,7 +2526,7 @@ impl Bank {
|
||||||
let invalid_cached_vote_accounts = AtomicUsize::default();
|
let invalid_cached_vote_accounts = AtomicUsize::default();
|
||||||
let invalid_cached_stake_accounts_rent_epoch = 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(|| {
|
thread_pool.install(|| {
|
||||||
stake_delegations
|
stake_delegations
|
||||||
.into_par_iter()
|
.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>(
|
fn load_vote_and_stake_accounts<F>(
|
||||||
&self,
|
&self,
|
||||||
thread_pool: &ThreadPool,
|
thread_pool: &ThreadPool,
|
||||||
|
@ -2675,7 +2708,8 @@ impl Bank {
|
||||||
F: Fn(&RewardCalculationEvent) + Send + Sync,
|
F: Fn(&RewardCalculationEvent) + Send + Sync,
|
||||||
{
|
{
|
||||||
let stakes = self.stakes_cache.stakes();
|
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.
|
// Obtain all unique voter pubkeys from stake delegations.
|
||||||
fn merge(mut acc: HashSet<Pubkey>, other: HashSet<Pubkey>) -> HashSet<Pubkey> {
|
fn merge(mut acc: HashSet<Pubkey>, other: HashSet<Pubkey>) -> HashSet<Pubkey> {
|
||||||
if acc.len() < other.len() {
|
if acc.len() < other.len() {
|
||||||
|
@ -7988,7 +8022,6 @@ pub(crate) mod tests {
|
||||||
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
||||||
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
|
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
|
||||||
message::{Message, MessageHeader},
|
message::{Message, MessageHeader},
|
||||||
native_token::LAMPORTS_PER_SOL,
|
|
||||||
nonce,
|
nonce,
|
||||||
poh_config::PohConfig,
|
poh_config::PohConfig,
|
||||||
program::MAX_RETURN_DATA,
|
program::MAX_RETURN_DATA,
|
||||||
|
@ -17520,7 +17553,7 @@ pub(crate) mod tests {
|
||||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
|
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
|
||||||
1_000_000_000,
|
1_000_000_000,
|
||||||
&validator_keypairs,
|
&validator_keypairs,
|
||||||
vec![10_000; 2],
|
vec![LAMPORTS_PER_SOL; 2],
|
||||||
);
|
);
|
||||||
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
|
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
|
||||||
let vote_and_stake_accounts =
|
let vote_and_stake_accounts =
|
||||||
|
|
|
@ -398,6 +398,10 @@ pub mod stake_raise_minimum_delegation_to_1_sol {
|
||||||
solana_sdk::declare_id!("4xmyBuR2VCXzy9H6qYpH9ckfgnTuMDQFPFBfTs4eBCY1");
|
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 {
|
pub mod add_set_compute_unit_price_ix {
|
||||||
solana_sdk::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g");
|
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"),
|
(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"),
|
(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_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"),
|
(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"),
|
(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"),
|
(include_account_index_in_rent_error::id(), "include account index in rent tx error #25190"),
|
||||||
|
|
Loading…
Reference in New Issue