Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit fdba057

Browse files
authored
stake-pool: Add merging transient stakes in update (#1618)
* Add check for transient stake account activation on removal * Add proper merging logic during update * Format + clippy * Add max possible validators * Disallow removal for any transient stake state * Reduce number of accounts for BPF instruction usage
1 parent 53c8649 commit fdba057

File tree

14 files changed

+1382
-284
lines changed

14 files changed

+1382
-284
lines changed

stake-pool/cli/src/main.rs

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ use {
2929
self,
3030
borsh::get_instance_packed_len,
3131
find_deposit_authority_program_address, find_stake_program_address,
32-
find_withdraw_authority_program_address,
32+
find_transient_stake_program_address, find_withdraw_authority_program_address,
3333
stake_program::{self, StakeAuthorize, StakeState},
3434
state::{Fee, StakePool, ValidatorList},
35+
MAX_VALIDATORS_TO_UPDATE,
3536
},
3637
std::process::exit,
3738
};
@@ -51,7 +52,6 @@ type Error = Box<dyn std::error::Error>;
5152
type CommandResult = Result<(), Error>;
5253

5354
const STAKE_STATE_LEN: usize = 200;
54-
const MAX_ACCOUNTS_TO_UPDATE: usize = 10;
5555
lazy_static! {
5656
static ref MIN_STAKE_BALANCE: u64 = native_token::sol_to_lamports(1.0);
5757
}
@@ -347,7 +347,7 @@ fn command_vsa_add(config: &Config, stake_pool_address: &Pubkey, stake: &Pubkey)
347347
fn command_vsa_remove(
348348
config: &Config,
349349
stake_pool_address: &Pubkey,
350-
stake: &Pubkey,
350+
vote_account: &Pubkey,
351351
new_authority: &Option<Pubkey>,
352352
) -> CommandResult {
353353
if !config.no_update {
@@ -357,6 +357,13 @@ fn command_vsa_remove(
357357
let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
358358
let pool_withdraw_authority =
359359
find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0;
360+
let (validator_stake_account, _) =
361+
find_stake_program_address(&spl_stake_pool::id(), &vote_account, stake_pool_address);
362+
let (transient_stake_account, _) = find_transient_stake_program_address(
363+
&spl_stake_pool::id(),
364+
&vote_account,
365+
stake_pool_address,
366+
);
360367

361368
let staker_pubkey = config.staker.pubkey();
362369
let new_authority = new_authority.as_ref().unwrap_or(&staker_pubkey);
@@ -371,7 +378,8 @@ fn command_vsa_remove(
371378
&pool_withdraw_authority,
372379
&new_authority,
373380
&stake_pool.validator_list,
374-
&stake,
381+
&validator_stake_account,
382+
&transient_stake_account,
375383
)?,
376384
],
377385
Some(&config.fee_payer.pubkey()),
@@ -640,27 +648,34 @@ fn command_update(config: &Config, stake_pool_address: &Pubkey) -> CommandResult
640648
.collect();
641649

642650
println!("Updating stake pool...");
651+
let (withdraw_authority, _) =
652+
find_withdraw_authority_program_address(&spl_stake_pool::id(), &stake_pool_address);
643653

644654
let mut instructions: Vec<Instruction> = vec![];
645-
for accounts_chunk in accounts_to_update.chunks(MAX_ACCOUNTS_TO_UPDATE) {
655+
let mut start_index = 0;
656+
for accounts_chunk in accounts_to_update.chunks(MAX_VALIDATORS_TO_UPDATE) {
646657
instructions.push(spl_stake_pool::instruction::update_validator_list_balance(
647658
&spl_stake_pool::id(),
659+
stake_pool_address,
660+
&withdraw_authority,
648661
&stake_pool.validator_list,
662+
&stake_pool.reserve_stake,
649663
&accounts_chunk,
650-
)?);
664+
start_index,
665+
false,
666+
));
667+
start_index += MAX_VALIDATORS_TO_UPDATE as u32;
651668
}
652669

653-
let (withdraw_authority, _) =
654-
find_withdraw_authority_program_address(&spl_stake_pool::id(), &stake_pool_address);
655-
656670
instructions.push(spl_stake_pool::instruction::update_stake_pool_balance(
657671
&spl_stake_pool::id(),
658672
stake_pool_address,
659-
&stake_pool.validator_list,
660673
&withdraw_authority,
674+
&stake_pool.validator_list,
675+
&stake_pool.reserve_stake,
661676
&stake_pool.manager_fee_account,
662677
&stake_pool.pool_mint,
663-
)?);
678+
));
664679

