Skip to content

Commit

Permalink
Cli: check current authorities before attempting to change them (#19853)
Browse files Browse the repository at this point in the history
* Stake-authorize: check account current authority

* Stake-set-lockup: check account current custodian

* Make helper fn pub(crate)

* Vote-authorize: check account current authority
  • Loading branch information
CriesofCarrots authored Sep 15, 2021
1 parent 3a4ea28 commit 15144fc
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 4 deletions.
27 changes: 23 additions & 4 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1973,17 +1973,36 @@ mod tests {
let result = process_command(&config);
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();
config.signers = vec![&bob_keypair];
config.command = CliCommand::VoteAuthorize {
vote_config.signers = vec![&current_authority];
vote_config.command = CliCommand::VoteAuthorize {
vote_account_pubkey: bob_pubkey,
new_authorized_pubkey,
vote_authorize: VoteAuthorize::Voter,
vote_authorize: VoteAuthorize::Withdrawer,
memo: None,
authorized: 0,
new_authorized: None,
};
let result = process_command(&config);
let result = process_command(&vote_config);
assert!(result.is_ok());

let new_identity_keypair = Keypair::new();
Expand Down
94 changes: 94 additions & 0 deletions cli/src/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use solana_sdk::{
account::from_account,
account_utils::StateMut,
clock::{Clock, UnixTimestamp, SECONDS_PER_DAY},
commitment_config::CommitmentConfig,
epoch_schedule::EpochSchedule,
message::Message,
pubkey::Pubkey,
Expand Down Expand Up @@ -1353,6 +1354,15 @@ pub fn process_stake_authorize(
) -> ProcessResult {
let mut ixs = Vec::new();
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 {
authorization_type,
new_authority_pubkey,
Expand All @@ -1365,6 +1375,29 @@ pub fn process_stake_authorize(
(new_authority_pubkey, "new_authorized_pubkey".to_string()),
)?;
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() {
ixs.push(stake_instruction::authorize_checked(
stake_account_pubkey, // stake account to update
Expand Down Expand Up @@ -1892,6 +1925,26 @@ pub fn process_stake_set_lockup(
let nonce_authority = config.signers[nonce_authority];
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 {
Message::new_with_nonce(
ixs,
Expand Down Expand Up @@ -2034,6 +2087,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(
rpc_client: &RpcClient,
reward: &RpcInflationReward,
Expand Down
20 changes: 20 additions & 0 deletions cli/src/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
},
memo::WithMemo,
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 solana_clap_utils::{
Expand Down Expand Up @@ -730,6 +731,25 @@ pub fn process_vote_authorize(
(&authorized.pubkey(), "authorized_account".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 latest_blockhash = rpc_client.get_latest_blockhash()?;
let vote_ix = if new_authorized_signer.is_some() {
vote_instruction::authorize_checked(
Expand Down

0 comments on commit 15144fc

Please sign in to comment.