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

Commit 9d3cb47

Browse files
authored
stake-pool: Allow removal of force-destaked validator (#5439)
* stake-pool: Allow removal of force-destaked validator * Update comment
1 parent b5302dd commit 9d3cb47

File tree

6 files changed

+213
-66
lines changed

6 files changed

+213
-66
lines changed

stake-pool/program/src/instruction.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub enum StakePoolInstruction {
112112
/// 2. `[]` Stake pool withdraw authority
113113
/// 3. `[w]` Validator stake list storage account
114114
/// 4. `[w]` Stake account to remove from the pool
115-
/// 5. `[]` Transient stake account, to check that that we're not trying to activate
115+
/// 5. `[w]` Transient stake account, to deactivate if necessary
116116
/// 6. `[]` Sysvar clock
117117
/// 7. `[]` Stake program id,
118118
RemoveValidatorFromPool,
@@ -777,7 +777,7 @@ pub fn remove_validator_from_pool(
777777
AccountMeta::new_readonly(*stake_pool_withdraw, false),
778778
AccountMeta::new(*validator_list, false),
779779
AccountMeta::new(*stake_account, false),
780-
AccountMeta::new_readonly(*transient_stake_account, false),
780+
AccountMeta::new(*transient_stake_account, false),
781781
AccountMeta::new_readonly(sysvar::clock::id(), false),
782782
AccountMeta::new_readonly(stake::program::id(), false),
783783
];

stake-pool/program/src/processor.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,32 +1193,35 @@ impl Processor {
11931193
) =>
11941194
{
11951195
if stake.delegation.deactivation_epoch == Epoch::MAX {
1196-
msg!(
1197-
"Transient stake {} activating, can't remove stake {} on validator {}",
1198-
transient_stake_account_info.key,
1199-
stake_account_info.key,
1200-
vote_account_address
1201-
);
1202-
return Err(StakePoolError::WrongStakeState.into());
1203-
} else {
1204-
StakeStatus::DeactivatingAll
1196+
Self::stake_deactivate(
1197+
transient_stake_account_info.clone(),
1198+
clock_info.clone(),
1199+
withdraw_authority_info.clone(),
1200+
stake_pool_info.key,
1201+
AUTHORITY_WITHDRAW,
1202+
stake_pool.stake_withdraw_bump_seed,
1203+
)?;
12051204
}
1205+
StakeStatus::DeactivatingAll
12061206
}
12071207
_ => StakeStatus::DeactivatingValidator,
12081208
}
12091209
} else {
12101210
StakeStatus::DeactivatingValidator
12111211
};
12121212

1213-
// deactivate stake
1214-
Self::stake_deactivate(
1215-
stake_account_info.clone(),
1216-
clock_info.clone(),
1217-
withdraw_authority_info.clone(),
1218-
stake_pool_info.key,
1219-
AUTHORITY_WITHDRAW,
1220-
stake_pool.stake_withdraw_bump_seed,
1221-
)?;
1213+
// If the stake was force-deactivated through deactivate-delinquent or
1214+
// some other means, we *do not* need to deactivate it again
1215+
if stake.delegation.deactivation_epoch == Epoch::MAX {
1216+
Self::stake_deactivate(
1217+
stake_account_info.clone(),
1218+
clock_info.clone(),
1219+
withdraw_authority_info.clone(),
1220+
stake_pool_info.key,
1221+
AUTHORITY_WITHDRAW,
1222+
stake_pool.stake_withdraw_bump_seed,
1223+
)?;
1224+
}
12221225

12231226
validator_stake_info.status = new_status.into();
12241227

stake-pool/program/tests/force_destake.rs

Lines changed: 162 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ mod helpers;
55

