diff --git a/stake-pool/cli/src/main.rs b/stake-pool/cli/src/main.rs index 60ab61c6b2e..bb20ffde8ed 100644 --- a/stake-pool/cli/src/main.rs +++ b/stake-pool/cli/src/main.rs @@ -430,7 +430,7 @@ fn command_vsa_add( } if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } // iterate until a free account is found @@ -488,7 +488,7 @@ fn command_vsa_remove( vote_account: &Pubkey, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -535,7 +535,7 @@ fn command_increase_validator_stake( ) -> CommandResult { let lamports = native_token::sol_to_lamports(amount); if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -574,7 +574,7 @@ fn command_decrease_validator_stake( ) -> CommandResult { let lamports = native_token::sol_to_lamports(amount); if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -671,7 +671,7 @@ fn command_deposit_stake( referrer_token_account: &Option, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -802,7 +802,7 @@ fn command_deposit_all_stake( referrer_token_account: &Option, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_addresses = get_all_stake(&config.rpc_client, stake_authority)?; @@ -945,7 +945,7 @@ fn command_deposit_sol( amount: f64, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let amount = native_token::sol_to_lamports(amount); @@ -1139,6 +1139,7 @@ fn command_update( stake_pool_address: &Pubkey, force: bool, no_merge: bool, + stale_only: bool, ) -> CommandResult { if config.no_update { println!("Update requested, but --no-update flag specified, so doing nothing"); @@ -1158,14 +1159,24 @@ fn command_update( let validator_list = get_validator_list(&config.rpc_client, &stake_pool.validator_list)?; - let (mut update_list_instructions, final_instructions) = + let (mut update_list_instructions, final_instructions) = if stale_only { + spl_stake_pool::instruction::update_stale_stake_pool( + &spl_stake_pool::id(), + &stake_pool, + &validator_list, + stake_pool_address, + no_merge, + epoch_info.epoch, + ) + } else { spl_stake_pool::instruction::update_stake_pool( &spl_stake_pool::id(), &stake_pool, &validator_list, stake_pool_address, no_merge, - ); + ) + }; let update_list_instructions_len = update_list_instructions.len(); if update_list_instructions_len > 0 { @@ -1360,7 +1371,7 @@ fn command_withdraw_stake( pool_amount: f64, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -1631,7 +1642,7 @@ fn command_withdraw_sol( pool_amount: f64, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -1748,7 +1759,7 @@ fn command_set_manager( new_fee_receiver: &Option, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; @@ -1800,7 +1811,7 @@ fn command_set_staker( new_staker: &Pubkey, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; unique_signers!(signers); @@ -1825,7 +1836,7 @@ fn command_set_funding_authority( funding_type: FundingType, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; unique_signers!(signers); @@ -1850,7 +1861,7 @@ fn command_set_fee( new_fee: FeeType, ) -> CommandResult { if !config.no_update { - command_update(config, stake_pool_address, false, false)?; + command_update(config, stake_pool_address, false, false, false)?; } let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; unique_signers!(signers); @@ -2418,7 +2429,7 @@ fn main() { Arg::with_name("force") .long("force") .takes_value(false) - .help("Update all balances, even if it has already been performed this epoch."), + .help("Update balances, even if it has already been performed this epoch."), ) .arg( Arg::with_name("no_merge") @@ -2426,6 +2437,12 @@ fn main() { .takes_value(false) .help("Do not automatically merge transient stakes. Useful if the stake pool is in an expected state, but the balances still need to be updated."), ) + .arg( + Arg::with_name("stale_only") + .long("stale-only") + .takes_value(false) + .help("If set, only updates validator list balances that have not been updated for this epoch. Otherwise, updates all validator balances on the validator list."), + ) ) .subcommand(SubCommand::with_name("withdraw-stake") .about("Withdraw active stake from the stake pool in exchange for pool tokens") @@ -2903,7 +2920,8 @@ fn main() { let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let no_merge = arg_matches.is_present("no_merge"); let force = arg_matches.is_present("force"); - command_update(&config, &stake_pool_address, force, no_merge) + let stale_only = arg_matches.is_present("stale_only"); + command_update(&config, &stake_pool_address, force, no_merge, stale_only) } ("withdraw-stake", Some(arg_matches)) => { let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); diff --git a/stake-pool/program/src/instruction.rs b/stake-pool/program/src/instruction.rs index 68b0946999c..4b8afd8e17b 100644 --- a/stake-pool/program/src/instruction.rs +++ b/stake-pool/program/src/instruction.rs @@ -16,7 +16,9 @@ use { instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, - stake, system_program, sysvar, + stake, + stake_history::Epoch, + system_program, sysvar, }, std::num::NonZeroU32, }; @@ -1496,6 +1498,47 @@ pub fn update_validator_list_balance_chunk( }) } +/// Creates `UpdateValidatorListBalance` instruction (update validator stake +/// account balances) +/// +/// Returns `None` if all validators in the given chunk has already been updated +/// for this epoch, returns the required instruction otherwise. +pub fn update_stale_validator_list_balance_chunk( + program_id: &Pubkey, + stake_pool: &Pubkey, + stake_pool_withdraw_authority: &Pubkey, + validator_list_address: &Pubkey, + reserve_stake: &Pubkey, + validator_list: &ValidatorList, + len: usize, + start_index: usize, + no_merge: bool, + current_epoch: Epoch, +) -> Result, ProgramError> { + let validator_list_subslice = validator_list + .validators + .get(start_index..start_index.saturating_add(len)) + .ok_or(ProgramError::InvalidInstructionData)?; + if validator_list_subslice.iter().all(|info| { + let last_update_epoch: u64 = info.last_update_epoch.into(); + last_update_epoch >= current_epoch + }) { + return Ok(None); + } + update_validator_list_balance_chunk( + program_id, + stake_pool, + stake_pool_withdraw_authority, + validator_list_address, + reserve_stake, + validator_list, + len, + start_index, + no_merge, + ) + .map(Some) +} + /// Creates `UpdateStakePoolBalance` instruction (pool balance from the stake /// account list balances) pub fn update_stake_pool_balance( @@ -1599,6 +1642,65 @@ pub fn update_stake_pool( (update_list_instructions, final_instructions) } +/// Creates the `UpdateValidatorListBalance` instructions only for validators on +/// `validator_list` that have not been updated for this epoch, and the +/// `UpdateStakePoolBalance` instruction for fully updating the stake pool. +/// +/// Basically same as [`update_stake_pool`], but skips validators that are +/// already updated for this epoch +pub fn update_stale_stake_pool( + program_id: &Pubkey, + stake_pool: &StakePool, + validator_list: &ValidatorList, + stake_pool_address: &Pubkey, + no_merge: bool, + current_epoch: Epoch, +) -> (Vec, Vec) { + let (withdraw_authority, _) = + find_withdraw_authority_program_address(program_id, stake_pool_address); + + let update_list_instructions = validator_list + .validators + .chunks(MAX_VALIDATORS_TO_UPDATE) + .enumerate() + .filter_map(|(i, chunk)| { + // unwrap-safety: chunk len and offset are derived + update_stale_validator_list_balance_chunk( + program_id, + stake_pool_address, + &withdraw_authority, + &stake_pool.validator_list, + &stake_pool.reserve_stake, + validator_list, + chunk.len(), + i.saturating_mul(MAX_VALIDATORS_TO_UPDATE), + no_merge, + current_epoch, + ) + .unwrap() + }) + .collect(); + + let final_instructions = vec![ + update_stake_pool_balance( + program_id, + stake_pool_address, + &withdraw_authority, + &stake_pool.validator_list, + &stake_pool.reserve_stake, + &stake_pool.manager_fee_account, + &stake_pool.pool_mint, + &stake_pool.token_program_id, + ), + cleanup_removed_validator_entries( + program_id, + stake_pool_address, + &stake_pool.validator_list, + ), + ]; + (update_list_instructions, final_instructions) +} + fn deposit_stake_internal( program_id: &Pubkey, stake_pool: &Pubkey,