665680
// TODO: A faster solution would be to send all the `update_validator_list_balance` instructions concurrently
666681
for instruction in instructions {
@@ -1135,15 +1150,6 @@ fn main() {
11351150
.required(true)
11361151
.help("Stake account to add to the pool"),
11371152
)
1138-
.arg(
1139-
Arg::with_name("token_receiver")
1140-
.long("token-receiver")
1141-
.validator(is_pubkey)
1142-
.value_name("ADDRESS")
1143-
.takes_value(true)
1144-
.help("Account to receive pool token. Must be initialized account of the stake pool token. \
1145-
Defaults to the new pool token account."),
1146-
)
11471153
)
11481154
.subcommand(SubCommand::with_name("remove-validator")
11491155
.about("Remove validator account from the stake pool. Must be signed by the pool staker.")
@@ -1157,23 +1163,13 @@ fn main() {
11571163
.help("Stake pool address"),
11581164
)
11591165
.arg(
1160-
Arg::with_name("stake_account")
1166+
Arg::with_name("vote_account")
11611167
.index(2)
11621168
.validator(is_pubkey)
1163-
.value_name("STAKE_ACCOUNT_ADDRESS")
1164-
.takes_value(true)
1165-
.required(true)
1166-
.help("Stake account to remove from the pool"),
1167-
)
1168-
.arg(
1169-
Arg::with_name("withdraw_from")
1170-
.long("withdraw-from")
1171-
.validator(is_pubkey)
1172-
.value_name("ADDRESS")
1169+
.value_name("VOTE_ACCOUNT_ADDRESS")
11731170
.takes_value(true)
11741171
.required(true)
1175-
.help("Token account to withdraw pool token from. \
1176-
Must have enough tokens for the full stake address balance."),
1172+
.help("Vote account for the validator to remove from the pool"),
11771173
)
11781174
.arg(
11791175
Arg::with_name("new_authority")
@@ -1455,9 +1451,9 @@ fn main() {
14551451
}
14561452
("remove-validator", Some(arg_matches)) => {
14571453
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
1458-
let stake_account = pubkey_of(arg_matches, "stake_account").unwrap();
1454+
let vote_account = pubkey_of(arg_matches, "vote_account").unwrap();
14591455
let new_authority: Option<Pubkey> = pubkey_of(arg_matches, "new_authority");
1460-
command_vsa_remove(&config, &stake_pool_address, &stake_account, &new_authority)
1456+
command_vsa_remove(&config, &stake_pool_address, &vote_account, &new_authority)
14611457
}
14621458
("deposit", Some(arg_matches)) => {
14631459
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();

stake-pool/program/src/instruction.rs

Lines changed: 84 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#![allow(clippy::too_many_arguments)]
44

55
use {
6-
crate::{stake_program, state::Fee},
6+
crate::{
7+
find_stake_program_address, find_transient_stake_program_address, stake_program, state::Fee,
8+
},
79
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
810
solana_program::{
911
instruction::{AccountMeta, Instruction},
@@ -88,8 +90,9 @@ pub enum StakePoolInstruction {
8890
/// 3. `[]` New withdraw/staker authority to set in the stake account
8991
/// 4. `[w]` Validator stake list storage account
9092
/// 5. `[w]` Stake account to remove from the pool
91-
/// 8. '[]' Sysvar clock
92-
/// 10. `[]` Stake program id,
93+
/// 6. `[]` Transient stake account, to check that that we're not trying to activate
94+
/// 7. '[]' Sysvar clock
95+
/// 8. `[]` Stake program id,
9396
RemoveValidatorFromPool,
9497

9598
/// (Staker only) Decrease active stake on a validator, eventually moving it to the reserve
@@ -132,7 +135,7 @@ pub enum StakePoolInstruction {
132135
/// 0. `[]` Stake pool
133136
/// 1. `[s]` Stake pool staker
134137
/// 2. `[]` Stake pool withdraw authority
135-
/// 3. `[]` Validator list
138+
/// 3. `[w]` Validator list
136139
/// 4. `[w]` Stake pool reserve stake
137140
/// 5. `[w]` Transient stake account
138141
/// 6. `[]` Validator vote account to delegate to
@@ -149,26 +152,36 @@ pub enum StakePoolInstruction {
149152
///
150153
/// While going through the pairs of validator and transient stake accounts,
151154
/// if the transient stake is inactive, it is merged into the reserve stake
152-
/// account. If the transient stake is active and has matching credits
155+
/// account. If the transient stake is active and has matching credits
153156
/// observed, it is merged into the canonical validator stake account. In
154157
/// all other states, nothing is done, and the balance is simply added to
155158
/// the canonical stake account balance.
156159
///
157160
/// 0. `[]` Stake pool
158-
/// 1. `[w]` Validator stake list storage account
159-
/// 2. `[w]` Reserve stake account
160-
/// 3. `[]` Stake pool withdraw authority
161-
/// 4. `[]` Sysvar clock account
162-
/// 5. `[]` Stake program
163-
/// 6. ..6+N ` [] N pairs of validator and transient stake accounts
164-
UpdateValidatorListBalance,
161+
/// 1. `[]` Stake pool withdraw authority
162+
/// 2. `[w]` Validator stake list storage account
163+
/// 3. `[w]` Reserve stake account
164+
/// 4. `[]` Sysvar clock
165+
/// 5. `[]` Sysvar stake history
166+
/// 6. `[]` Stake program
167+
/// 7. ..7+N ` [] N pairs of validator and transient stake accounts
168+
UpdateValidatorListBalance {
169+
/// Index to start updating on the validator list
170+
#[allow(dead_code)] // but it's not
171+
start_index: u32,
172+
/// If true, don't try merging transient stake accounts into the reserve or
173+
/// validator stake account. Useful for testing or if a particular stake
174+
/// account is in a bad state, but we still want to update
175+
#[allow(dead_code)] // but it's not
176+
no_merge: bool,
177+
},
165178

166179
/// Updates total pool balance based on balances in the reserve and validator list
167180
///
168181
/// 0. `[w]` Stake pool
169-
/// 1. `[]` Validator stake list storage account
170-
/// 2. `[]` Reserve stake account
171-
/// 3. `[]` Stake pool withdraw authority
182+
/// 1. `[]` Stake pool withdraw authority
183+
/// 2. `[]` Validator stake list storage account
184+
/// 3. `[]` Reserve stake account
172185
/// 4. `[w]` Account to receive pool fee tokens
173186
/// 5. `[w]` Pool mint account
174187
/// 6. `[]` Sysvar clock account
@@ -347,6 +360,7 @@ pub fn remove_validator_from_pool(
347360
new_stake_authority: &Pubkey,
348361
validator_list: &Pubkey,
349362
stake_account: &Pubkey,
363+
transient_stake_account: &Pubkey,
350364
) -> Result<Instruction, ProgramError> {
351365
let accounts = vec![
352366
AccountMeta::new(*stake_pool, false),
@@ -355,6 +369,7 @@ pub fn remove_validator_from_pool(
355369
AccountMeta::new_readonly(*new_stake_authority, false),
356370
AccountMeta::new(*validator_list, false),
357371
AccountMeta::new(*stake_account, false),
372+
AccountMeta::new_readonly(*transient_stake_account, false),
358373
AccountMeta::new_readonly(sysvar::clock::id(), false),
359374
AccountMeta::new_readonly(stake_program::id(), false),
360375
];
@@ -413,7 +428,7 @@ pub fn increase_validator_stake(
413428
AccountMeta::new_readonly(*stake_pool, false),
414429
AccountMeta::new_readonly(*staker, true),
415430
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
416-
AccountMeta::new_readonly(*validator_list, false),
431+
AccountMeta::new(*validator_list, false),
417432
AccountMeta::new(*reserve_stake, false),
418433
AccountMeta::new(*transient_stake, false),
419434
AccountMeta::new_readonly(*validator, false),
@@ -434,45 +449,80 @@ pub fn increase_validator_stake(
434449
/// Creates `UpdateValidatorListBalance` instruction (update validator stake account balances)
435450
pub fn update_validator_list_balance(
436451
program_id: &Pubkey,
437-
validator_list_storage: &Pubkey,
438-
validator_list: &[Pubkey],
439-
) -> Result<Instruction, ProgramError> {
440-
let mut accounts: Vec<AccountMeta> = validator_list
441-
.iter()
442-
.map(|pubkey| AccountMeta::new_readonly(*pubkey, false))
443-
.collect();
444-
accounts.insert(0, AccountMeta::new(*validator_list_storage, false));
445-
accounts.insert(1, AccountMeta::new_readonly(sysvar::clock::id(), false));
446-
Ok(Instruction {
452+
stake_pool: &Pubkey,
453+
stake_pool_withdraw_authority: &Pubkey,
454+
validator_list: &Pubkey,
455+
reserve_stake: &Pubkey,
456+
validator_vote_accounts: &[Pubkey],
457+
start_index: u32,
458+
no_merge: bool,
459+
) -> Instruction {
460+
let mut accounts = vec![
461+
AccountMeta::new_readonly(*stake_pool, false),
462+
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
463+
AccountMeta::new(*validator_list, false),
464+
AccountMeta::new(*reserve_stake, false),
465+
AccountMeta::new_readonly(sysvar::clock::id(), false),
466+
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
467+
AccountMeta::new_readonly(stake_program::id(), false),
468+
];
469+
accounts.append(
470+
&mut validator_vote_accounts
471+
.iter()
472+
.flat_map(|vote_account_address| {
473+
let (validator_stake_account, _) =
474+
find_stake_program_address(program_id, vote_account_address, stake_pool);
475+
let (transient_stake_account, _) = find_transient_stake_program_address(
476+
program_id,
477+
vote_account_address,
478+
stake_pool,
479+
);
480+
vec![
481+
AccountMeta::new(validator_stake_account, false),
482+
AccountMeta::new(transient_stake_account, false),
483+
]
484+
})
485+
.collect::<Vec<AccountMeta>>(),
486+
);
487+
Instruction {
447488
program_id: *program_id,
448489
accounts,
449-
data: StakePoolInstruction::UpdateValidatorListBalance.try_to_vec()?,
450-
})
490+
data: StakePoolInstruction::UpdateValidatorListBalance {
491+
start_index,
492+
no_merge,
493+
}
494+
.try_to_vec()
495+
.unwrap(),
496+
}
451497
}
452498

453499
/// Creates `UpdateStakePoolBalance` instruction (pool balance from the stake account list balances)
454500
pub fn update_stake_pool_balance(
455501
program_id: &Pubkey,
456502
stake_pool: &Pubkey,
457-
validator_list_storage: &Pubkey,
458503
withdraw_authority: &Pubkey,
504+
validator_list_storage: &Pubkey,
505+
reserve_stake: &Pubkey,
459506
manager_fee_account: &Pubkey,
460507
stake_pool_mint: &Pubkey,
461-
) -> Result<Instruction, ProgramError> {
508+
) -> Instruction {
462509
let accounts = vec![
463510
AccountMeta::new(*stake_pool, false),
464-
AccountMeta::new(*validator_list_storage, false),
465511
AccountMeta::new_readonly(*withdraw_authority, false),
512+
AccountMeta::new_readonly(*validator_list_storage, false),
513+
AccountMeta::new_readonly(*reserve_stake, false),
466514
AccountMeta::new(*manager_fee_account, false),
467515
AccountMeta::new(*stake_pool_mint, false),
468516
AccountMeta::new_readonly(sysvar::clock::id(), false),
469517
AccountMeta::new_readonly(spl_token::id(), false),
470518
];
471-
Ok(Instruction {
519+
Instruction {
472520
program_id: *program_id,
473521
accounts,
474-
data: StakePoolInstruction::UpdateStakePoolBalance.try_to_vec()?,
475-
})
522+
data: StakePoolInstruction::UpdateStakePoolBalance
523+
.try_to_vec()
524+
.unwrap(),
525+
}
476526
}
477527

478528
/// Creates a 'Deposit' instruction.

stake-pool/program/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,23 @@ const TRANSIENT_STAKE_SEED: &[u8] = b"transient";
3232
/// for merges without a mismatch on credits observed
3333
pub const MINIMUM_ACTIVE_STAKE: u64 = LAMPORTS_PER_SOL;
3434

35+
/// Maximum amount of validator stake accounts to update per
36+
/// `UpdateValidatorListBalance` instruction, based on compute limits
37+
pub const MAX_VALIDATORS_TO_UPDATE: usize = 10;
38+
3539
/// Get the stake amount under consideration when calculating pool token
3640
/// conversions
3741
pub fn minimum_stake_lamports(meta: &Meta) -> u64 {
3842
meta.rent_exempt_reserve
3943
.saturating_add(MINIMUM_ACTIVE_STAKE)
4044
}
4145

46+
/// Get the stake amount under consideration when calculating pool token
47+
/// conversions
48+
pub fn minimum_reserve_lamports(meta: &Meta) -> u64 {
49+
meta.rent_exempt_reserve.saturating_add(1)
50+
}
51+
4252
/// Generates the deposit authority program address for the stake pool
4353
pub fn find_deposit_authority_program_address(
4454
program_id: &Pubkey,

0 commit comments

Comments
 (0)