cli: skip delegate-stake current voter check for unstaked voters (#32787)
* cli: use `getVoteAccounts` for delegate-stake current voter check * cli: skip delegate-stake current voter check for unstaked voters * cli: refactor delegate-stake current voter check
This commit is contained in:
parent
a1a0829a8b
commit
2ea88b41fb
|
@ -32,7 +32,9 @@ use {
|
|||
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||
solana_rpc_client::rpc_client::RpcClient,
|
||||
solana_rpc_client_api::{
|
||||
request::DELINQUENT_VALIDATOR_SLOT_DISTANCE, response::RpcInflationReward,
|
||||
config::RpcGetVoteAccountsConfig,
|
||||
request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
|
||||
response::{RpcInflationReward, RpcVoteAccountStatus},
|
||||
},
|
||||
solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
|
||||
solana_sdk::{
|
||||
|
@ -57,7 +59,6 @@ use {
|
|||
sysvar::{clock, stake_history},
|
||||
transaction::Transaction,
|
||||
},
|
||||
solana_vote_program::vote_state::VoteState,
|
||||
std::{ops::Deref, sync::Arc},
|
||||
};
|
||||
|
||||
|
@ -2543,38 +2544,41 @@ pub fn process_delegate_stake(
|
|||
if !sign_only {
|
||||
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
||||
// voted at the tip of the ledger
|
||||
let vote_account_data = rpc_client
|
||||
.get_account(vote_account_pubkey)
|
||||
.map_err(|err| {
|
||||
CliError::RpcRequestError(format!(
|
||||
"Vote account not found: {vote_account_pubkey}. error: {err}",
|
||||
))
|
||||
})?
|
||||
.data;
|
||||
let get_vote_accounts_config = RpcGetVoteAccountsConfig {
|
||||
vote_pubkey: Some(vote_account_pubkey.to_string()),
|
||||
keep_unstaked_delinquents: Some(true),
|
||||
commitment: Some(rpc_client.commitment()),
|
||||
..RpcGetVoteAccountsConfig::default()
|
||||
};
|
||||
let RpcVoteAccountStatus {
|
||||
current,
|
||||
delinquent,
|
||||
} = rpc_client.get_vote_accounts_with_config(get_vote_accounts_config)?;
|
||||
// filter should return at most one result
|
||||
let rpc_vote_account =
|
||||
current
|
||||
.get(0)
|
||||
.or_else(|| delinquent.get(0))
|
||||
.ok_or(CliError::RpcRequestError(format!(
|
||||
"Vote account not found: {vote_account_pubkey}"
|
||||
)))?;
|
||||
|
||||
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
|
||||
CliError::RpcRequestError(
|
||||
"Account data could not be deserialized to vote state".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let sanity_check_result = match vote_state.root_slot {
|
||||
None => Err(CliError::BadParameter(
|
||||
let activated_stake = rpc_vote_account.activated_stake;
|
||||
let root_slot = rpc_vote_account.root_slot;
|
||||
let min_root_slot = rpc_client
|
||||
.get_slot()
|
||||
.map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
|
||||
let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
|
||||
Ok(())
|
||||
} else if root_slot == 0 {
|
||||
Err(CliError::BadParameter(
|
||||
"Unable to delegate. Vote account has no root slot".to_string(),
|
||||
)),
|
||||
Some(root_slot) => {
|
||||
let min_root_slot = rpc_client
|
||||
.get_slot()?
|
||||
.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE);
|
||||
if root_slot < min_root_slot {
|
||||
Err(CliError::DynamicProgramError(format!(
|
||||
"Unable to delegate. Vote account appears delinquent \
|
||||
because its current root slot, {root_slot}, is less than {min_root_slot}"
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(CliError::DynamicProgramError(format!(
|
||||
"Unable to delegate. Vote account appears delinquent \
|
||||
because its current root slot, {root_slot}, is less than {min_root_slot}"
|
||||
)))
|
||||
};
|
||||
|
||||
if let Err(err) = &sanity_check_result {
|
||||
|
|
|
@ -11,7 +11,10 @@ use {
|
|||
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
||||
solana_faucet::faucet::run_local_faucet,
|
||||
solana_rpc_client::rpc_client::RpcClient,
|
||||
solana_rpc_client_api::response::{RpcStakeActivation, StakeActivationState},
|
||||
solana_rpc_client_api::{
|
||||
request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
|
||||
response::{RpcStakeActivation, StakeActivationState},
|
||||
},
|
||||
solana_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery},
|
||||
solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
|
@ -295,8 +298,23 @@ fn test_stake_delegation_force() {
|
|||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let authorized_withdrawer = Keypair::new().pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
let slots_per_epoch = 32;
|
||||
let test_validator = TestValidatorGenesis::default()
|
||||
.fee_rate_governor(FeeRateGovernor::new(0, 0))
|
||||
.rent(Rent {
|
||||
lamports_per_byte_year: 1,
|
||||
exemption_threshold: 1.0,
|
||||
..Rent::default()
|
||||
})
|
||||
.epoch_schedule(EpochSchedule::custom(
|
||||
slots_per_epoch,
|
||||
slots_per_epoch,
|
||||
/* enable_warmup_epochs = */ false,
|
||||
))
|
||||
.faucet_addr(Some(faucet_addr))
|
||||
.warp_slot(DELINQUENT_VALIDATOR_SLOT_DISTANCE * 2) // get out in front of the cli voter delinquency check
|
||||
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
|
||||
.expect("validator start failed");
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
@ -345,7 +363,7 @@ fn test_stake_delegation_force() {
|
|||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000_000_000),
|
||||
amount: SpendAmount::Some(25_000_000_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
|
@ -358,7 +376,7 @@ fn test_stake_delegation_force() {
|
|||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
// Delegate stake fails (vote account had never voted)
|
||||
// Delegate stake succeeds despite no votes, because voter has zero stake
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::DelegateStake {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
|
@ -375,11 +393,55 @@ fn test_stake_delegation_force() {
|
|||
redelegation_stake_account: None,
|
||||
compute_unit_price: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
// Create a second stake account
|
||||
let stake_keypair2 = Keypair::new();
|
||||
config.signers = vec![&default_signer, &stake_keypair2];
|
||||
config.command = CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(25_000_000_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
from: 0,
|
||||
compute_unit_price: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
wait_for_next_epoch_plus_n_slots(&rpc_client, 1);
|
||||
|
||||
// Delegate stake2 fails because voter has not voted, but is now staked
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::DelegateStake {
|
||||
stake_account_pubkey: stake_keypair2.pubkey(),
|
||||
vote_account_pubkey: vote_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
force: false,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
redelegation_stake_account: None,
|
||||
compute_unit_price: None,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// But if we force it, it works anyway!
|
||||
config.command = CliCommand::DelegateStake {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
stake_account_pubkey: stake_keypair2.pubkey(),
|
||||
vote_account_pubkey: vote_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
|
|
Loading…
Reference in New Issue