* 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:
@ -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![¤t_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();
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
Reference in New Issue
Block a user