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_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||||
solana_rpc_client::rpc_client::RpcClient,
|
solana_rpc_client::rpc_client::RpcClient,
|
||||||
solana_rpc_client_api::{
|
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_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
@ -57,7 +59,6 @@ use {
|
||||||
sysvar::{clock, stake_history},
|
sysvar::{clock, stake_history},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
},
|
},
|
||||||
solana_vote_program::vote_state::VoteState,
|
|
||||||
std::{ops::Deref, sync::Arc},
|
std::{ops::Deref, sync::Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2543,38 +2544,41 @@ pub fn process_delegate_stake(
|
||||||
if !sign_only {
|
if !sign_only {
|
||||||
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
||||||
// voted at the tip of the ledger
|
// voted at the tip of the ledger
|
||||||
let vote_account_data = rpc_client
|
let get_vote_accounts_config = RpcGetVoteAccountsConfig {
|
||||||
.get_account(vote_account_pubkey)
|
vote_pubkey: Some(vote_account_pubkey.to_string()),
|
||||||
.map_err(|err| {
|
keep_unstaked_delinquents: Some(true),
|
||||||
CliError::RpcRequestError(format!(
|
commitment: Some(rpc_client.commitment()),
|
||||||
"Vote account not found: {vote_account_pubkey}. error: {err}",
|
..RpcGetVoteAccountsConfig::default()
|
||||||
))
|
};
|
||||||
})?
|
let RpcVoteAccountStatus {
|
||||||
.data;
|
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(|_| {
|
let activated_stake = rpc_vote_account.activated_stake;
|
||||||
CliError::RpcRequestError(
|
let root_slot = rpc_vote_account.root_slot;
|
||||||
"Account data could not be deserialized to vote state".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let sanity_check_result = match vote_state.root_slot {
|
|
||||||
None => Err(CliError::BadParameter(
|
|
||||||
"Unable to delegate. Vote account has no root slot".to_string(),
|
|
||||||
)),
|
|
||||||
Some(root_slot) => {
|
|
||||||
let min_root_slot = rpc_client
|
let min_root_slot = rpc_client
|
||||||
.get_slot()?
|
.get_slot()
|
||||||
.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE);
|
.map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
|
||||||
if root_slot < min_root_slot {
|
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(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
Err(CliError::DynamicProgramError(format!(
|
Err(CliError::DynamicProgramError(format!(
|
||||||
"Unable to delegate. Vote account appears delinquent \
|
"Unable to delegate. Vote account appears delinquent \
|
||||||
because its current root slot, {root_slot}, is less than {min_root_slot}"
|
because its current root slot, {root_slot}, is less than {min_root_slot}"
|
||||||
)))
|
)))
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = &sanity_check_result {
|
if let Err(err) = &sanity_check_result {
|
||||||
|
|
|
@ -11,7 +11,10 @@ use {
|
||||||
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
||||||
solana_faucet::faucet::run_local_faucet,
|
solana_faucet::faucet::run_local_faucet,
|
||||||
solana_rpc_client::rpc_client::RpcClient,
|
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_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
|
@ -295,8 +298,23 @@ fn test_stake_delegation_force() {
|
||||||
let mint_pubkey = mint_keypair.pubkey();
|
let mint_pubkey = mint_keypair.pubkey();
|
||||||
let authorized_withdrawer = Keypair::new().pubkey();
|
let authorized_withdrawer = Keypair::new().pubkey();
|
||||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||||
let test_validator =
|
let slots_per_epoch = 32;
|
||||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
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 =
|
let rpc_client =
|
||||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||||
|
@ -345,7 +363,7 @@ fn test_stake_delegation_force() {
|
||||||
withdrawer: None,
|
withdrawer: None,
|
||||||
withdrawer_signer: None,
|
withdrawer_signer: None,
|
||||||
lockup: Lockup::default(),
|
lockup: Lockup::default(),
|
||||||
amount: SpendAmount::Some(50_000_000_000),
|
amount: SpendAmount::Some(25_000_000_000),
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
dump_transaction_message: false,
|
dump_transaction_message: false,
|
||||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||||
|
@ -358,7 +376,7 @@ fn test_stake_delegation_force() {
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
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.signers = vec![&default_signer];
|
||||||
config.command = CliCommand::DelegateStake {
|
config.command = CliCommand::DelegateStake {
|
||||||
stake_account_pubkey: stake_keypair.pubkey(),
|
stake_account_pubkey: stake_keypair.pubkey(),
|
||||||
|
@ -375,11 +393,55 @@ fn test_stake_delegation_force() {
|
||||||
redelegation_stake_account: None,
|
redelegation_stake_account: None,
|
||||||
compute_unit_price: 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();
|
process_command(&config).unwrap_err();
|
||||||
|
|
||||||
// But if we force it, it works anyway!
|
// But if we force it, it works anyway!
|
||||||
config.command = CliCommand::DelegateStake {
|
config.command = CliCommand::DelegateStake {
|
||||||
stake_account_pubkey: stake_keypair.pubkey(),
|
stake_account_pubkey: stake_keypair2.pubkey(),
|
||||||
vote_account_pubkey: vote_keypair.pubkey(),
|
vote_account_pubkey: vote_keypair.pubkey(),
|
||||||
stake_authority: 0,
|
stake_authority: 0,
|
||||||
force: true,
|
force: true,
|
||||||
|
|
Loading…
Reference in New Issue