Cli: check current authorities before attempting to change them (backport #19853) (#19923)

* Cli: check current authorities before attempting to change them (#19853)

* Stake-authorize: check account current authority

* Stake-set-lockup: check account current custodian

* Make helper fn pub(crate)

* Vote-authorize: check account current authority

(cherry picked from commit 15144fc923)

# Conflicts:
#	cli/src/vote.rs

* Fix conflict

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
mergify[bot]
2021-09-15 22:53:44 +00:00
committed by GitHub
parent 97bd521725
commit 992b313941
3 changed files with 138 additions and 4 deletions

View File

@ -1951,17 +1951,36 @@ mod tests {
let result = process_command(&config); let result = process_command(&config);
assert!(result.is_ok()); assert!(result.is_ok());
let vote_account_info_response = json!(Response {
context: RpcResponseContext { slot: 1 },
value: json!({
"data": ["KLUv/QBYNQIAtAIBAAAAbnoc3Smwt4/ROvTFWY/v9O8qlxZuPKby5Pv8zYBQW/EFAAEAAB8ACQD6gx92zAiAAecDP4B2XeEBSIx7MQeung==", "base64+zstd"],
"lamports": 42,
"owner": "Vote111111111111111111111111111111111111111",
"executable": false,
"rentEpoch": 1,
}),
});
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
let mut vote_config = CliConfig {
rpc_client: Some(Arc::new(rpc_client)),
json_rpc_url: "http://127.0.0.1:8899".to_string(),
..CliConfig::default()
};
let current_authority = keypair_from_seed(&[5; 32]).unwrap();
let new_authorized_pubkey = solana_sdk::pubkey::new_rand(); let new_authorized_pubkey = solana_sdk::pubkey::new_rand();
config.signers = vec![&bob_keypair]; vote_config.signers = vec![&current_authority];
config.command = CliCommand::VoteAuthorize { vote_config.command = CliCommand::VoteAuthorize {
vote_account_pubkey: bob_pubkey, vote_account_pubkey: bob_pubkey,
new_authorized_pubkey, new_authorized_pubkey,
vote_authorize: VoteAuthorize::Voter, vote_authorize: VoteAuthorize::Withdrawer,
memo: None, memo: None,
authorized: 0, authorized: 0,
new_authorized: None, new_authorized: None,
}; };
let result = process_command(&config); let result = process_command(&vote_config);
assert!(result.is_ok()); assert!(result.is_ok());
let new_identity_keypair = Keypair::new(); let new_identity_keypair = Keypair::new();

View File

@ -32,6 +32,7 @@ use solana_sdk::{
account::from_account, account::from_account,
account_utils::StateMut, account_utils::StateMut,
clock::{Clock, UnixTimestamp, SECONDS_PER_DAY}, clock::{Clock, UnixTimestamp, SECONDS_PER_DAY},
commitment_config::CommitmentConfig,
epoch_schedule::EpochSchedule, epoch_schedule::EpochSchedule,
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
@ -1354,6 +1355,15 @@ pub fn process_stake_authorize(
) -> ProcessResult { ) -> ProcessResult {
let mut ixs = Vec::new(); let mut ixs = Vec::new();
let custodian = custodian.map(|index| config.signers[index]); let custodian = custodian.map(|index| config.signers[index]);
let current_stake_account = if !sign_only {
Some(get_stake_account_state(
rpc_client,
stake_account_pubkey,
config.commitment,
)?)
} else {
None
};
for StakeAuthorizationIndexed { for StakeAuthorizationIndexed {
authorization_type, authorization_type,
new_authority_pubkey, new_authority_pubkey,
@ -1366,6 +1376,29 @@ pub fn process_stake_authorize(
(new_authority_pubkey, "new_authorized_pubkey".to_string()), (new_authority_pubkey, "new_authorized_pubkey".to_string()),
)?; )?;
let authority = config.signers[*authority]; let authority = config.signers[*authority];
if let Some(current_stake_account) = current_stake_account {
let authorized = match current_stake_account {
StakeState::Stake(Meta { authorized, .. }, ..) => Some(authorized),
StakeState::Initialized(Meta { authorized, .. }) => Some(authorized),
_ => None,
};
if let Some(authorized) = authorized {
match authorization_type {
StakeAuthorize::Staker => {
check_current_authority(&authorized.staker, &authority.pubkey())?;
}
StakeAuthorize::Withdrawer => {
check_current_authority(&authorized.withdrawer, &authority.pubkey())?;
}
}
} else {
return Err(CliError::RpcRequestError(format!(
"{:?} is not an Initialized or Delegated stake account",
stake_account_pubkey,
))
.into());
}
}
if new_authority_signer.is_some() { if new_authority_signer.is_some() {
ixs.push(stake_instruction::authorize_checked( ixs.push(stake_instruction::authorize_checked(
stake_account_pubkey, // stake account to update stake_account_pubkey, // stake account to update
@ -1899,6 +1932,26 @@ pub fn process_stake_set_lockup(
let nonce_authority = config.signers[nonce_authority]; let nonce_authority = config.signers[nonce_authority];
let fee_payer = config.signers[fee_payer]; let fee_payer = config.signers[fee_payer];
if !sign_only {
let state = get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment)?;
let lockup = match state {
StakeState::Stake(Meta { lockup, .. }, ..) => Some(lockup),
StakeState::Initialized(Meta { lockup, .. }) => Some(lockup),
_ => None,
};
if let Some(lockup) = lockup {
if lockup.custodian != Pubkey::default() {
check_current_authority(&lockup.custodian, &custodian.pubkey())?;
}
} else {
return Err(CliError::RpcRequestError(format!(
"{:?} is not an Initialized or Delegated stake account",
stake_account_pubkey,
))
.into());
}
}
let message = if let Some(nonce_account) = &nonce_account { let message = if let Some(nonce_account) = &nonce_account {
Message::new_with_nonce( Message::new_with_nonce(
ixs, ixs,
@ -2041,6 +2094,47 @@ pub fn build_stake_state(
} }
} }
fn get_stake_account_state(
rpc_client: &RpcClient,
stake_account_pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<StakeState, Box<dyn std::error::Error>> {
let stake_account = rpc_client
.get_account_with_commitment(stake_account_pubkey, commitment_config)?
.value
.ok_or_else(|| {
CliError::RpcRequestError(format!("{:?} account does not exist", stake_account_pubkey))
})?;
if stake_account.owner != stake::program::id() {
return Err(CliError::RpcRequestError(format!(
"{:?} is not a stake account",
stake_account_pubkey,
))
.into());
}
stake_account.state().map_err(|err| {
CliError::RpcRequestError(format!(
"Account data could not be deserialized to stake state: {}",
err
))
.into()
})
}
pub(crate) fn check_current_authority(
account_current_authority: &Pubkey,
provided_current_authority: &Pubkey,
) -> Result<(), CliError> {
if account_current_authority != provided_current_authority {
Err(CliError::RpcRequestError(format!(
"Invalid current authority provided: {:?}, expected {:?}",
provided_current_authority, account_current_authority
)))
} else {
Ok(())
}
}
pub fn get_epoch_boundary_timestamps( pub fn get_epoch_boundary_timestamps(
rpc_client: &RpcClient, rpc_client: &RpcClient,
reward: &RpcInflationReward, reward: &RpcInflationReward,

View File

@ -6,6 +6,7 @@ use crate::{
}, },
memo::WithMemo, memo::WithMemo,
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
stake::check_current_authority,
}; };
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand}; use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{ use solana_clap_utils::{
@ -669,6 +670,26 @@ pub fn process_vote_authorize(
(&authorized.pubkey(), "authorized_account".to_string()), (&authorized.pubkey(), "authorized_account".to_string()),
(new_authorized_pubkey, "new_authorized_pubkey".to_string()), (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
)?; )?;
let (_, vote_state) = get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?;
match vote_authorize {
VoteAuthorize::Voter => {
let current_authorized_voter = vote_state
.authorized_voters()
.last()
.ok_or_else(|| {
CliError::RpcRequestError(
"Invalid vote account state; no authorized voters found".to_string(),
)
})?
.1;
check_current_authority(current_authorized_voter, &authorized.pubkey())?
}
VoteAuthorize::Withdrawer => {
check_current_authority(&vote_state.authorized_withdrawer, &authorized.pubkey())?
}
}
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let vote_ix = if new_authorized_signer.is_some() { let vote_ix = if new_authorized_signer.is_some() {
vote_instruction::authorize_checked( vote_instruction::authorize_checked(