66
use {
77
helpers::*,
8-
solana_program::{instruction::InstructionError, pubkey::Pubkey, stake},
8+
solana_program::{
9+
borsh0_10::try_from_slice_unchecked,
10+
instruction::InstructionError,
11+
pubkey::Pubkey,
12+
stake::{
13+
self,
14+
state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState},
15+
},
16+
},
917
solana_program_test::*,
1018
solana_sdk::{
1119
account::{Account, WritableAccount},
@@ -16,40 +24,27 @@ use {
1624
spl_stake_pool::{
1725
error::StakePoolError,
1826
find_stake_program_address, find_transient_stake_program_address, id,
19-
state::{StakeStatus, ValidatorStakeInfo},
27+
state::{AccountType, StakeStatus, ValidatorList, ValidatorListHeader, ValidatorStakeInfo},
2028
MINIMUM_ACTIVE_STAKE,
2129
},
2230
std::num::NonZeroU32,
2331
};
2432

25-
async fn setup() -> (
26-
ProgramTestContext,
27-
StakePoolAccounts,
28-
Pubkey,
29-
Option<NonZeroU32>,
30-
) {
33+
async fn setup(
34+
stake_pool_accounts: &StakePoolAccounts,
35+
forced_stake: &StakeState,
36+
voter_pubkey: &Pubkey,
37+
) -> (ProgramTestContext, Option<NonZeroU32>) {
3138
let mut program_test = program_test();
32-
let stake_pool_accounts = StakePoolAccounts::default();
3339

3440
let stake_pool_pubkey = stake_pool_accounts.stake_pool.pubkey();
3541
let (mut stake_pool, mut validator_list) = stake_pool_accounts.state();
3642

37-
let voter_pubkey = add_vote_account(&mut program_test);
38-
let meta = stake::state::Meta {
39-
rent_exempt_reserve: STAKE_ACCOUNT_RENT_EXEMPTION,
40-
authorized: stake::state::Authorized {
41-
staker: stake_pool_accounts.withdraw_authority,
42-
withdrawer: stake_pool_accounts.withdraw_authority,
43-
},
44-
lockup: stake_pool.lockup,
45-
};
43+
let _ = add_vote_account_with_pubkey(voter_pubkey, &mut program_test);
4644

4745
let stake_account = Account::create(
4846
TEST_STAKE_AMOUNT + STAKE_ACCOUNT_RENT_EXEMPTION,
49-
bincode::serialize::<stake::state::StakeState>(&stake::state::StakeState::Initialized(
50-
meta,
51-
))
52-
.unwrap(),
47+
bincode::serialize::<StakeState>(forced_stake).unwrap(),
5348
stake::program::id(),
5449
false,
5550
Epoch::default(),
@@ -58,13 +53,13 @@ async fn setup() -> (
5853
let raw_validator_seed = 42;
5954
let validator_seed = NonZeroU32::new(raw_validator_seed);
6055
let (stake_address, _) =
61-
find_stake_program_address(&id(), &voter_pubkey, &stake_pool_pubkey, validator_seed);
56+
find_stake_program_address(&id(), voter_pubkey, &stake_pool_pubkey, validator_seed);
6257
program_test.add_account(stake_address, stake_account);
6358
let active_stake_lamports = TEST_STAKE_AMOUNT - MINIMUM_ACTIVE_STAKE;
6459
// add to validator list
6560
validator_list.validators.push(ValidatorStakeInfo {
6661
status: StakeStatus::Active.into(),
67-
vote_account_address: voter_pubkey,
62+
vote_account_address: *voter_pubkey,
6863
active_stake_lamports: active_stake_lamports.into(),
6964
transient_stake_lamports: 0.into(),
7065
last_update_epoch: 0.into(),
@@ -110,12 +105,27 @@ async fn setup() -> (
110105
);
111106

112107
let context = program_test.start_with_context().await;
113-
(context, stake_pool_accounts, voter_pubkey, validator_seed)
108+
(context, validator_seed)
114109
}
115110

116111
#[tokio::test]
117112
async fn success_update() {
118-
let (mut context, stake_pool_accounts, voter_pubkey, validator_seed) = setup().await;
113+
let stake_pool_accounts = StakePoolAccounts::default();
114+
let meta = Meta {
115+
rent_exempt_reserve: STAKE_ACCOUNT_RENT_EXEMPTION,
116+
authorized: Authorized {
117+
staker: stake_pool_accounts.withdraw_authority,
118+
withdrawer: stake_pool_accounts.withdraw_authority,
119+
},
120+
lockup: Lockup::default(),
121+
};
122+
let voter_pubkey = Pubkey::new_unique();
123+
let (mut context, validator_seed) = setup(
124+
&stake_pool_accounts,
125+
&StakeState::Initialized(meta),
126+
&voter_pubkey,
127+
)
128+
.await;
119129
let pre_reserve_lamports = context
120130
.banks_client
121131
.get_account(stake_pool_accounts.reserve_stake.pubkey())
@@ -169,7 +179,22 @@ async fn success_update() {
169179

170180
#[tokio::test]
171181
async fn fail_increase() {
172-
let (mut context, stake_pool_accounts, voter_pubkey, validator_seed) = setup().await;
182+
let stake_pool_accounts = StakePoolAccounts::default();
183+
let meta = Meta {
184+
rent_exempt_reserve: STAKE_ACCOUNT_RENT_EXEMPTION,
185+
authorized: Authorized {
186+
staker: stake_pool_accounts.withdraw_authority,
187+
withdrawer: stake_pool_accounts.withdraw_authority,
188+
},
189+
lockup: Lockup::default(),
190+
};
191+
let voter_pubkey = Pubkey::new_unique();
192+
let (mut context, validator_seed) = setup(
193+
&stake_pool_accounts,
194+
&StakeState::Initialized(meta),
195+
&voter_pubkey,
196+
)
197+
.await;
173198
let (stake_address, _) = find_stake_program_address(
174199
&id(),
175200
&voter_pubkey,
@@ -206,3 +231,113 @@ async fn fail_increase() {
206231
)
207232
);
208233
}
234+
235+
#[tokio::test]
236+
async fn success_remove_validator() {
237+
let stake_pool_accounts = StakePoolAccounts::default();
238+
let meta = Meta {
239+
rent_exempt_reserve: STAKE_ACCOUNT_RENT_EXEMPTION,
240+
authorized: Authorized {
241+
staker: stake_pool_accounts.withdraw_authority,
242+
withdrawer: stake_pool_accounts.withdraw_authority,
243+
},
244+
lockup: Lockup::default(),
245+
};
246+
let voter_pubkey = Pubkey::new_unique();
247+
let stake = Stake {
248+
delegation: Delegation {
249+
voter_pubkey,
250+
stake: TEST_STAKE_AMOUNT,
251+
activation_epoch: 0,
252+
deactivation_epoch: 0,
253+
..Delegation::default()
254+
},
255+
credits_observed: 1,
256+
};
257+
let (mut context, validator_seed) = setup(
258+
&stake_pool_accounts,
259+
&StakeState::Stake(meta, stake),
260+
&voter_pubkey,
261+
)
262+
.await;
263+
264+
// move forward to after deactivation
265+
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
266+
context.warp_to_slot(first_normal_slot + 1).unwrap();
267+
stake_pool_accounts
268+
.update_all(
269+
&mut context.banks_client,
270+
&context.payer,
271+
&context.last_blockhash,
272+
&[voter_pubkey],
273+
false,
274+
)
275+
.await;
276+
277+
let (stake_address, _) = find_stake_program_address(
278+
&id(),
279+
&voter_pubkey,
280+
&stake_pool_accounts.stake_pool.pubkey(),
281+
validator_seed,
282+
);
283+
let transient_stake_seed = 0;
284+
let transient_stake_address = find_transient_stake_program_address(
285+
&id(),
286+
&voter_pubkey,
287+
&stake_pool_accounts.stake_pool.pubkey(),
288+
transient_stake_seed,
289+
)
290+
.0;
291+
292+
let error = stake_pool_accounts
293+
.remove_validator_from_pool(
294+
&mut context.banks_client,
295+
&context.payer,
296+
&context.last_blockhash,
297+
&stake_address,
298+
&transient_stake_address,
299+
)
300+
.await;
301+
assert!(error.is_none(), "{:?}", error);
302+
303+
// Get a new blockhash for the next update to work
304+
context.get_new_latest_blockhash().await.unwrap();
305+
306+
let error = stake_pool_accounts
307+
.update_all(
308+
&mut context.banks_client,
309+
&context.payer,
310+
&context.last_blockhash,
311+
&[voter_pubkey],
312+
false,
313+
)
314+
.await;
315+
assert!(error.is_none(), "{:?}", error);
316+
317+
// Check if account was removed from the list of stake accounts
318+
let validator_list = get_account(
319+
&mut context.banks_client,
320+
&stake_pool_accounts.validator_list.pubkey(),
321+
)
322+
.await;
323+
let validator_list =
324+
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
325+
assert_eq!(
326+
validator_list,
327+
ValidatorList {
328+
header: ValidatorListHeader {
329+
account_type: AccountType::ValidatorList,
330+
max_validators: stake_pool_accounts.max_validators,
331+
},
332+
validators: vec![]
333+
}
334+
);
335+
336+
// Check stake account no longer exists
337+
let account = context
338+
.banks_client
339+
.get_account(stake_address)
340+
.await
341+
.unwrap();
342+
assert!(account.is_none());
343+
}

stake-pool/program/tests/helpers/mod.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,13 +2384,15 @@ pub async fn get_validator_list_sum(
23842384
validator_sum + reserve_stake.lamports - rent - MINIMUM_RESERVE_LAMPORTS
23852385
}
23862386

2387-
pub fn add_vote_account(program_test: &mut ProgramTest) -> Pubkey {
2387+
pub fn add_vote_account_with_pubkey(
2388+
voter_pubkey: &Pubkey,
2389+
program_test: &mut ProgramTest,
2390+
) -> Pubkey {
23882391
let authorized_voter = Pubkey::new_unique();
23892392
let authorized_withdrawer = Pubkey::new_unique();
23902393
let commission = 1;
23912394

23922395
// create vote account
2393-
let vote_pubkey = Pubkey::new_unique();
23942396
let node_pubkey = Pubkey::new_unique();
23952397
let vote_state = VoteStateVersions::new_current(VoteState::new(
23962398
&VoteInit {
@@ -2408,8 +2410,13 @@ pub fn add_vote_account(program_test: &mut ProgramTest) -> Pubkey {
24082410
false,
24092411
Epoch::default(),
24102412
);
2411-
program_test.add_account(vote_pubkey, vote_account);
2412-
vote_pubkey
2413+
program_test.add_account(*voter_pubkey, vote_account);
2414+
*voter_pubkey
2415+
}
2416+
2417+
pub fn add_vote_account(program_test: &mut ProgramTest) -> Pubkey {
2418+
let voter_pubkey = Pubkey::new_unique();
2419+
add_vote_account_with_pubkey(&voter_pubkey, program_test)
24132420
}
24142421

24152422
#[allow(clippy::too_many_arguments)]

0 commit comments

Comments
 (0)