diff --git a/book/src/paper-wallet/usage.md b/book/src/paper-wallet/usage.md index e7c6051321..de45bd689f 100644 --- a/book/src/paper-wallet/usage.md +++ b/book/src/paper-wallet/usage.md @@ -251,10 +251,10 @@ Refer to the following page for a comprehensive guide on running a validator: Solana CLI tooling supports secure keypair input for stake delegation. To do so, first create a stake account with some SOL. Use the special `ASK` keyword to trigger a seed phrase input prompt for the stake account and use -`--ask-seed-phrase keypair` to securely input the funding keypair. +`--keypair ASK` to securely input the funding keypair. ```bash -solana create-stake-account ASK 1 --ask-seed-phrase keypair +solana create-stake-account ASK 1 --keypair ASK [stake_account] seed phrase: 🔒 [stake_account] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: @@ -262,11 +262,11 @@ solana create-stake-account ASK 1 --ask-seed-phrase keypair [keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: ``` -Then, to delegate that stake to a validator, use `--ask-seed-phrase keypair` to +Then, to delegate that stake to a validator, use `--keypair ASK` to securely input the funding keypair. ```bash -solana delegate-stake --ask-seed-phrase keypair +solana delegate-stake --keypair ASK [keypair] seed phrase: 🔒 [keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: diff --git a/clap-utils/src/input_parsers.rs b/clap-utils/src/input_parsers.rs index a2b2c333c3..b81767af14 100644 --- a/clap-utils/src/input_parsers.rs +++ b/clap-utils/src/input_parsers.rs @@ -1,4 +1,6 @@ -use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG}; +use crate::keypair::{ + keypair_from_seed_phrase, keypair_util_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG, +}; use chrono::DateTime; use clap::ArgMatches; use solana_remote_wallet::remote_wallet::DerivationPath; @@ -93,6 +95,18 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option, +) -> Result>, Box> { + if let Some(location) = matches.value_of(name) { + keypair_util_from_path(matches, location, name).map(Some) + } else { + Ok(None) + } +} + pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option { value_of(matches, name).map(sol_to_lamports) } diff --git a/clap-utils/src/input_validators.rs b/clap-utils/src/input_validators.rs index 6c68278418..1b13b88da1 100644 --- a/clap-utils/src/input_validators.rs +++ b/clap-utils/src/input_validators.rs @@ -1,5 +1,6 @@ -use crate::keypair::ASK_KEYWORD; +use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD}; use chrono::DateTime; +use solana_remote_wallet::remote_keypair::generate_remote_keypair; use solana_sdk::{ hash::Hash, pubkey::Pubkey, @@ -50,6 +51,16 @@ pub fn is_pubkey_or_keypair_or_ask_keyword(string: String) -> Result<(), String> is_pubkey(string.clone()).or_else(|_| is_keypair_or_ask_keyword(string)) } +pub fn is_valid_signer(string: String) -> Result<(), String> { + match parse_keypair_path(&string) { + KeypairUrl::Usb(path) => generate_remote_keypair(path, None) + .map(|_| ()) + .map_err(|err| format!("{:?}", err)), + KeypairUrl::Filepath(path) => is_keypair(path), + _ => Ok(()), + } +} + // Return an error if string cannot be parsed as pubkey=signature string pub fn is_pubkey_sig(string: String) -> Result<(), String> { let mut signer = string.split('='); diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index d8be15ae17..180d4aaa92 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -1,14 +1,24 @@ -use crate::ArgConstant; +use crate::{ + input_parsers::{derivation_of, pubkeys_sigs_of}, + offline::SIGNER_ARG, + ArgConstant, +}; use bip39::{Language, Mnemonic, Seed}; -use clap::values_t; +use clap::{values_t, ArgMatches, Error, ErrorKind}; use rpassword::prompt_password_stderr; -use solana_sdk::signature::{ - keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair_file, Keypair, Signer, +use solana_remote_wallet::remote_keypair::generate_remote_keypair; +use solana_sdk::{ + pubkey::Pubkey, + signature::{ + keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair, + read_keypair_file, Keypair, Presigner, Signature, Signer, + }, }; use std::{ error, io::{stdin, stdout, Write}, process::exit, + str::FromStr, }; pub enum KeypairUrl { @@ -16,6 +26,7 @@ pub enum KeypairUrl { Filepath(String), Usb(String), Stdin, + Pubkey(Pubkey), } pub fn parse_keypair_path(path: &str) -> KeypairUrl { @@ -25,11 +36,66 @@ pub fn parse_keypair_path(path: &str) -> KeypairUrl { KeypairUrl::Ask } else if path.starts_with("usb://") { KeypairUrl::Usb(path.split_at(6).1.to_string()) + } else if let Ok(pubkey) = Pubkey::from_str(path) { + KeypairUrl::Pubkey(pubkey) } else { KeypairUrl::Filepath(path.to_string()) } } +pub fn presigner_from_pubkey_sigs( + pubkey: &Pubkey, + signers: &[(Pubkey, Signature)], +) -> Option { + signers.iter().find_map(|(signer, sig)| { + if *signer == *pubkey { + Some(Presigner::new(signer, sig)) + } else { + None + } + }) +} + +pub fn keypair_util_from_path( + matches: &ArgMatches, + path: &str, + keypair_name: &str, +) -> Result, Box> { + match parse_keypair_path(path) { + KeypairUrl::Ask => { + let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); + Ok(Box::new(keypair_from_seed_phrase( + keypair_name, + skip_validation, + false, + )?)) + } + KeypairUrl::Filepath(path) => Ok(Box::new(read_keypair_file(&path)?)), + KeypairUrl::Stdin => { + let mut stdin = std::io::stdin(); + Ok(Box::new(read_keypair(&mut stdin)?)) + } + KeypairUrl::Usb(path) => Ok(Box::new(generate_remote_keypair( + path, + derivation_of(matches, "derivation_path"), + )?)), + KeypairUrl::Pubkey(pubkey) => { + let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name) + .as_ref() + .and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners)); + if let Some(presigner) = presigner { + Ok(Box::new(presigner)) + } else { + Err(Error::with_description( + "Missing signature for supplied pubkey", + ErrorKind::MissingRequiredArgument, + ) + .into()) + } + } + } +} + // Keyword used to indicate that the user should be asked for a keypair seed phrase pub const ASK_KEYWORD: &str = "ASK"; diff --git a/clap-utils/src/lib.rs b/clap-utils/src/lib.rs index ea5bd45d46..a66efa3981 100644 --- a/clap-utils/src/lib.rs +++ b/clap-utils/src/lib.rs @@ -26,3 +26,4 @@ pub struct ArgConstant<'a> { pub mod input_parsers; pub mod input_validators; pub mod keypair; +pub mod offline; diff --git a/clap-utils/src/offline.rs b/clap-utils/src/offline.rs new file mode 100644 index 0000000000..b0533a28de --- /dev/null +++ b/clap-utils/src/offline.rs @@ -0,0 +1,19 @@ +use crate::ArgConstant; + +pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant { + name: "blockhash", + long: "blockhash", + help: "Use the supplied blockhash", +}; + +pub const SIGN_ONLY_ARG: ArgConstant<'static> = ArgConstant { + name: "sign_only", + long: "sign-only", + help: "Sign the transaction offline", +}; + +pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant { + name: "signer", + long: "signer", + help: "Provide a public-key/signature pair for the transaction", +}; diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 17f5cb5a69..44d6b7167a 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -14,16 +14,15 @@ use log::*; use num_traits::FromPrimitive; use serde_json::{self, json, Value}; use solana_budget_program::budget_instruction::{self, BudgetError}; -use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant}; +use solana_clap_utils::{ + input_parsers::*, input_validators::*, offline::SIGN_ONLY_ARG, ArgConstant, +}; use solana_client::{client_error::ClientError, rpc_client::RpcClient}; #[cfg(not(test))] use solana_faucet::faucet::request_airdrop_transaction; #[cfg(test)] use solana_faucet::faucet_mock::request_airdrop_transaction; -use solana_remote_wallet::{ - ledger::get_ledger_from_info, - remote_wallet::{DerivationPath, RemoteWallet, RemoteWalletInfo}, -}; +use solana_remote_wallet::remote_wallet::DerivationPath; use solana_sdk::{ bpf_loader, clock::{Epoch, Slot}, @@ -36,9 +35,8 @@ use solana_sdk::{ native_token::lamports_to_sol, program_utils::DecodeError, pubkey::Pubkey, - signature::{keypair_from_seed, Keypair, Signature, Signer, SignerError}, + signature::{Keypair, Signature, Signer, SignerError}, system_instruction::{self, create_address_with_seed, SystemError, MAX_ADDRESS_SEED_LEN}, - system_transaction, transaction::{Transaction, TransactionError}, }; use solana_stake_program::stake_state::{Lockup, StakeAuthorize}; @@ -48,6 +46,7 @@ use std::{ fs::File, io::{Read, Write}, net::{IpAddr, SocketAddr}, + rc::Rc, thread::sleep, time::Duration, {error, fmt}, @@ -67,8 +66,8 @@ pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(FEE_PAYER_ARG.name) .long(FEE_PAYER_ARG.long) .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help(FEE_PAYER_ARG.help) } @@ -94,85 +93,6 @@ impl std::ops::Deref for KeypairEq { } } -#[derive(Debug)] -pub enum SigningAuthority { - Online(Keypair), - // We hold a random keypair alongside our legit pubkey in order - // to generate a placeholder signature in the transaction - Offline(Pubkey, Keypair), -} - -impl SigningAuthority { - pub fn new_from_matches( - matches: &ArgMatches<'_>, - name: &str, - signers: Option<&[(Pubkey, Signature)]>, - ) -> Result, CliError> { - if matches.is_present(name) { - keypair_of(matches, name) - .map(|keypair| keypair.into()) - .or_else(|| { - pubkey_of(matches, name) - .filter(|pubkey| { - signers - .and_then(|signers| { - signers.iter().find(|(signer, _sig)| *signer == *pubkey) - }) - .is_some() - }) - .map(|pubkey| pubkey.into()) - }) - .ok_or_else(|| CliError::BadParameter("Invalid authority".to_string())) - .map(Some) - } else { - Ok(None) - } - } - - pub fn keypair(&self) -> &Keypair { - match self { - SigningAuthority::Online(keypair) => keypair, - SigningAuthority::Offline(_pubkey, keypair) => keypair, - } - } - - pub fn pubkey(&self) -> Pubkey { - match self { - SigningAuthority::Online(keypair) => keypair.pubkey(), - SigningAuthority::Offline(pubkey, _keypair) => *pubkey, - } - } -} - -impl From for SigningAuthority { - fn from(keypair: Keypair) -> Self { - SigningAuthority::Online(keypair) - } -} - -impl From for SigningAuthority { - fn from(pubkey: Pubkey) -> Self { - SigningAuthority::Offline(pubkey, keypair_from_seed(pubkey.as_ref()).unwrap()) - } -} - -impl PartialEq for SigningAuthority { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (SigningAuthority::Online(keypair1), SigningAuthority::Online(keypair2)) => { - keypair1.pubkey() == keypair2.pubkey() - } - (SigningAuthority::Online(keypair), SigningAuthority::Offline(pubkey, _)) - | (SigningAuthority::Offline(pubkey, _), SigningAuthority::Online(keypair)) => { - keypair.pubkey() == *pubkey - } - (SigningAuthority::Offline(pubkey1, _), SigningAuthority::Offline(pubkey2, _)) => { - pubkey1 == pubkey2 - } - } - } -} - pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> { nonce::nonce_authority_arg().requires(NONCE_ARG.name) } @@ -186,10 +106,9 @@ pub struct PayCommand { pub witnesses: Option>, pub cancelable: bool, pub sign_only: bool, - pub signers: Option>, pub blockhash_query: BlockhashQuery, pub nonce_account: Option, - pub nonce_authority: Option, + pub nonce_authority: Option>, } #[derive(Debug, PartialEq)] @@ -245,11 +164,11 @@ pub enum CliCommand { // Nonce commands AuthorizeNonceAccount { nonce_account: Pubkey, - nonce_authority: Option, + nonce_authority: Option>, new_authority: Pubkey, }, CreateNonceAccount { - nonce_account: KeypairEq, + nonce_account: Rc>, seed: Option, nonce_authority: Option, lamports: u64, @@ -257,7 +176,7 @@ pub enum CliCommand { GetNonce(Pubkey), NewNonce { nonce_account: Pubkey, - nonce_authority: Option, + nonce_authority: Option>, }, ShowNonceAccount { nonce_account_pubkey: Pubkey, @@ -265,7 +184,7 @@ pub enum CliCommand { }, WithdrawFromNonceAccount { nonce_account: Pubkey, - nonce_authority: Option, + nonce_authority: Option>, destination_account_pubkey: Pubkey, lamports: u64, }, @@ -273,54 +192,50 @@ pub enum CliCommand { Deploy(String), // Stake Commands CreateStakeAccount { - stake_account: SigningAuthority, + stake_account: Rc>, seed: Option, staker: Option, withdrawer: Option, lockup: Lockup, lamports: u64, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, - from: Option, + nonce_authority: Option>, + fee_payer: Option>, + from: Option>, }, DeactivateStake { stake_account_pubkey: Pubkey, - stake_authority: Option, + stake_authority: Option>, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, + nonce_authority: Option>, + fee_payer: Option>, }, DelegateStake { stake_account_pubkey: Pubkey, vote_account_pubkey: Pubkey, - stake_authority: Option, + stake_authority: Option>, force: bool, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, + nonce_authority: Option>, + fee_payer: Option>, }, SplitStake { stake_account_pubkey: Pubkey, - stake_authority: Option, + stake_authority: Option>, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - split_stake_account: KeypairEq, + nonce_authority: Option>, + split_stake_account: Rc>, seed: Option, lamports: u64, - fee_payer: Option, + fee_payer: Option>, }, ShowStakeHistory { use_lamports_unit: bool, @@ -333,36 +248,33 @@ pub enum CliCommand { stake_account_pubkey: Pubkey, new_authorized_pubkey: Pubkey, stake_authorize: StakeAuthorize, - authority: Option, + authority: Option>, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, + nonce_authority: Option>, + fee_payer: Option>, }, StakeSetLockup { stake_account_pubkey: Pubkey, lockup: Lockup, - custodian: Option, + custodian: Option>, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, + nonce_authority: Option>, + fee_payer: Option>, }, WithdrawStake { stake_account_pubkey: Pubkey, destination_account_pubkey: Pubkey, lamports: u64, - withdraw_authority: Option, + withdraw_authority: Option>, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, + nonce_authority: Option>, + fee_payer: Option>, }, // Storage Commands CreateStorageAccount { @@ -429,13 +341,12 @@ pub enum CliCommand { Transfer { lamports: u64, to: Pubkey, - from: Option, + from: Option>, sign_only: bool, - signers: Option>, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option, - fee_payer: Option, + nonce_authority: Option>, + fee_payer: Option>, }, Witness(Pubkey, Pubkey), // Witness(to, process_id) } @@ -474,10 +385,16 @@ impl error::Error for CliError { } } +impl From> for CliError { + fn from(error: Box) -> Self { + CliError::DynamicProgramError(format!("{:?}", error)) + } +} + pub struct CliConfig { pub command: CliCommand, pub json_rpc_url: String, - pub keypair: Keypair, + pub keypair: Box, pub keypair_path: Option, pub derivation_path: Option, pub rpc_client: Option, @@ -495,20 +412,8 @@ impl CliConfig { "http://127.0.0.1:8899".to_string() } - pub(crate) fn pubkey(&self) -> Result> { - if let Some(path) = &self.keypair_path { - if path.starts_with("usb://") { - let (remote_wallet_info, mut derivation_path) = - RemoteWalletInfo::parse_path(path.to_string())?; - if let Some(derivation) = &self.derivation_path { - let derivation = derivation.clone(); - derivation_path = derivation; - } - let ledger = get_ledger_from_info(remote_wallet_info)?; - return Ok(ledger.get_pubkey(&derivation_path)?); - } - } - Ok(self.keypair.pubkey()) + pub(crate) fn pubkey(&self) -> Result { + self.keypair.try_pubkey() } } @@ -520,7 +425,7 @@ impl Default for CliConfig { use_lamports_unit: false, }, json_rpc_url: Self::default_json_rpc_url(), - keypair: Keypair::new(), + keypair: Box::new(Keypair::new()), keypair_path: Some(Self::default_keypair_path()), derivation_path: None, rpc_client: None, @@ -701,14 +606,9 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result ProcessResult { .to_string()) } -pub fn replace_signatures(tx: &mut Transaction, signers: &[(Pubkey, Signature)]) -> ProcessResult { - tx.replace_signatures(signers).map_err(|_| { - CliError::BadParameter( - "Transaction construction failed, incorrect signature or public key provided" - .to_string(), - ) - })?; - Ok("".to_string()) -} - pub fn parse_create_address_with_seed( matches: &ArgMatches<'_>, ) -> Result { @@ -1055,37 +934,39 @@ fn process_deploy( let mut messages: Vec<&Message> = Vec::new(); let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(program_data.len())?; - let mut create_account_tx = system_transaction::create_account( - &config.keypair, - &program_id, - blockhash, + let ix = system_instruction::create_account( + &config.keypair.pubkey(), + &program_id.pubkey(), minimum_balance.max(1), program_data.len() as u64, &bpf_loader::id(), ); + let message = Message::new(vec![ix]); + let mut create_account_tx = Transaction::new_unsigned(message); + create_account_tx.try_sign(&[config.keypair.as_ref(), &program_id], blockhash)?; messages.push(&create_account_tx.message); - let signers = [&config.keypair, &program_id]; - let write_transactions: Vec<_> = program_data - .chunks(DATA_CHUNK_SIZE) - .zip(0..) - .map(|(chunk, i)| { - let instruction = loader_instruction::write( - &program_id.pubkey(), - &bpf_loader::id(), - (i * DATA_CHUNK_SIZE) as u32, - chunk.to_vec(), - ); - let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); - Transaction::new(&signers, message, blockhash) - }) - .collect(); + let signers = [config.keypair.as_ref(), &program_id]; + let mut write_transactions = vec![]; + for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { + let instruction = loader_instruction::write( + &program_id.pubkey(), + &bpf_loader::id(), + (i * DATA_CHUNK_SIZE) as u32, + chunk.to_vec(), + ); + let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, blockhash)?; + write_transactions.push(tx); + } for transaction in write_transactions.iter() { messages.push(&transaction.message); } let instruction = loader_instruction::finalize(&program_id.pubkey(), &bpf_loader::id()); let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); - let mut finalize_tx = Transaction::new(&signers, message, blockhash); + let mut finalize_tx = Transaction::new_unsigned(message); + finalize_tx.try_sign(&signers, blockhash)?; messages.push(&finalize_tx.message); check_account_for_multiple_fees( @@ -1127,10 +1008,9 @@ fn process_pay( witnesses: &Option>, cancelable: bool, sign_only: bool, - signers: &Option>, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, ) -> ProcessResult { check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), @@ -1146,35 +1026,27 @@ fn process_pay( }; if timestamp == None && *witnesses == None { + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let ix = system_instruction::transfer(&config.keypair.pubkey(), to, lamports); let mut tx = if let Some(nonce_account) = &nonce_account { - let nonce_authority: &Keypair = nonce_authority - .map(|authority| authority.keypair()) - .unwrap_or(&config.keypair); - system_transaction::nonced_transfer( - &config.keypair, - to, - lamports, - nonce_account, - nonce_authority, - blockhash, - ) + let message = + Message::new_with_nonce(vec![ix], None, nonce_account, &nonce_authority.pubkey()); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref(), nonce_authority], blockhash)?; + tx } else { - system_transaction::transfer(&config.keypair, to, lamports, blockhash) + let message = Message::new(vec![ix]); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref()], blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } - if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { - let nonce_authority: Pubkey = nonce_authority - .map(|authority| authority.pubkey()) - .unwrap_or_else(|| config.keypair.pubkey()); let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority, &blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &blockhash)?; } check_account_for_fee( rpc_client, @@ -1182,7 +1054,8 @@ fn process_pay( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = + rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } else if *witnesses == None { @@ -1204,14 +1077,9 @@ fn process_pay( cancelable, lamports, ); - let mut tx = Transaction::new_signed_instructions( - &[&config.keypair, &contract_state], - ixs, - blockhash, - ); - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } + let message = Message::new(ixs); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref(), &contract_state], blockhash)?; if sign_only { return_signers(&tx) } else { @@ -1222,7 +1090,7 @@ fn process_pay( &tx.message, )?; let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]); + .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), &contract_state]); let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ @@ -1252,19 +1120,14 @@ fn process_pay( cancelable, lamports, ); - let mut tx = Transaction::new_signed_instructions( - &[&config.keypair, &contract_state], - ixs, - blockhash, - ); - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } + let message = Message::new(ixs); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref(), &contract_state], blockhash)?; if sign_only { return_signers(&tx) } else { let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]); + .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), &contract_state]); check_account_for_fee( rpc_client, &config.keypair.pubkey(), @@ -1291,14 +1154,16 @@ fn process_cancel(rpc_client: &RpcClient, config: &CliConfig, pubkey: &Pubkey) - pubkey, &config.keypair.pubkey(), ); - let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); + let message = Message::new(vec![ix]); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref()], blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } @@ -1312,14 +1177,16 @@ fn process_time_elapsed( let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let ix = budget_instruction::apply_timestamp(&config.keypair.pubkey(), pubkey, to, dt); - let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); + let message = Message::new(vec![ix]); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref()], blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } @@ -1329,20 +1196,17 @@ fn process_transfer( config: &CliConfig, lamports: u64, to: &Pubkey, - from: Option<&SigningAuthority>, + from: Option<&(dyn Signer + 'static)>, sign_only: bool, - signers: Option<&Vec<(Pubkey, Signature)>>, blockhash_query: &BlockhashQuery, nonce_account: Option<&Pubkey>, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, + nonce_authority: Option<&(dyn Signer + 'static)>, + fee_payer: Option<&(dyn Signer + 'static)>, ) -> ProcessResult { - let (from_pubkey, from) = from - .map(|f| (f.pubkey(), f.keypair())) - .unwrap_or((config.keypair.pubkey(), &config.keypair)); + let from = from.unwrap_or_else(|| config.keypair.as_ref()); check_unique_pubkeys( - (&from_pubkey, "cli keypair".to_string()), + (&from.pubkey(), "cli keypair".to_string()), (to, "to".to_string()), )?; @@ -1350,38 +1214,36 @@ fn process_transfer( blockhash_query.get_blockhash_fee_calculator(rpc_client)?; let ixs = vec![system_instruction::transfer(&from.pubkey(), to, lamports)]; - let (nonce_authority_pubkey, nonce_authority) = nonce_authority - .map(|authority| (authority.pubkey(), authority.keypair())) - .unwrap_or((config.keypair.pubkey(), &config.keypair)); - let fee_payer = fee_payer.map(|fp| fp.keypair()).unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); + let mut signers = vec![fee_payer]; + if *fee_payer != *from { + signers.push(from) + } let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + signers.push(nonce_authority); + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &[fee_payer, from, nonce_authority], nonce_account, &nonce_authority.pubkey(), - recent_blockhash, - ) + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, recent_blockhash)?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &[fee_payer, from], - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } - if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -1389,7 +1251,7 @@ fn process_transfer( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -1403,14 +1265,16 @@ fn process_witness( let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let ix = budget_instruction::apply_signature(&config.keypair.pubkey(), pubkey, to); - let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); + let message = Message::new(vec![ix]); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref()], blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } @@ -1503,7 +1367,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, nonce_account, - nonce_authority.as_ref(), + nonce_authority.as_deref(), new_authority, ), // Create nonce account @@ -1515,7 +1379,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { } => process_create_nonce_account( &rpc_client, config, - nonce_account, + nonce_account.as_ref().as_ref(), seed.clone(), *nonce_authority, *lamports, @@ -1528,7 +1392,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::NewNonce { nonce_account, ref nonce_authority, - } => process_new_nonce(&rpc_client, config, nonce_account, nonce_authority.as_ref()), + } => process_new_nonce( + &rpc_client, + config, + nonce_account, + nonce_authority.as_deref(), + ), // Show the contents of a nonce account CliCommand::ShowNonceAccount { nonce_account_pubkey, @@ -1544,7 +1413,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, &nonce_account, - nonce_authority.as_ref(), + nonce_authority.as_deref(), &destination_account_pubkey, *lamports, ), @@ -1567,7 +1436,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { lockup, lamports, sign_only, - ref signers, blockhash_query, ref nonce_account, ref nonce_authority, @@ -1576,25 +1444,23 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { } => process_create_stake_account( &rpc_client, config, - stake_account, + stake_account.as_ref().as_ref(), seed, staker, withdrawer, lockup, *lamports, *sign_only, - signers.as_ref(), blockhash_query, nonce_account.as_ref(), - nonce_authority.as_ref(), - fee_payer.as_ref(), - from.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), + from.as_deref(), ), CliCommand::DeactivateStake { stake_account_pubkey, ref stake_authority, sign_only, - ref signers, blockhash_query, nonce_account, ref nonce_authority, @@ -1603,13 +1469,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, &stake_account_pubkey, - stake_authority.as_ref(), + stake_authority.as_deref(), *sign_only, - signers, blockhash_query, *nonce_account, - nonce_authority.as_ref(), - fee_payer.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), ), CliCommand::DelegateStake { stake_account_pubkey, @@ -1617,7 +1482,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { ref stake_authority, force, sign_only, - ref signers, blockhash_query, nonce_account, ref nonce_authority, @@ -1627,20 +1491,18 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { config, &stake_account_pubkey, &vote_account_pubkey, - stake_authority.as_ref(), + stake_authority.as_deref(), *force, *sign_only, - signers, blockhash_query, *nonce_account, - nonce_authority.as_ref(), - fee_payer.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), ), CliCommand::SplitStake { stake_account_pubkey, ref stake_authority, sign_only, - ref signers, blockhash_query, nonce_account, ref nonce_authority, @@ -1652,16 +1514,15 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, &stake_account_pubkey, - stake_authority.as_ref(), + stake_authority.as_deref(), *sign_only, - signers, blockhash_query, *nonce_account, - nonce_authority.as_ref(), - split_stake_account, + nonce_authority.as_deref(), + split_stake_account.as_ref().as_ref(), seed, *lamports, - fee_payer.as_ref(), + fee_payer.as_deref(), ), CliCommand::ShowStakeAccount { pubkey: stake_account_pubkey, @@ -1681,7 +1542,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { stake_authorize, ref authority, sign_only, - ref signers, blockhash_query, nonce_account, ref nonce_authority, @@ -1692,20 +1552,18 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &stake_account_pubkey, &new_authorized_pubkey, *stake_authorize, - authority.as_ref(), + authority.as_deref(), *sign_only, - signers, blockhash_query, *nonce_account, - nonce_authority.as_ref(), - fee_payer.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), ), CliCommand::StakeSetLockup { stake_account_pubkey, mut lockup, ref custodian, sign_only, - ref signers, blockhash_query, nonce_account, ref nonce_authority, @@ -1715,13 +1573,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { config, &stake_account_pubkey, &mut lockup, - custodian.as_ref(), + custodian.as_deref(), *sign_only, - signers, blockhash_query, *nonce_account, - nonce_authority.as_ref(), - fee_payer.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), ), CliCommand::WithdrawStake { stake_account_pubkey, @@ -1729,7 +1586,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { lamports, ref withdraw_authority, sign_only, - ref signers, blockhash_query, ref nonce_account, ref nonce_authority, @@ -1740,13 +1596,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &stake_account_pubkey, &destination_account_pubkey, *lamports, - withdraw_authority.as_ref(), + withdraw_authority.as_deref(), *sign_only, - signers.as_ref(), blockhash_query, nonce_account.as_ref(), - nonce_authority.as_ref(), - fee_payer.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), ), // Storage Commands @@ -1890,7 +1745,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { ref witnesses, cancelable, sign_only, - ref signers, blockhash_query, nonce_account, ref nonce_authority, @@ -1904,10 +1758,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { witnesses, *cancelable, *sign_only, - signers, blockhash_query, *nonce_account, - nonce_authority.as_ref(), + nonce_authority.as_deref(), ), CliCommand::ShowAccount { pubkey, @@ -1929,7 +1782,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { to, ref from, sign_only, - ref signers, ref blockhash_query, ref nonce_account, ref nonce_authority, @@ -1939,13 +1791,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { config, *lamports, to, - from.as_ref(), + from.as_deref(), *sign_only, - signers.as_ref(), blockhash_query, nonce_account.as_ref(), - nonce_authority.as_ref(), - fee_payer.as_ref(), + nonce_authority.as_deref(), + fee_payer.as_deref(), ), // Apply witness signature to contract CliCommand::Witness(to, pubkey) => process_witness(&rpc_client, config, &to, &pubkey), @@ -2121,7 +1972,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .index(1) .value_name("PUBKEY") .takes_value(true) - .validator(is_pubkey_or_keypair) + .validator(is_valid_signer) .help("The public key of the balance to check"), ) .arg( @@ -2330,8 +2181,8 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' Arg::with_name("from") .long("from") .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help("Source account of funds (if different from client local account)"), ) .offline_args() @@ -2384,11 +2235,11 @@ mod tests { account::Account, nonce_state::{Meta as NonceMeta, NonceState}, pubkey::Pubkey, - signature::{keypair_from_seed, read_keypair_file, write_keypair_file}, + signature::{keypair_from_seed, read_keypair_file, write_keypair_file, Presigner}, system_program, transaction::TransactionError, }; - use std::{collections::HashMap, path::PathBuf}; + use std::{collections::HashMap, path::PathBuf, rc::Rc}; fn make_tmp_path(name: &str) -> String { let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); @@ -2404,13 +2255,6 @@ mod tests { path } - #[test] - fn test_signing_authority_dummy_keypairs() { - let signing_authority: SigningAuthority = Pubkey::new(&[1u8; 32]).into(); - assert_eq!(signing_authority, Pubkey::new(&[1u8; 32]).into()); - assert_ne!(signing_authority, Pubkey::new(&[2u8; 32]).into()); - } - #[test] fn test_cli_parse_command() { let test_commands = app("test", "desc", "version"); @@ -2694,64 +2538,6 @@ mod tests { } ); - // Test Pay Subcommand w/ signer - let key1 = Pubkey::new_rand(); - let sig1 = Keypair::new().sign_message(&[0u8]); - let signer1 = format!("{}={}", key1, sig1); - let test_pay = test_commands.clone().get_matches_from(vec![ - "test", - "pay", - &pubkey_string, - "50", - "--blockhash", - &blockhash_string, - "--signer", - &signer1, - ]); - assert_eq!( - parse_command(&test_pay).unwrap(), - CliCommandInfo { - command: CliCommand::Pay(PayCommand { - lamports: 50_000_000_000, - to: pubkey, - blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - signers: Some(vec![(key1, sig1)]), - ..PayCommand::default() - }), - require_keypair: true - } - ); - - // Test Pay Subcommand w/ signers - let key2 = Pubkey::new_rand(); - let sig2 = Keypair::new().sign_message(&[1u8]); - let signer2 = format!("{}={}", key2, sig2); - let test_pay = test_commands.clone().get_matches_from(vec![ - "test", - "pay", - &pubkey_string, - "50", - "--blockhash", - &blockhash_string, - "--signer", - &signer1, - "--signer", - &signer2, - ]); - assert_eq!( - parse_command(&test_pay).unwrap(), - CliCommandInfo { - command: CliCommand::Pay(PayCommand { - lamports: 50_000_000_000, - to: pubkey, - blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - signers: Some(vec![(key1, sig1), (key2, sig2)]), - ..PayCommand::default() - }), - require_keypair: true - } - ); - // Test Pay Subcommand w/ Blockhash let test_pay = test_commands.clone().get_matches_from(vec![ "test", @@ -2860,8 +2646,7 @@ mod tests { to: pubkey, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(pubkey), - nonce_authority: Some(authority_pubkey.into()), - signers: Some(vec![(authority_pubkey, sig)]), + nonce_authority: Some(Presigner::new(&authority_pubkey, &sig).into()), ..PayCommand::default() }), require_keypair: true @@ -2969,7 +2754,7 @@ mod tests { let keypair = Keypair::new(); let pubkey = keypair.pubkey().to_string(); - config.keypair = keypair; + config.keypair = keypair.into(); config.command = CliCommand::Address; assert_eq!(process_command(&config).unwrap(), pubkey); @@ -3029,7 +2814,7 @@ mod tests { let bob_pubkey = bob_keypair.pubkey(); let custodian = Pubkey::new_rand(); config.command = CliCommand::CreateStakeAccount { - stake_account: bob_keypair.into(), + stake_account: Rc::new(bob_keypair.into()), seed: None, staker: None, withdrawer: None, @@ -3040,7 +2825,6 @@ mod tests { }, lamports: 1234, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -3058,7 +2842,6 @@ mod tests { lamports: 100, withdraw_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -3072,7 +2855,6 @@ mod tests { stake_account_pubkey: stake_pubkey, stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -3087,11 +2869,10 @@ mod tests { stake_account_pubkey: stake_pubkey, stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, - split_stake_account: split_stake_account.into(), + split_stake_account: Rc::new(split_stake_account.into()), seed: None, lamports: 1234, fee_payer: None, @@ -3394,7 +3175,6 @@ mod tests { to: to_pubkey, from: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -3424,7 +3204,6 @@ mod tests { to: to_pubkey, from: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -3457,13 +3236,12 @@ mod tests { command: CliCommand::Transfer { lamports: 42_000_000_000, to: to_pubkey, - from: Some(from_pubkey.into()), + from: Some(Presigner::new(&from_pubkey, &from_sig).into()), sign_only: false, - signers: Some(vec![(from_pubkey, from_sig)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: Some(from_pubkey.into()), + fee_payer: Some(Presigner::new(&from_pubkey, &from_sig).into()), }, require_keypair: true, } @@ -3495,7 +3273,6 @@ mod tests { to: to_pubkey, from: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_address.into()), nonce_authority: Some(read_keypair_file(&nonce_authority_file).unwrap().into()), diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 71fc4cfe16..0f536b2d24 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -20,9 +20,11 @@ use solana_sdk::{ commitment_config::CommitmentConfig, epoch_schedule::Epoch, hash::Hash, + message::Message, pubkey::Pubkey, signature::{Keypair, Signer}, - system_transaction, + system_instruction, + transaction::Transaction, }; use std::{ collections::{HashMap, VecDeque}, @@ -743,8 +745,10 @@ pub fn process_ping( let (recent_blockhash, fee_calculator) = rpc_client.get_new_blockhash(&last_blockhash)?; last_blockhash = recent_blockhash; - let transaction = - system_transaction::transfer(&config.keypair, &to, lamports, recent_blockhash); + let ix = system_instruction::transfer(&config.keypair.pubkey(), &to, lamports); + let message = Message::new(vec![ix]); + let mut transaction = Transaction::new_unsigned(message); + transaction.try_sign(&[config.keypair.as_ref()], recent_blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), diff --git a/cli/src/main.rs b/cli/src/main.rs index fd5e99e309..8047cf8b91 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,17 +4,13 @@ use console::style; use solana_clap_utils::{ input_parsers::derivation_of, input_validators::{is_derivation, is_url}, - keypair::{ - self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG, - SKIP_SEED_PHRASE_VALIDATION_ARG, - }, + keypair::{keypair_util_from_path, SKIP_SEED_PHRASE_VALIDATION_ARG}, }; use solana_cli::{ cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliError}, display::{println_name_value, println_name_value_or}, }; use solana_cli_config::config::{Config, CONFIG_FILE}; -use solana_sdk::signature::read_keypair_file; use std::error; @@ -105,42 +101,24 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result ( - keypair, - Some(matches.value_of("keypair").unwrap().to_string()), - ), - keypair::Source::SeedPhrase => (keypair, None), - keypair::Source::Generated => { - let keypair_path = if config.keypair_path != "" { - config.keypair_path - } else { - let default_keypair_path = CliConfig::default_keypair_path(); - if !std::path::Path::new(&default_keypair_path).exists() { - return Err(CliError::KeypairFileNotFound(format!( - "Generate a new keypair at {} with `solana-keygen new`", - default_keypair_path - )) - .into()); - } + let path = if matches.is_present("keypair") { + matches.value_of("keypair").unwrap().to_string() + } else if config.keypair_path != "" { + config.keypair_path + } else { + let default_keypair_path = CliConfig::default_keypair_path(); + if !std::path::Path::new(&default_keypair_path).exists() { + return Err(CliError::KeypairFileNotFound(format!( + "Generate a new keypair at {} with `solana-keygen new`", default_keypair_path - }; - - let keypair = if keypair_path.starts_with("usb://") { - keypair - } else { - read_keypair_file(&keypair_path).or_else(|err| { - Err(CliError::BadParameter(format!( - "{}: Unable to open keypair file: {}", - err, keypair_path - ))) - })? - }; - - (keypair, Some(keypair_path)) + )) + .into()); } - } + default_keypair_path + }; + + let keypair = keypair_util_from_path(matches, &path, "keypair")?; + (keypair, Some(path)) } else { let default = CliConfig::default(); (default.keypair, None) @@ -201,6 +179,7 @@ fn main() -> Result<(), Box> { Arg::with_name("derivation_path") .long("derivation-path") .value_name("ACCOUNT or ACCOUNT/CHANGE") + .global(true) .takes_value(true) .validator(is_derivation) .help("Derivation path to use: m/44'/501'/ACCOUNT'/CHANGE'; default key is device base pubkey: m/44'/501'/0'") @@ -212,15 +191,6 @@ fn main() -> Result<(), Box> { .global(true) .help("Show extra information header"), ) - .arg( - Arg::with_name(ASK_SEED_PHRASE_ARG.name) - .long(ASK_SEED_PHRASE_ARG.long) - .value_name("KEYPAIR NAME") - .global(true) - .takes_value(true) - .possible_values(&["keypair"]) - .help(ASK_SEED_PHRASE_ARG.help), - ) .arg( Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name) .long(SKIP_SEED_PHRASE_VALIDATION_ARG.long) diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index 2b95cb3047..bce7794b04 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -1,19 +1,20 @@ use crate::cli::{ build_balance_message, check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, - SigningAuthority, }; -use crate::offline::BLOCKHASH_ARG; use clap::{App, Arg, ArgMatches, SubCommand}; -use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant}; +use solana_clap_utils::{ + input_parsers::*, input_validators::*, offline::BLOCKHASH_ARG, ArgConstant, +}; use solana_client::rpc_client::RpcClient; use solana_sdk::{ account::Account, account_utils::StateMut, hash::Hash, + message::Message, nonce_state::{Meta, NonceState}, pubkey::Pubkey, - signature::{Keypair, Signer}, + signature::Signer, system_instruction::{ advance_nonce_account, authorize_nonce_account, create_address_with_seed, create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError, @@ -65,8 +66,8 @@ pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(NONCE_AUTHORITY_ARG.name) .long(NONCE_AUTHORITY_ARG.long) .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help(NONCE_AUTHORITY_ARG.help) } @@ -218,8 +219,7 @@ impl NonceSubCommands for App<'_, '_> { pub fn parse_authorize_nonce_account(matches: &ArgMatches<'_>) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let new_authority = pubkey_of(matches, "new_authority").unwrap(); - let nonce_authority = - SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?; + let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; Ok(CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { @@ -232,7 +232,7 @@ pub fn parse_authorize_nonce_account(matches: &ArgMatches<'_>) -> Result) -> Result { - let nonce_account = keypair_of(matches, "nonce_account_keypair").unwrap(); + let nonce_account = signer_of("nonce_account_keypair", matches)?.unwrap(); let seed = matches.value_of("seed").map(|s| s.to_string()); let lamports = lamports_of_sol(matches, "amount").unwrap(); let nonce_authority = pubkey_of(matches, NONCE_AUTHORITY_ARG.name); @@ -259,8 +259,7 @@ pub fn parse_get_nonce(matches: &ArgMatches<'_>) -> Result) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); - let nonce_authority = - SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?; + let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; Ok(CliCommandInfo { command: CliCommand::NewNonce { @@ -290,8 +289,7 @@ pub fn parse_withdraw_from_nonce_account( let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap(); let lamports = lamports_of_sol(matches, "amount").unwrap(); - let nonce_authority = - SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?; + let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; Ok(CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { @@ -336,36 +334,35 @@ pub fn process_authorize_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, - nonce_authority: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, new_authority: &Pubkey, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let nonce_authority = nonce_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority); - let mut tx = Transaction::new_signed_with_payer( - vec![ix], - Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority], + let message = Message::new_with_payer(vec![ix], Some(&config.keypair.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[config.keypair.as_ref(), nonce_authority], recent_blockhash, - ); + )?; + check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]); + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), nonce_authority]); log_instruction_custom_error::(result) } pub fn process_create_nonce_account( rpc_client: &RpcClient, config: &CliConfig, - nonce_account: &Keypair, + nonce_account: &dyn Signer, seed: Option, nonce_authority: Option, lamports: u64, @@ -428,17 +425,15 @@ pub fn process_create_nonce_account( let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let signers = if nonce_account_pubkey != config.keypair.pubkey() { - vec![&config.keypair, nonce_account] // both must sign if `from` and `to` differ + vec![config.keypair.as_ref(), nonce_account] // both must sign if `from` and `to` differ } else { - vec![&config.keypair] // when stake_account == config.keypair and there's a seed, we only need one signature + vec![config.keypair.as_ref()] // when stake_account == config.keypair and there's a seed, we only need one signature }; - let mut tx = Transaction::new_signed_with_payer( - ixs, - Some(&config.keypair.pubkey()), - &signers, - recent_blockhash, - ); + let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, recent_blockhash)?; + check_account_for_fee( rpc_client, &config.keypair.pubkey(), @@ -473,7 +468,7 @@ pub fn process_new_nonce( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, - nonce_authority: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, ) -> ProcessResult { check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), @@ -487,25 +482,23 @@ pub fn process_new_nonce( .into()); } - let nonce_authority = nonce_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey()); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let mut tx = Transaction::new_signed_with_payer( - vec![ix], - Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority], + let message = Message::new_with_payer(vec![ix], Some(&config.keypair.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[config.keypair.as_ref(), nonce_authority], recent_blockhash, - ); + )?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]); + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), nonce_authority]); log_instruction_custom_error::(result) } @@ -562,35 +555,33 @@ pub fn process_withdraw_from_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, - nonce_authority: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, destination_account_pubkey: &Pubkey, lamports: u64, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let nonce_authority = nonce_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); let ix = withdraw_nonce_account( nonce_account, &nonce_authority.pubkey(), destination_account_pubkey, lamports, ); - let mut tx = Transaction::new_signed_with_payer( - vec![ix], - Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority], + let message = Message::new_with_payer(vec![ix], Some(&config.keypair.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[config.keypair.as_ref(), nonce_authority], recent_blockhash, - ); + )?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]); + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), nonce_authority]); log_instruction_custom_error::(result) } @@ -602,9 +593,10 @@ mod tests { account::Account, hash::hash, nonce_state::{Meta as NonceMeta, NonceState}, - signature::{read_keypair_file, write_keypair}, + signature::{read_keypair_file, write_keypair, Keypair}, system_program, }; + use std::rc::Rc; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -678,7 +670,7 @@ mod tests { parse_command(&test_create_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), seed: None, nonce_authority: None, lamports: 50_000_000_000, @@ -700,7 +692,7 @@ mod tests { parse_command(&test_create_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), seed: None, nonce_authority: Some( read_keypair_file(&authority_keypair_file).unwrap().pubkey() diff --git a/cli/src/offline.rs b/cli/src/offline.rs index ade5d8b497..5aee7142bb 100644 --- a/cli/src/offline.rs +++ b/cli/src/offline.rs @@ -3,30 +3,12 @@ use serde_json::Value; use solana_clap_utils::{ input_parsers::value_of, input_validators::{is_hash, is_pubkey_sig}, - ArgConstant, + offline::{BLOCKHASH_ARG, SIGNER_ARG, SIGN_ONLY_ARG}, }; use solana_client::rpc_client::RpcClient; use solana_sdk::{fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey, signature::Signature}; use std::str::FromStr; -pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant { - name: "blockhash", - long: "blockhash", - help: "Use the supplied blockhash", -}; - -pub const SIGN_ONLY_ARG: ArgConstant<'static> = ArgConstant { - name: "sign_only", - long: "sign-only", - help: "Sign the transaction offline", -}; - -pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant { - name: "signer", - long: "signer", - help: "Provide a public-key/signature pair for the transaction", -}; - #[derive(Clone, Debug, PartialEq)] pub enum BlockhashQuery { None(Hash, FeeCalculator), diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 6efd74ec5f..bced644740 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1,20 +1,19 @@ use crate::{ cli::{ build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg, - log_instruction_custom_error, nonce_authority_arg, replace_signatures, return_signers, - CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SigningAuthority, - FEE_PAYER_ARG, + log_instruction_custom_error, nonce_authority_arg, return_signers, CliCommand, + CliCommandInfo, CliConfig, CliError, ProcessResult, FEE_PAYER_ARG, }, nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG}, offline::*, }; use clap::{App, Arg, ArgMatches, SubCommand}; use console::style; -use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant}; +use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant}; use solana_client::rpc_client::RpcClient; -use solana_sdk::signature::{Keypair, Signature}; use solana_sdk::{ account_utils::StateMut, + message::Message, pubkey::Pubkey, signature::Signer, system_instruction::{create_address_with_seed, SystemError}, @@ -47,8 +46,8 @@ fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(STAKE_AUTHORITY_ARG.name) .long(STAKE_AUTHORITY_ARG.long) .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help(STAKE_AUTHORITY_ARG.help) } @@ -56,8 +55,8 @@ fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(WITHDRAW_AUTHORITY_ARG.name) .long(WITHDRAW_AUTHORITY_ARG.long) .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help(WITHDRAW_AUTHORITY_ARG.help) } @@ -76,7 +75,7 @@ impl StakeSubCommands for App<'_, '_> { .value_name("STAKE ACCOUNT") .takes_value(true) .required(true) - .validator(is_pubkey_or_keypair_or_ask_keyword) + .validator(is_valid_signer) .help("Signing authority of the stake address to fund") ) .arg( @@ -138,8 +137,8 @@ impl StakeSubCommands for App<'_, '_> { Arg::with_name("from") .long("from") .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help("Source account of funds (if different from client local account)"), ) .offline_args() @@ -369,8 +368,8 @@ impl StakeSubCommands for App<'_, '_> { Arg::with_name("custodian") .long("custodian") .takes_value(true) - .value_name("KEYPAIR or PUBKEY") - .validator(is_pubkey_or_keypair_or_ask_keyword) + .value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH") + .validator(is_valid_signer) .help("Public key of signing custodian (defaults to cli config pubkey)") ) .offline_args() @@ -425,19 +424,14 @@ pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result) -> Result WITHDRAW_AUTHORITY_ARG.name, }; let sign_only = matches.is_present(SIGN_ONLY_ARG.name); - let signers = pubkeys_sigs_of(&matches, SIGNER_ARG.name); - let authority = - SigningAuthority::new_from_matches(&matches, authority_flag, signers.as_deref())?; + let authority = signer_of(authority_flag, matches)?; let blockhash_query = BlockhashQuery::new_from_matches(matches); let nonce_account = pubkey_of(&matches, NONCE_ARG.name); - let nonce_authority = - SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, signers.as_deref())?; - let fee_payer = - SigningAuthority::new_from_matches(&matches, FEE_PAYER_ARG.name, signers.as_deref())?; + let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; + let fee_payer = signer_of(FEE_PAYER_ARG.name, matches)?; Ok(CliCommandInfo { command: CliCommand::StakeAuthorize { @@ -520,7 +505,6 @@ pub fn parse_stake_authorize( stake_authorize, authority, sign_only, - signers, blockhash_query, nonce_account, nonce_authority, @@ -532,7 +516,7 @@ pub fn parse_stake_authorize( pub fn parse_split_stake(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); - let split_stake_account = keypair_of(matches, "split_stake_account").unwrap(); + let split_stake_account = signer_of("split_stake_account", matches)?.unwrap(); let lamports = lamports_of_sol(matches, "amount").unwrap(); let seed = matches.value_of("seed").map(|s| s.to_string()); @@ -541,19 +525,15 @@ pub fn parse_split_stake(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result) -> Result) -> Result) -> Result) -> Result, staker: &Option, withdrawer: &Option, lockup: &Lockup, lamports: u64, sign_only: bool, - signers: Option<&Vec<(Pubkey, Signature)>>, blockhash_query: &BlockhashQuery, nonce_account: Option<&Pubkey>, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, - from: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, + fee_payer: Option<&dyn Signer>, + from: Option<&dyn Signer>, ) -> ProcessResult { // Offline derived address creation currently is not possible // https://github.com/solana-labs/solana/pull/8252 - if seed.is_some() && (sign_only || signers.is_some()) { + if seed.is_some() && sign_only { return Err(CliError::BadParameter( "Offline stake account creation with derived addresses are not yet supported" .to_string(), @@ -717,17 +682,14 @@ pub fn process_create_stake_account( .into()); } - let (stake_account_pubkey, stake_account) = (stake_account.pubkey(), stake_account.keypair()); let stake_account_address = if let Some(seed) = seed { - create_address_with_seed(&stake_account_pubkey, &seed, &solana_stake_program::id())? + create_address_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())? } else { - stake_account_pubkey + stake_account.pubkey() }; - let (from_pubkey, from) = from - .map(|f| (f.pubkey(), f.keypair())) - .unwrap_or((config.keypair.pubkey(), &config.keypair)); + let from = from.unwrap_or_else(|| config.keypair.as_ref()); check_unique_pubkeys( - (&from_pubkey, "from keypair".to_string()), + (&from.pubkey(), "from keypair".to_string()), (&stake_account_address, "stake_account".to_string()), )?; @@ -757,8 +719,8 @@ pub fn process_create_stake_account( } let authorized = Authorized { - staker: staker.unwrap_or(from_pubkey), - withdrawer: withdrawer.unwrap_or(from_pubkey), + staker: staker.unwrap_or(from.pubkey()), + withdrawer: withdrawer.unwrap_or(from.pubkey()), }; let ixs = if let Some(seed) = seed { @@ -783,43 +745,37 @@ pub fn process_create_stake_account( let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let fee_payer = fee_payer.map(|fp| fp.keypair()).unwrap_or(&config.keypair); - let mut tx_signers = if stake_account_pubkey != from_pubkey { + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); + let mut tx_signers = if stake_account.pubkey() != from.pubkey() { vec![fee_payer, from, stake_account] // both must sign if `from` and `to` differ } else { vec![fee_payer, from] // when stake_account == config.keypair and there's a seed, we only need one signature }; - let (nonce_authority_pubkey, nonce_authority) = nonce_authority - .map(|na| (na.pubkey(), na.keypair())) - .unwrap_or((config.keypair.pubkey(), &config.keypair)); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { tx_signers.push(nonce_authority); - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &tx_signers, nonce_account, &nonce_authority.pubkey(), - recent_blockhash, - ) + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&tx_signers, recent_blockhash)?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &tx_signers, - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&tx_signers, recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -839,19 +795,18 @@ pub fn process_stake_authorize( stake_account_pubkey: &Pubkey, authorized_pubkey: &Pubkey, stake_authorize: StakeAuthorize, - authority: Option<&SigningAuthority>, + authority: Option<&dyn Signer>, sign_only: bool, - signers: &Option>, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, + fee_payer: Option<&dyn Signer>, ) -> ProcessResult { check_unique_pubkeys( (stake_account_pubkey, "stake_account_pubkey".to_string()), (authorized_pubkey, "new_authorized_pubkey".to_string()), )?; - let authority = authority.map(|a| a.keypair()).unwrap_or(&config.keypair); + let authority = authority.unwrap_or_else(|| config.keypair.as_ref()); let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; let ixs = vec![stake_instruction::authorize( @@ -861,36 +816,30 @@ pub fn process_stake_authorize( stake_authorize, // stake or withdraw )]; - let (nonce_authority, nonce_authority_pubkey) = nonce_authority - .map(|a| (a.keypair(), a.pubkey())) - .unwrap_or((&config.keypair, config.keypair.pubkey())); - let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &[fee_payer, nonce_authority, authority], nonce_account, &nonce_authority.pubkey(), - recent_blockhash, - ) + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, nonce_authority, authority], recent_blockhash)?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &[fee_payer, authority], - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, authority], recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -898,7 +847,7 @@ pub fn process_stake_authorize( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -908,53 +857,47 @@ pub fn process_deactivate_stake_account( rpc_client: &RpcClient, config: &CliConfig, stake_account_pubkey: &Pubkey, - stake_authority: Option<&SigningAuthority>, + stake_authority: Option<&dyn Signer>, sign_only: bool, - signers: &Option>, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, + fee_payer: Option<&dyn Signer>, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let stake_authority = stake_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let stake_authority = stake_authority.unwrap_or_else(|| config.keypair.as_ref()); let ixs = vec![stake_instruction::deactivate_stake( stake_account_pubkey, &stake_authority.pubkey(), )]; - let (nonce_authority, nonce_authority_pubkey) = nonce_authority - .map(|a| (a.keypair(), a.pubkey())) - .unwrap_or((&config.keypair, config.keypair.pubkey())); - let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &[fee_payer, nonce_authority, stake_authority], nonce_account, &nonce_authority.pubkey(), + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[fee_payer, nonce_authority, stake_authority], recent_blockhash, - ) + )?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &[fee_payer, stake_authority], - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, stake_authority], recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -962,7 +905,7 @@ pub fn process_deactivate_stake_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -974,19 +917,16 @@ pub fn process_withdraw_stake( stake_account_pubkey: &Pubkey, destination_account_pubkey: &Pubkey, lamports: u64, - withdraw_authority: Option<&SigningAuthority>, + withdraw_authority: Option<&dyn Signer>, sign_only: bool, - signers: Option<&Vec<(Pubkey, Signature)>>, blockhash_query: &BlockhashQuery, nonce_account: Option<&Pubkey>, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, + fee_payer: Option<&dyn Signer>, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let withdraw_authority = withdraw_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let withdraw_authority = withdraw_authority.unwrap_or_else(|| config.keypair.as_ref()); let ixs = vec![stake_instruction::withdraw( stake_account_pubkey, @@ -995,36 +935,33 @@ pub fn process_withdraw_stake( lamports, )]; - let fee_payer = fee_payer.map(|fp| fp.keypair()).unwrap_or(&config.keypair); - let (nonce_authority_pubkey, nonce_authority) = nonce_authority - .map(|na| (na.pubkey(), na.keypair())) - .unwrap_or((config.keypair.pubkey(), &config.keypair)); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &[fee_payer, withdraw_authority, nonce_authority], nonce_account, &nonce_authority.pubkey(), + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[fee_payer, withdraw_authority, nonce_authority], recent_blockhash, - ) + )?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &[fee_payer, withdraw_authority], - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, withdraw_authority], recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -1032,7 +969,7 @@ pub fn process_withdraw_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -1042,29 +979,26 @@ pub fn process_split_stake( rpc_client: &RpcClient, config: &CliConfig, stake_account_pubkey: &Pubkey, - stake_authority: Option<&SigningAuthority>, + stake_authority: Option<&dyn Signer>, sign_only: bool, - signers: &Option>, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&SigningAuthority>, - split_stake_account: &Keypair, + nonce_authority: Option<&dyn Signer>, + split_stake_account: &dyn Signer, split_stake_account_seed: &Option, lamports: u64, - fee_payer: Option<&SigningAuthority>, + fee_payer: Option<&dyn Signer>, ) -> ProcessResult { - let (fee_payer_pubkey, fee_payer) = fee_payer - .map(|f| (f.pubkey(), f.keypair())) - .unwrap_or((config.keypair.pubkey(), &config.keypair)); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); check_unique_pubkeys( - (&fee_payer_pubkey, "fee-payer keypair".to_string()), + (&fee_payer.pubkey(), "fee-payer keypair".to_string()), ( &split_stake_account.pubkey(), "split_stake_account".to_string(), ), )?; check_unique_pubkeys( - (&fee_payer_pubkey, "fee-payer keypair".to_string()), + (&fee_payer.pubkey(), "fee-payer keypair".to_string()), (&stake_account_pubkey, "stake_account".to_string()), )?; check_unique_pubkeys( @@ -1075,9 +1009,7 @@ pub fn process_split_stake( ), )?; - let stake_authority = stake_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let stake_authority = stake_authority.unwrap_or_else(|| config.keypair.as_ref()); let split_stake_account_address = if let Some(seed) = split_stake_account_seed { create_address_with_seed( @@ -1138,41 +1070,41 @@ pub fn process_split_stake( ) }; - let (nonce_authority, nonce_authority_pubkey) = nonce_authority - .map(|a| (a.keypair(), a.pubkey())) - .unwrap_or((&config.keypair, config.keypair.pubkey())); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), + nonce_account, + &nonce_authority.pubkey(), + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( &[ fee_payer, nonce_authority, stake_authority, split_stake_account, ], - nonce_account, - &nonce_authority.pubkey(), recent_blockhash, - ) + )?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( &[fee_payer, stake_authority, split_stake_account], recent_blockhash, - ) + )?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -1180,7 +1112,7 @@ pub fn process_split_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -1191,17 +1123,16 @@ pub fn process_stake_set_lockup( config: &CliConfig, stake_account_pubkey: &Pubkey, lockup: &mut Lockup, - custodian: Option<&SigningAuthority>, + custodian: Option<&dyn Signer>, sign_only: bool, - signers: &Option>, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, + fee_payer: Option<&dyn Signer>, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let custodian = custodian.map(|a| a.keypair()).unwrap_or(&config.keypair); + let custodian = custodian.unwrap_or_else(|| config.keypair.as_ref()); // If new custodian is not explicitly set, default to current custodian if lockup.custodian == Pubkey::default() { lockup.custodian = custodian.pubkey(); @@ -1211,36 +1142,30 @@ pub fn process_stake_set_lockup( lockup, &custodian.pubkey(), )]; - let (nonce_authority, nonce_authority_pubkey) = nonce_authority - .map(|a| (a.keypair(), a.pubkey())) - .unwrap_or((&config.keypair, config.keypair.pubkey())); - let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &[fee_payer, nonce_authority, custodian], nonce_account, &nonce_authority.pubkey(), - recent_blockhash, - ) + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, nonce_authority, custodian], recent_blockhash)?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &[fee_payer, custodian], - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, custodian], recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -1248,7 +1173,7 @@ pub fn process_stake_set_lockup( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -1324,7 +1249,7 @@ pub fn process_show_stake_account( if stake_account.owner != solana_stake_program::id() { return Err(CliError::RpcRequestError(format!( "{:?} is not a stake account", - stake_account_pubkey + stake_account_pubkey, )) .into()); } @@ -1380,22 +1305,19 @@ pub fn process_delegate_stake( config: &CliConfig, stake_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey, - stake_authority: Option<&SigningAuthority>, + stake_authority: Option<&dyn Signer>, force: bool, sign_only: bool, - signers: &Option>, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&SigningAuthority>, - fee_payer: Option<&SigningAuthority>, + nonce_authority: Option<&dyn Signer>, + fee_payer: Option<&dyn Signer>, ) -> ProcessResult { check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), (stake_account_pubkey, "stake_account_pubkey".to_string()), )?; - let stake_authority = stake_authority - .map(|a| a.keypair()) - .unwrap_or(&config.keypair); + let stake_authority = stake_authority.unwrap_or_else(|| config.keypair.as_ref()); if !sign_only { // Sanity check the vote account to ensure it is attached to a validator that has recently @@ -1450,36 +1372,33 @@ pub fn process_delegate_stake( &stake_authority.pubkey(), vote_account_pubkey, )]; - let (nonce_authority, nonce_authority_pubkey) = nonce_authority - .map(|a| (a.keypair(), a.pubkey())) - .unwrap_or((&config.keypair, config.keypair.pubkey())); - let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); let mut tx = if let Some(nonce_account) = &nonce_account { - Transaction::new_signed_with_nonce( + let message = Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), - &[fee_payer, nonce_authority, stake_authority], nonce_account, &nonce_authority.pubkey(), + ); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[fee_payer, nonce_authority, stake_authority], recent_blockhash, - ) + )?; + tx } else { - Transaction::new_signed_with_payer( - ixs, - Some(&fee_payer.pubkey()), - &[fee_payer, stake_authority], - recent_blockhash, - ) + let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[fee_payer, stake_authority], recent_blockhash)?; + tx }; - if let Some(signers) = signers { - replace_signatures(&mut tx, &signers)?; - } if sign_only { return_signers(&tx) } else { if let Some(nonce_account) = &nonce_account { let nonce_account = rpc_client.get_account(nonce_account)?; - check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; } check_account_for_fee( rpc_client, @@ -1487,7 +1406,7 @@ pub fn process_delegate_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } } @@ -1499,8 +1418,11 @@ mod tests { use solana_sdk::{ fee_calculator::FeeCalculator, hash::Hash, - signature::{keypair_from_seed, read_keypair_file, write_keypair, Signer}, + signature::{ + keypair_from_seed, read_keypair_file, write_keypair, Keypair, Presigner, Signer, + }, }; + use std::rc::Rc; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -1537,7 +1459,6 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -1564,7 +1485,6 @@ mod tests { stake_authorize, authority: Some(read_keypair_file(&authority_keypair_file).unwrap().into()), sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -1594,7 +1514,6 @@ mod tests { stake_authorize, authority: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -1603,8 +1522,9 @@ mod tests { require_keypair: true } ); - // Test Authorize Subcommand w/ signer + // Test Authorize Subcommand w/ offline feepayer let keypair = Keypair::new(); + let pubkey = keypair.pubkey(); let sig = keypair.sign_message(&[0u8]); let signer = format!("{}={}", keypair.pubkey(), sig); let test_authorize = test_commands.clone().get_matches_from(vec![ @@ -1616,6 +1536,8 @@ mod tests { &blockhash_string, "--signer", &signer, + "--fee-payer", + &pubkey.to_string(), ]); assert_eq!( parse_command(&test_authorize).unwrap(), @@ -1626,19 +1548,20 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: Some(vec![(keypair.pubkey(), sig)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: None, + fee_payer: Some(Presigner::new(&pubkey, &sig).into()), }, require_keypair: true } ); - // Test Authorize Subcommand w/ signers + // Test Authorize Subcommand w/ offline fee payer and nonce authority let keypair2 = Keypair::new(); + let pubkey2 = keypair2.pubkey(); let sig2 = keypair.sign_message(&[0u8]); let signer2 = format!("{}={}", keypair2.pubkey(), sig2); + let nonce_account = Pubkey::new(&[1u8; 32]); let test_authorize = test_commands.clone().get_matches_from(vec![ "test", &subcommand, @@ -1650,6 +1573,12 @@ mod tests { &signer, "--signer", &signer2, + "--fee-payer", + &pubkey.to_string(), + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2.to_string(), ]); assert_eq!( parse_command(&test_authorize).unwrap(), @@ -1660,11 +1589,10 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: Some(vec![(keypair.pubkey(), sig), (keypair2.pubkey(), sig2),]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_account: Some(nonce_account), + nonce_authority: Some(Presigner::new(&pubkey2, &sig2).into()), + fee_payer: Some(Presigner::new(&pubkey, &sig).into()), }, require_keypair: true } @@ -1687,7 +1615,6 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, @@ -1723,7 +1650,6 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account_pubkey), nonce_authority: Some(nonce_authority_keypair.into()), @@ -1755,7 +1681,6 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -1788,11 +1713,10 @@ mod tests { stake_authorize, authority: None, sign_only: false, - signers: Some(vec![(fee_payer_pubkey, sig)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: Some(fee_payer_pubkey.into()), + fee_payer: Some(Presigner::new(&fee_payer_pubkey, &sig).into()), }, require_keypair: true } @@ -1846,7 +1770,7 @@ mod tests { parse_command(&test_create_stake_account).unwrap(), CliCommandInfo { command: CliCommand::CreateStakeAccount { - stake_account: stake_account_keypair.into(), + stake_account: Rc::new(stake_account_keypair.into()), seed: None, staker: Some(authorized), withdrawer: Some(authorized), @@ -1857,7 +1781,6 @@ mod tests { }, lamports: 50_000_000_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -1885,14 +1808,13 @@ mod tests { parse_command(&test_create_stake_account2).unwrap(), CliCommandInfo { command: CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000_000_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -1936,19 +1858,18 @@ mod tests { parse_command(&test_create_stake_account2).unwrap(), CliCommandInfo { command: CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000_000_000, sign_only: false, - signers: Some(vec![(offline_pubkey, offline_sig)]), blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account), - nonce_authority: Some(offline_pubkey.into()), - fee_payer: Some(offline_pubkey.into()), - from: Some(offline_pubkey.into()), + nonce_authority: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), + fee_payer: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), + from: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), }, require_keypair: false, } @@ -1972,7 +1893,6 @@ mod tests { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -2006,7 +1926,6 @@ mod tests { ), force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -2033,7 +1952,6 @@ mod tests { stake_authority: None, force: true, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -2063,7 +1981,6 @@ mod tests { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, @@ -2091,7 +2008,6 @@ mod tests { stake_authority: None, force: false, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -2101,7 +2017,7 @@ mod tests { } ); - // Test Delegate Subcommand w/ signer + // Test Delegate Subcommand w/ absent fee payer let key1 = Pubkey::new_rand(); let sig1 = Keypair::new().sign_message(&[0u8]); let signer1 = format!("{}={}", key1, sig1); @@ -2114,6 +2030,8 @@ mod tests { &blockhash_string, "--signer", &signer1, + "--fee-payer", + &key1.to_string(), ]); assert_eq!( parse_command(&test_delegate_stake).unwrap(), @@ -2124,17 +2042,16 @@ mod tests { stake_authority: None, force: false, sign_only: false, - signers: Some(vec![(key1, sig1)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: None, + fee_payer: Some(Presigner::new(&key1, &sig1).into()), }, require_keypair: false } ); - // Test Delegate Subcommand w/ signers + // Test Delegate Subcommand w/ absent fee payer and absent nonce authority let key2 = Pubkey::new_rand(); let sig2 = Keypair::new().sign_message(&[0u8]); let signer2 = format!("{}={}", key2, sig2); @@ -2149,6 +2066,12 @@ mod tests { &signer1, "--signer", &signer2, + "--fee-payer", + &key1.to_string(), + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &key2.to_string(), ]); assert_eq!( parse_command(&test_delegate_stake).unwrap(), @@ -2159,22 +2082,19 @@ mod tests { stake_authority: None, force: false, sign_only: false, - signers: Some(vec![(key1, sig1), (key2, sig2)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_account: Some(nonce_account), + nonce_authority: Some(Presigner::new(&key2, &sig2).into()), + fee_payer: Some(Presigner::new(&key1, &sig1).into()), }, require_keypair: false } ); - // Test Delegate Subcommand w/ fee-payer + // Test Delegate Subcommand w/ present fee-payer let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file(); let fee_payer_keypair = Keypair::new(); write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap(); - let fee_payer_pubkey = fee_payer_keypair.pubkey(); - let fee_payer_string = fee_payer_pubkey.to_string(); let test_delegate_stake = test_commands.clone().get_matches_from(vec![ "test", "delegate-stake", @@ -2192,7 +2112,6 @@ mod tests { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -2202,40 +2121,6 @@ mod tests { } ); - // Test Delegate Subcommand w/ absentee fee-payer - let sig = fee_payer_keypair.sign_message(&[0u8]); - let signer = format!("{}={}", fee_payer_string, sig); - let test_delegate_stake = test_commands.clone().get_matches_from(vec![ - "test", - "delegate-stake", - &stake_account_string, - &vote_account_string, - "--fee-payer", - &fee_payer_string, - "--blockhash", - &blockhash_string, - "--signer", - &signer, - ]); - assert_eq!( - parse_command(&test_delegate_stake).unwrap(), - CliCommandInfo { - command: CliCommand::DelegateStake { - stake_account_pubkey, - vote_account_pubkey, - stake_authority: None, - force: false, - sign_only: false, - signers: Some(vec![(fee_payer_pubkey, sig)]), - blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - nonce_account: None, - nonce_authority: None, - fee_payer: Some(fee_payer_pubkey.into()), - }, - require_keypair: false - } - ); - // Test WithdrawStake Subcommand let test_withdraw_stake = test_commands.clone().get_matches_from(vec![ "test", @@ -2254,7 +2139,6 @@ mod tests { lamports: 42_000_000_000, withdraw_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -2288,7 +2172,6 @@ mod tests { .into() ), sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -2332,11 +2215,10 @@ mod tests { .into() ), sign_only: false, - signers: Some(vec![(offline_pubkey, offline_sig)]), blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account), - nonce_authority: Some(offline_pubkey.into()), - fee_payer: Some(offline_pubkey.into()), + nonce_authority: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), + fee_payer: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), }, require_keypair: false, } @@ -2355,7 +2237,6 @@ mod tests { stake_account_pubkey, stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -2384,7 +2265,6 @@ mod tests { .into() ), sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -2411,7 +2291,6 @@ mod tests { stake_account_pubkey, stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, @@ -2436,7 +2315,6 @@ mod tests { stake_account_pubkey, stake_authority: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -2446,7 +2324,7 @@ mod tests { } ); - // Test Deactivate Subcommand w/ signers + // Test Deactivate Subcommand w/ absent fee payer let key1 = Pubkey::new_rand(); let sig1 = Keypair::new().sign_message(&[0u8]); let signer1 = format!("{}={}", key1, sig1); @@ -2458,6 +2336,8 @@ mod tests { &blockhash_string, "--signer", &signer1, + "--fee-payer", + &key1.to_string(), ]); assert_eq!( parse_command(&test_deactivate_stake).unwrap(), @@ -2466,17 +2346,16 @@ mod tests { stake_account_pubkey, stake_authority: None, sign_only: false, - signers: Some(vec![(key1, sig1)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: None, + fee_payer: Some(Presigner::new(&key1, &sig1).into()), }, require_keypair: false } ); - // Test Deactivate Subcommand w/ signers + // Test Deactivate Subcommand w/ absent fee payer and nonce authority let key2 = Pubkey::new_rand(); let sig2 = Keypair::new().sign_message(&[0u8]); let signer2 = format!("{}={}", key2, sig2); @@ -2490,6 +2369,12 @@ mod tests { &signer1, "--signer", &signer2, + "--fee-payer", + &key1.to_string(), + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &key2.to_string(), ]); assert_eq!( parse_command(&test_deactivate_stake).unwrap(), @@ -2498,11 +2383,10 @@ mod tests { stake_account_pubkey, stake_authority: None, sign_only: false, - signers: Some(vec![(key1, sig1), (key2, sig2)]), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_account: Some(nonce_account), + nonce_authority: Some(Presigner::new(&key2, &sig2).into()), + fee_payer: Some(Presigner::new(&key1, &sig1).into()), }, require_keypair: false } @@ -2523,7 +2407,6 @@ mod tests { stake_account_pubkey, stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -2533,37 +2416,6 @@ mod tests { } ); - // Test Deactivate Subcommand w/ absentee fee-payer - let sig = fee_payer_keypair.sign_message(&[0u8]); - let signer = format!("{}={}", fee_payer_string, sig); - let test_deactivate_stake = test_commands.clone().get_matches_from(vec![ - "test", - "deactivate-stake", - &stake_account_string, - "--fee-payer", - &fee_payer_string, - "--blockhash", - &blockhash_string, - "--signer", - &signer, - ]); - assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), - CliCommandInfo { - command: CliCommand::DeactivateStake { - stake_account_pubkey, - stake_authority: None, - sign_only: false, - signers: Some(vec![(fee_payer_pubkey, sig)]), - blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - nonce_account: None, - nonce_authority: None, - fee_payer: Some(fee_payer_pubkey.into()), - }, - require_keypair: false - } - ); - // Test SplitStake SubCommand let (keypair_file, mut tmp_file) = make_tmp_file(); let stake_account_keypair = Keypair::new(); @@ -2586,13 +2438,14 @@ mod tests { stake_account_pubkey: stake_account_keypair.pubkey(), stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, - split_stake_account: read_keypair_file(&split_stake_account_keypair_file) - .unwrap() - .into(), + split_stake_account: Rc::new( + read_keypair_file(&split_stake_account_keypair_file) + .unwrap() + .into(), + ), seed: None, lamports: 50_000_000_000, fee_payer: None, @@ -2643,21 +2496,19 @@ mod tests { CliCommandInfo { command: CliCommand::SplitStake { stake_account_pubkey: stake_account_keypair.pubkey(), - stake_authority: Some(stake_auth_pubkey.into()), + stake_authority: Some(Presigner::new(&stake_auth_pubkey, &stake_sig).into()), sign_only: false, - signers: Some(vec![ - (nonce_auth_pubkey, nonce_sig), - (stake_auth_pubkey, stake_sig) - ]), blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.into()), - nonce_authority: Some(nonce_auth_pubkey.into()), - split_stake_account: read_keypair_file(&split_stake_account_keypair_file) - .unwrap() - .into(), + nonce_authority: Some(Presigner::new(&nonce_auth_pubkey, &nonce_sig).into()), + split_stake_account: Rc::new( + read_keypair_file(&split_stake_account_keypair_file) + .unwrap() + .into(), + ), seed: None, lamports: 50_000_000_000, - fee_payer: Some(nonce_auth_pubkey.into()), + fee_payer: Some(Presigner::new(&nonce_auth_pubkey, &nonce_sig).into()), }, require_keypair: false, } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index dba719dee1..3ee2edf8d8 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -191,19 +191,20 @@ pub fn process_create_storage_account( ); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let mut tx = Transaction::new_signed_instructions( - &[&config.keypair, &storage_account], - ixs, + let message = Message::new(ixs); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[config.keypair.as_ref(), storage_account], recent_blockhash, - ); + )?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &storage_account]); + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), storage_account]); log_instruction_custom_error::(result) } @@ -217,10 +218,10 @@ pub fn process_claim_storage_reward( let instruction = storage_instruction::claim_reward(node_account_pubkey, storage_account_pubkey); - let signers = [&config.keypair]; + let signers = [config.keypair.as_ref()]; let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); - - let mut tx = Transaction::new(&signers, message, recent_blockhash); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, recent_blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index 54777d6c18..3dde9cb1d2 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -301,7 +301,7 @@ pub fn process_set_validator_info( .unwrap_or(0); let keys = vec![(id(), false), (config.keypair.pubkey(), true)]; - let (message, signers): (Message, Vec<&Keypair>) = if balance == 0 { + let (message, signers): (Message, Vec<&dyn Signer>) = if balance == 0 { if info_pubkey != info_keypair.pubkey() { println!( "Account {:?} does not exist. Generating new keypair...", @@ -327,7 +327,7 @@ pub fn process_set_validator_info( keys, &validator_info, )]); - let signers = vec![&config.keypair, &info_keypair]; + let signers = vec![config.keypair.as_ref(), &info_keypair]; let message = Message::new(instructions); (message, signers) } else { @@ -343,13 +343,14 @@ pub fn process_set_validator_info( &validator_info, )]; let message = Message::new_with_payer(instructions, Some(&config.keypair.pubkey())); - let signers = vec![&config.keypair]; + let signers = vec![config.keypair.as_ref()]; (message, signers) }; // Submit transaction let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let mut tx = Transaction::new(&signers, message, recent_blockhash); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, recent_blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 167477117f..76b905e229 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -7,6 +7,7 @@ use solana_clap_utils::{input_parsers::*, input_validators::*}; use solana_client::rpc_client::RpcClient; use solana_sdk::{ account::Account, + message::Message, pubkey::Pubkey, signature::Keypair, signature::Signer, @@ -311,12 +312,14 @@ pub fn process_create_vote_account( let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let signers = if vote_account_pubkey != config.keypair.pubkey() { - vec![&config.keypair, vote_account] // both must sign if `from` and `to` differ + vec![config.keypair.as_ref(), vote_account] // both must sign if `from` and `to` differ } else { - vec![&config.keypair] // when stake_account == config.keypair and there's a seed, we only need one signature + vec![config.keypair.as_ref()] // when stake_account == config.keypair and there's a seed, we only need one signature }; - let mut tx = Transaction::new_signed_instructions(&signers, ixs, recent_blockhash); + let message = Message::new(ixs); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&signers, recent_blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), @@ -346,19 +349,16 @@ pub fn process_vote_authorize( vote_authorize, // vote or withdraw )]; - let mut tx = Transaction::new_signed_with_payer( - ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair], - recent_blockhash, - ); + let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[config.keypair.as_ref()], recent_blockhash)?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } @@ -380,19 +380,19 @@ pub fn process_vote_update_validator( new_identity_pubkey, )]; - let mut tx = Transaction::new_signed_with_payer( - ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, authorized_voter], + let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign( + &[config.keypair.as_ref(), authorized_voter], recent_blockhash, - ); + )?; check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); log_instruction_custom_error::(result) } diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index f5f89487f1..1df026dcdf 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -1,12 +1,10 @@ -use solana_cli::cli::{ - process_command, request_and_confirm_airdrop, CliCommand, CliConfig, SigningAuthority, -}; +use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ hash::Hash, pubkey::Pubkey, - signature::{read_keypair_file, write_keypair, Keypair, Signer}, + signature::{keypair_from_seed, read_keypair_file, write_keypair, Keypair, Signer}, system_instruction::create_address_with_seed, system_program, }; @@ -15,6 +13,7 @@ use std::sync::mpsc::channel; #[cfg(test)] use solana_core::validator::new_validator_for_tests; +use std::rc::Rc; use std::thread::sleep; use std::time::Duration; @@ -51,11 +50,13 @@ fn test_nonce() { config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); let mut config_nonce = CliConfig::default(); + config_nonce.keypair = keypair.into(); config_nonce.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let (keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_nonce.keypair, tmp_file.as_file_mut()).unwrap(); full_battery_tests( &rpc_client, @@ -84,11 +85,13 @@ fn test_nonce_with_seed() { config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); let mut config_nonce = CliConfig::default(); + config_nonce.keypair = keypair.into(); config_nonce.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let (keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_nonce.keypair, tmp_file.as_file_mut()).unwrap(); full_battery_tests( &rpc_client, @@ -117,11 +120,12 @@ fn test_nonce_with_authority() { config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let nonce_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&nonce_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_nonce = CliConfig::default(); config_nonce.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_nonce.keypair, tmp_file.as_file_mut()).unwrap(); let nonce_authority = Keypair::new(); let (authority_keypair_file, mut tmp_file2) = make_tmp_file(); @@ -141,7 +145,7 @@ fn test_nonce_with_authority() { remove_dir_all(ledger_path).unwrap(); } -fn read_keypair_from_option(keypair_file: &Option<&str>) -> Option { +fn read_keypair_from_option(keypair_file: &Option<&str>) -> Option> { keypair_file.map(|akf| read_keypair_file(&akf).unwrap().into()) } @@ -167,15 +171,14 @@ fn full_battery_tests( create_address_with_seed(&config_nonce.keypair.pubkey(), seed, &system_program::id()) .unwrap() } else { - config_nonce.keypair.pubkey() + read_keypair_file(&nonce_keypair_file).unwrap().pubkey() }; // Create nonce account config_payer.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed, - nonce_authority: read_keypair_from_option(&authority_keypair_file) - .map(|na: SigningAuthority| na.pubkey()), + nonce_authority: read_keypair_from_option(&authority_keypair_file).map(|k| k.pubkey()), lamports: 1000, }; diff --git a/cli/tests/pay.rs b/cli/tests/pay.rs index 9386c38c1c..dae91759a5 100644 --- a/cli/tests/pay.rs +++ b/cli/tests/pay.rs @@ -1,5 +1,6 @@ use chrono::prelude::*; use serde_json::Value; +use solana_clap_utils::keypair::presigner_from_pubkey_sigs; use solana_cli::{ cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand}, offline::{parse_sign_only_reply_string, BlockhashQuery}, @@ -18,6 +19,7 @@ use std::sync::mpsc::channel; #[cfg(test)] use solana_core::validator::new_validator_for_tests; +use std::rc::Rc; use std::thread::sleep; use std::time::Duration; use tempfile::NamedTempFile; @@ -304,17 +306,20 @@ fn test_offline_pay_tx() { check_balance(0, &rpc_client, &bob_pubkey); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = + presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap(); + let online_pubkey = config_online.keypair.pubkey(); + config_online.keypair = offline_presigner.into(); config_online.command = CliCommand::Pay(PayCommand { lamports: 10, to: bob_pubkey, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), ..PayCommand::default() }); process_command(&config_online).unwrap(); check_balance(40, &rpc_client, &config_offline.keypair.pubkey()); - check_balance(50, &rpc_client, &config_online.keypair.pubkey()); + check_balance(50, &rpc_client, &online_pubkey); check_balance(10, &rpc_client, &bob_pubkey); server.close().unwrap(); @@ -357,7 +362,7 @@ fn test_nonced_pay_tx() { let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed: None, nonce_authority: Some(config.keypair.pubkey()), lamports: minimum_nonce_balance, diff --git a/cli/tests/request_airdrop.rs b/cli/tests/request_airdrop.rs index 931054bf07..475bc47e60 100644 --- a/cli/tests/request_airdrop.rs +++ b/cli/tests/request_airdrop.rs @@ -2,7 +2,6 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; use solana_core::validator::new_validator_for_tests; use solana_faucet::faucet::run_local_faucet; -use solana_sdk::signature::Signer; use std::fs::remove_dir_all; use std::sync::mpsc::channel; diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index bb75922442..5c2188270a 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -1,3 +1,4 @@ +use solana_clap_utils::keypair::presigner_from_pubkey_sigs; use solana_cli::{ cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig}, offline::{parse_sign_only_reply_string, BlockhashQuery}, @@ -20,6 +21,7 @@ use std::sync::mpsc::channel; use solana_core::validator::{ new_validator_for_tests, new_validator_for_tests_ex, new_validator_for_tests_with_vote_pubkey, }; +use std::rc::Rc; use std::thread::sleep; use std::time::Duration; @@ -77,14 +79,13 @@ fn test_stake_delegation_force() { let (stake_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -100,7 +101,6 @@ fn test_stake_delegation_force() { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -115,7 +115,6 @@ fn test_stake_delegation_force() { stake_authority: None, force: true, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -139,13 +138,14 @@ fn test_seed_stake_delegation_and_deactivation() { let rpc_client = RpcClient::new_socket(leader_data.rpc); + let validator_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (validator_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&validator_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_validator = CliConfig::default(); + config_validator.keypair = validator_keypair.into(); config_validator.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let (validator_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_validator.keypair, tmp_file.as_file_mut()).unwrap(); - let mut config_stake = CliConfig::default(); config_stake.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); @@ -169,14 +169,13 @@ fn test_seed_stake_delegation_and_deactivation() { // Create stake account with a seed, uses the validator config as the base, // which is nice ;) config_validator.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&validator_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&validator_keypair_file).unwrap().into()), seed: Some("hi there".to_string()), staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -192,7 +191,6 @@ fn test_seed_stake_delegation_and_deactivation() { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -205,7 +203,6 @@ fn test_seed_stake_delegation_and_deactivation() { stake_account_pubkey: stake_address, stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -233,11 +230,13 @@ fn test_stake_delegation_and_deactivation() { config_validator.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (stake_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_stake = CliConfig::default(); + config_stake.keypair = stake_keypair.into(); config_stake.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_stake.keypair, tmp_file.as_file_mut()).unwrap(); request_and_confirm_airdrop( &rpc_client, @@ -250,14 +249,13 @@ fn test_stake_delegation_and_deactivation() { // Create stake account config_validator.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -273,7 +271,6 @@ fn test_stake_delegation_and_deactivation() { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -286,7 +283,6 @@ fn test_stake_delegation_and_deactivation() { stake_account_pubkey: config_stake.keypair.pubkey(), stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -318,11 +314,13 @@ fn test_offline_stake_delegation_and_deactivation() { config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (stake_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_stake = CliConfig::default(); + config_stake.keypair = stake_keypair.into(); config_stake.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_stake.keypair, tmp_file.as_file_mut()).unwrap(); let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = String::default(); @@ -350,14 +348,13 @@ fn test_offline_stake_delegation_and_deactivation() { // Create stake account config_validator.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: Some(config_offline.keypair.pubkey().into()), withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -374,7 +371,6 @@ fn test_offline_stake_delegation_and_deactivation() { stake_authority: None, force: false, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -382,17 +378,18 @@ fn test_offline_stake_delegation_and_deactivation() { }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = + presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap(); config_payer.command = CliCommand::DelegateStake { stake_account_pubkey: config_stake.keypair.pubkey(), vote_account_pubkey: vote_pubkey, - stake_authority: Some(config_offline.keypair.pubkey().into()), + stake_authority: Some(offline_presigner.clone().into()), force: false, sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, - fee_payer: Some(config_offline.keypair.pubkey().into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config_payer).unwrap(); @@ -402,7 +399,6 @@ fn test_offline_stake_delegation_and_deactivation() { stake_account_pubkey: config_stake.keypair.pubkey(), stake_authority: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -410,15 +406,16 @@ fn test_offline_stake_delegation_and_deactivation() { }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = + presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap(); config_payer.command = CliCommand::DeactivateStake { stake_account_pubkey: config_stake.keypair.pubkey(), - stake_authority: Some(config_offline.keypair.pubkey().into()), + stake_authority: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: Some(config_offline.keypair.pubkey().into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config_payer).unwrap(); @@ -438,7 +435,11 @@ fn test_nonced_stake_delegation_and_deactivation() { let rpc_client = RpcClient::new_socket(leader_data.rpc); + let config_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (config_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_keypair, tmp_file.as_file_mut()).unwrap(); let mut config = CliConfig::default(); + config.keypair = config_keypair.into(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let minimum_nonce_balance = rpc_client @@ -453,14 +454,13 @@ fn test_nonced_stake_delegation_and_deactivation() { let (stake_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -474,7 +474,7 @@ fn test_nonced_stake_delegation_and_deactivation() { let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed: None, nonce_authority: Some(config.keypair.pubkey()), lamports: minimum_nonce_balance, @@ -496,7 +496,6 @@ fn test_nonced_stake_delegation_and_deactivation() { stake_authority: None, force: false, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, @@ -513,15 +512,13 @@ fn test_nonced_stake_delegation_and_deactivation() { }; // Deactivate stake - let config_keypair = Keypair::from_bytes(&config.keypair.to_bytes()).unwrap(); config.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_keypair.pubkey(), stake_authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: Some(config_keypair.into()), + nonce_authority: Some(read_keypair_file(&config_keypair_file).unwrap().into()), fee_payer: None, }; process_command(&config).unwrap(); @@ -547,8 +544,13 @@ fn test_stake_authorize() { request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) .unwrap(); + let offline_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (offline_authority_file, mut tmp_file) = make_tmp_file(); + write_keypair(&offline_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_offline = CliConfig::default(); + config_offline.keypair = offline_keypair.into(); config_offline.json_rpc_url = String::default(); + let offline_authority_pubkey = config_offline.keypair.pubkey(); config_offline.command = CliCommand::ClusterVersion; // Verfiy that we cannot reach the cluster process_command(&config_offline).unwrap_err(); @@ -567,14 +569,13 @@ fn test_stake_authorize() { let (stake_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -594,7 +595,6 @@ fn test_stake_authorize() { stake_authorize: StakeAuthorize::Staker, authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -610,16 +610,12 @@ fn test_stake_authorize() { assert_eq!(current_authority, online_authority_pubkey); // Assign new offline stake authority - let offline_authority_pubkey = config_offline.keypair.pubkey(); - let (offline_authority_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: offline_authority_pubkey, stake_authorize: StakeAuthorize::Staker, authority: Some(read_keypair_file(&online_authority_file).unwrap().into()), sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -646,7 +642,6 @@ fn test_stake_authorize() { stake_authorize: StakeAuthorize::Staker, authority: Some(read_keypair_file(&offline_authority_file).unwrap().into()), sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -654,17 +649,18 @@ fn test_stake_authorize() { }; let sign_reply = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); + let offline_presigner = + presigner_from_pubkey_sigs(&offline_authority_pubkey, &signers).unwrap(); config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: nonced_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(offline_authority_pubkey.into()), + authority: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: Some(offline_authority_pubkey.into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -683,7 +679,7 @@ fn test_stake_authorize() { let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed: None, nonce_authority: Some(config_offline.keypair.pubkey()), lamports: minimum_nonce_balance, @@ -709,7 +705,6 @@ fn test_stake_authorize() { stake_authorize: StakeAuthorize::Staker, authority: Some(read_keypair_file(&nonced_authority_file).unwrap().into()), sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, @@ -718,17 +713,20 @@ fn test_stake_authorize() { let sign_reply = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); assert_eq!(blockhash, nonce_hash); + let offline_presigner = + presigner_from_pubkey_sigs(&offline_authority_pubkey, &signers).unwrap(); + let nonced_authority_presigner = + presigner_from_pubkey_sigs(&nonced_authority_pubkey, &signers).unwrap(); config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: online_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(nonced_authority_pubkey.into()), + authority: Some(nonced_authority_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: Some(offline_authority_pubkey.into()), - fee_payer: Some(offline_authority_pubkey.into()), + nonce_authority: Some(offline_presigner.clone().into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -766,17 +764,17 @@ fn test_stake_authorize_with_fee_payer() { let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let payer_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let (payer_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&payer_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_payer = CliConfig::default(); + config_payer.keypair = payer_keypair.into(); config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let payer_pubkey = config_payer.keypair.pubkey(); - let (payer_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_payer.keypair, tmp_file.as_file_mut()).unwrap(); let mut config_offline = CliConfig::default(); let offline_pubkey = config_offline.keypair.pubkey(); - let (_offline_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap(); // Verify we're offline config_offline.command = CliCommand::ClusterVersion; process_command(&config_offline).unwrap_err(); @@ -797,14 +795,13 @@ fn test_stake_authorize_with_fee_payer() { let (stake_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -826,7 +823,6 @@ fn test_stake_authorize_with_fee_payer() { stake_authorize: StakeAuthorize::Staker, authority: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -855,7 +851,6 @@ fn test_stake_authorize_with_fee_payer() { stake_authorize: StakeAuthorize::Staker, authority: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -863,17 +858,17 @@ fn test_stake_authorize_with_fee_payer() { }; let sign_reply = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: payer_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(offline_pubkey.into()), + authority: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: Some(offline_pubkey.into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); // `config`'s balance again has not changed @@ -911,8 +906,6 @@ fn test_stake_split() { let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = String::default(); let offline_pubkey = config_offline.keypair.pubkey(); - let (_offline_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap(); // Verify we're offline config_offline.command = CliCommand::ClusterVersion; process_command(&config_offline).unwrap_err(); @@ -933,14 +926,13 @@ fn test_stake_split() { let (stake_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: Some(offline_pubkey), withdrawer: Some(offline_pubkey), lockup: Lockup::default(), lamports: 10 * minimum_stake_balance, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -963,7 +955,7 @@ fn test_stake_split() { let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed: None, nonce_authority: Some(offline_pubkey), lamports: minimum_nonce_balance, @@ -988,29 +980,28 @@ fn test_stake_split() { stake_account_pubkey: stake_account_pubkey, stake_authority: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account_pubkey), nonce_authority: None, - split_stake_account: read_keypair_file(&split_keypair_file).unwrap().into(), + split_stake_account: Rc::new(read_keypair_file(&split_keypair_file).unwrap().into()), seed: None, lamports: 2 * minimum_stake_balance, fee_payer: None, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::SplitStake { stake_account_pubkey: stake_account_pubkey, - stake_authority: Some(offline_pubkey.into()), + stake_authority: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account_pubkey), - nonce_authority: Some(offline_pubkey.into()), - split_stake_account: read_keypair_file(&split_keypair_file).unwrap().into(), + nonce_authority: Some(offline_presigner.clone().into()), + split_stake_account: Rc::new(read_keypair_file(&split_keypair_file).unwrap().into()), seed: None, lamports: 2 * minimum_stake_balance, - fee_payer: Some(offline_pubkey.into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); check_balance( @@ -1045,8 +1036,6 @@ fn test_stake_set_lockup() { let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = String::default(); let offline_pubkey = config_offline.keypair.pubkey(); - let (_offline_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap(); // Verify we're offline config_offline.command = CliCommand::ClusterVersion; process_command(&config_offline).unwrap_err(); @@ -1072,14 +1061,13 @@ fn test_stake_set_lockup() { lockup.custodian = config.keypair.pubkey(); config.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: Some(offline_pubkey), withdrawer: Some(offline_pubkey), lockup, lamports: 10 * minimum_stake_balance, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -1104,7 +1092,6 @@ fn test_stake_set_lockup() { lockup, custodian: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -1136,7 +1123,6 @@ fn test_stake_set_lockup() { lockup, custodian: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -1154,7 +1140,6 @@ fn test_stake_set_lockup() { lockup, custodian: Some(read_keypair_file(&online_custodian_file).unwrap().into()), sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -1181,7 +1166,6 @@ fn test_stake_set_lockup() { lockup, custodian: Some(online_custodian.into()), sign_only: false, - signers: None, blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, @@ -1198,7 +1182,7 @@ fn test_stake_set_lockup() { let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed: None, nonce_authority: Some(offline_pubkey), lamports: minimum_nonce_balance, @@ -1225,7 +1209,6 @@ fn test_stake_set_lockup() { lockup, custodian: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account_pubkey), nonce_authority: None, @@ -1233,16 +1216,16 @@ fn test_stake_set_lockup() { }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: Some(offline_pubkey.into()), + custodian: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account_pubkey), - nonce_authority: Some(offline_pubkey.into()), - fee_payer: Some(offline_pubkey.into()), + nonce_authority: Some(offline_presigner.clone().into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -1269,14 +1252,12 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { let rpc_client = RpcClient::new_socket(leader_data.rpc); let mut config = CliConfig::default(); - config.keypair = keypair_from_seed(&[1u8; 32]).unwrap(); + config.keypair = keypair_from_seed(&[1u8; 32]).unwrap().into(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let mut config_offline = CliConfig::default(); - config_offline.keypair = keypair_from_seed(&[2u8; 32]).unwrap(); + config_offline.keypair = keypair_from_seed(&[2u8; 32]).unwrap().into(); let offline_pubkey = config_offline.keypair.pubkey(); - let (offline_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap(); config_offline.json_rpc_url = String::default(); config_offline.command = CliCommand::ClusterVersion; // Verfiy that we cannot reach the cluster @@ -1304,7 +1285,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), seed: None, nonce_authority: Some(offline_pubkey), lamports: minimum_nonce_balance, @@ -1325,14 +1306,13 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { let (stake_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config_offline.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_pubkey), nonce_authority: None, @@ -1341,20 +1321,22 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::CreateStakeAccount { - stake_account: stake_pubkey.into(), + stake_account: presigner_from_pubkey_sigs(&stake_pubkey, &signers) + .map(|p| Rc::new(p.into())) + .unwrap(), seed: None, - staker: Some(offline_pubkey.into()), + staker: Some(offline_pubkey), withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_pubkey), - nonce_authority: Some(offline_pubkey.into()), - fee_payer: Some(offline_pubkey.into()), - from: Some(offline_pubkey.into()), + nonce_authority: Some(offline_presigner.clone().into()), + fee_payer: Some(offline_presigner.clone().into()), + from: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); check_balance(50_000, &rpc_client, &stake_pubkey); @@ -1376,7 +1358,6 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { lamports: 42, withdraw_authority: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_pubkey), nonce_authority: None, @@ -1384,36 +1365,35 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::WithdrawStake { stake_account_pubkey: stake_pubkey, destination_account_pubkey: recipient_pubkey, lamports: 42, - withdraw_authority: Some(offline_pubkey.into()), + withdraw_authority: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_pubkey), - nonce_authority: Some(offline_pubkey.into()), - fee_payer: Some(offline_pubkey.into()), + nonce_authority: Some(offline_presigner.clone().into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); check_balance(42, &rpc_client, &recipient_pubkey); // Test that offline derived addresses fail config_offline.command = CliCommand::CreateStakeAccount { - stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + stake_account: Rc::new(Box::new(read_keypair_file(&stake_keypair_file).unwrap())), seed: Some("fail".to_string()), staker: None, withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_pubkey), - nonce_authority: Some(read_keypair_file(&offline_keypair_file).unwrap().into()), - fee_payer: Some(read_keypair_file(&offline_keypair_file).unwrap().into()), - from: Some(read_keypair_file(&offline_keypair_file).unwrap().into()), + nonce_authority: None, + fee_payer: None, + from: None, }; process_command(&config_offline).unwrap_err(); diff --git a/cli/tests/transfer.rs b/cli/tests/transfer.rs index 487ae679cc..9b5476aa77 100644 --- a/cli/tests/transfer.rs +++ b/cli/tests/transfer.rs @@ -1,3 +1,4 @@ +use solana_clap_utils::keypair::presigner_from_pubkey_sigs; use solana_cli::{ cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig}, offline::{parse_sign_only_reply_string, BlockhashQuery}, @@ -16,6 +17,7 @@ use std::sync::mpsc::channel; #[cfg(test)] use solana_core::validator::new_validator_for_tests_ex; +use std::rc::Rc; use std::thread::sleep; use std::time::Duration; use tempfile::NamedTempFile; @@ -66,7 +68,6 @@ fn test_transfer() { to: recipient_pubkey, from: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::All, nonce_account: None, nonce_authority: None, @@ -94,7 +95,6 @@ fn test_transfer() { to: recipient_pubkey, from: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, @@ -102,16 +102,16 @@ fn test_transfer() { }; let sign_only_reply = process_command(&offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: Some(offline_pubkey.into()), + from: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, - fee_payer: Some(offline_pubkey.into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); check_balance(39, &rpc_client, &offline_pubkey); @@ -125,7 +125,7 @@ fn test_transfer() { .get_minimum_balance_for_rent_exemption(NonceState::size()) .unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: read_keypair_file(&nonce_account_file).unwrap().into(), + nonce_account: Rc::new(read_keypair_file(&nonce_account_file).unwrap().into()), seed: None, nonce_authority: None, lamports: minimum_nonce_balance, @@ -147,7 +147,6 @@ fn test_transfer() { to: recipient_pubkey, from: None, sign_only: false, - signers: None, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, @@ -187,7 +186,6 @@ fn test_transfer() { to: recipient_pubkey, from: None, sign_only: true, - signers: None, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, @@ -195,16 +193,16 @@ fn test_transfer() { }; let sign_only_reply = process_command(&offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply); + let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); config.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: Some(offline_pubkey.into()), + from: Some(offline_presigner.clone().into()), sign_only: false, - signers: Some(signers), blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: Some(offline_pubkey.into()), - fee_payer: Some(offline_pubkey.into()), + nonce_authority: Some(offline_presigner.clone().into()), + fee_payer: Some(offline_presigner.clone().into()), }; process_command(&config).unwrap(); check_balance(28, &rpc_client, &offline_pubkey); diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index cd9c1ccb3a..f9bacd39b1 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -6,21 +6,16 @@ use clap::{ }; use num_cpus; use solana_clap_utils::{ - input_parsers::derivation_of, input_validators::is_derivation, keypair::{ - keypair_from_seed_phrase, parse_keypair_path, prompt_passphrase, KeypairUrl, + keypair_from_seed_phrase, keypair_util_from_path, prompt_passphrase, SKIP_SEED_PHRASE_VALIDATION_ARG, }, }; use solana_cli_config::config::{Config, CONFIG_FILE}; -use solana_remote_wallet::remote_keypair::generate_remote_keypair; use solana_sdk::{ pubkey::write_pubkey_file, - signature::{ - keypair_from_seed, read_keypair, read_keypair_file, write_keypair, write_keypair_file, - Keypair, Signer, - }, + signature::{keypair_from_seed, write_keypair, write_keypair_file, Keypair, Signer}, }; use std::{ collections::HashSet, @@ -64,26 +59,7 @@ fn get_keypair_from_matches( path.extend(&[".config", "solana", "id.json"]); path.to_str().unwrap() }; - - match parse_keypair_path(path) { - KeypairUrl::Ask => { - let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); - Ok(Box::new(keypair_from_seed_phrase( - "pubkey recovery", - skip_validation, - false, - )?)) - } - KeypairUrl::Filepath(path) => Ok(Box::new(read_keypair_file(&path)?)), - KeypairUrl::Stdin => { - let mut stdin = std::io::stdin(); - Ok(Box::new(read_keypair(&mut stdin)?)) - } - KeypairUrl::Usb(path) => Ok(Box::new(generate_remote_keypair( - path, - derivation_of(matches, "derivation_path"), - )?)), - } + keypair_util_from_path(matches, path, "pubkey recovery") } fn output_keypair( diff --git a/remote-wallet/src/ledger.rs b/remote-wallet/src/ledger.rs index 56da1a5f1b..e6aef6b4a9 100644 --- a/remote-wallet/src/ledger.rs +++ b/remote-wallet/src/ledger.rs @@ -341,7 +341,7 @@ fn extend_and_serialize(derivation_path: &DerivationPath) -> Vec { pub fn get_ledger_from_info( info: RemoteWalletInfo, ) -> Result, RemoteWalletError> { - let wallet_manager = initialize_wallet_manager(); + let wallet_manager = initialize_wallet_manager()?; let _device_count = wallet_manager.update_devices()?; let devices = wallet_manager.list_devices(); let (pubkeys, device_paths): (Vec, Vec) = devices diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index 277d8222f0..e5037faeb9 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -294,9 +294,9 @@ pub fn is_valid_hid_device(usage_page: u16, interface_number: i32) -> bool { } /// Helper to initialize hidapi and RemoteWalletManager -pub fn initialize_wallet_manager() -> Arc { - let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().unwrap())); - RemoteWalletManager::new(hidapi) +pub fn initialize_wallet_manager() -> Result, RemoteWalletError> { + let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new()?)); + Ok(RemoteWalletManager::new(hidapi)) } #[cfg(test)] diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 2d3a9fcf56..838b5e1474 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -139,6 +139,18 @@ pub trait Signer { fn try_sign_message(&self, message: &[u8]) -> Result; } +impl PartialEq for dyn Signer { + fn eq(&self, other: &dyn Signer) -> bool { + self.pubkey() == other.pubkey() + } +} + +impl std::fmt::Debug for dyn Signer { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "Signer: {:?}", self.pubkey()) + } +} + impl Signer for Keypair { /// Return the public key for the given keypair fn pubkey(&self) -> Pubkey { @@ -167,6 +179,15 @@ where } } +impl From for Box +where + T: Signer + 'static, +{ + fn from(keypair_util: T) -> Self { + Box::new(keypair_util) + } +} + #[derive(Debug, Error, PartialEq)] pub enum SignerError { #[error("keypair-pubkey mismatch")] @@ -202,15 +223,14 @@ pub enum SignerError { UserCancel, } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Presigner { pubkey: Pubkey, signature: Signature, } impl Presigner { - #[allow(dead_code)] - fn new(pubkey: &Pubkey, signature: &Signature) -> Self { + pub fn new(pubkey: &Pubkey, signature: &Signature) -> Self { Self { pubkey: *pubkey, signature: *signature, diff --git a/sdk/src/signers.rs b/sdk/src/signers.rs index 67b7507969..8d8997b814 100644 --- a/sdk/src/signers.rs +++ b/sdk/src/signers.rs @@ -48,6 +48,34 @@ impl Signers for [Box] { default_keypairs_impl!(); } +impl Signers for Vec<&dyn Signer> { + default_keypairs_impl!(); +} + +impl Signers for [&dyn Signer] { + default_keypairs_impl!(); +} + +impl Signers for [&dyn Signer; 0] { + default_keypairs_impl!(); +} + +impl Signers for [&dyn Signer; 1] { + default_keypairs_impl!(); +} + +impl Signers for [&dyn Signer; 2] { + default_keypairs_impl!(); +} + +impl Signers for [&dyn Signer; 3] { + default_keypairs_impl!(); +} + +impl Signers for [&dyn Signer; 4] { + default_keypairs_impl!(); +} + impl Signers for [&T; 0] { default_keypairs_impl!(); } @@ -111,4 +139,22 @@ mod tests { vec![Signature::default(), Signature::default()], ); } + + #[test] + fn test_dyn_keypairs_by_ref_compile() { + let foo = Foo {}; + let bar = Bar {}; + let xs: Vec<&dyn Signer> = vec![&foo, &bar]; + assert_eq!( + xs.sign_message(b""), + vec![Signature::default(), Signature::default()], + ); + + // Same as above, but less compiler magic. + let xs_ref: &[&dyn Signer] = &xs; + assert_eq!( + Signers::sign_message(xs_ref, b""), + vec![Signature::default(), Signature::default()], + ); + } } diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index 89796f5faf..e5b24d8b02 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -379,7 +379,7 @@ mod tests { use crate::{ hash::hash, instruction::AccountMeta, - signature::{Keypair, Signer}, + signature::{Keypair, Presigner, Signer}, system_instruction, }; use bincode::{deserialize, serialize, serialized_size}; @@ -671,4 +671,52 @@ mod tests { ); assert!(tx.is_signed()); } + + #[test] + fn test_try_sign_dyn_keypairs() { + let program_id = Pubkey::default(); + let keypair = Keypair::new(); + let pubkey = keypair.pubkey(); + let presigner_keypair = Keypair::new(); + let presigner_pubkey = presigner_keypair.pubkey(); + + let ix = Instruction::new( + program_id, + &0, + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new(presigner_pubkey, true), + ], + ); + let mut tx = Transaction::new_unsigned_instructions(vec![ix]); + + let presigner_sig = presigner_keypair.sign_message(&tx.message_data()); + let presigner = Presigner::new(&presigner_pubkey, &presigner_sig); + + let signers: Vec<&dyn Signer> = vec![&keypair, &presigner]; + + let res = tx.try_sign(&signers, Hash::default()); + assert_eq!(res, Ok(())); + assert_eq!(tx.signatures[0], keypair.sign_message(&tx.message_data())); + assert_eq!(tx.signatures[1], presigner_sig); + + // Wrong key should error, not panic + let another_pubkey = Pubkey::new_rand(); + let ix = Instruction::new( + program_id, + &0, + vec![ + AccountMeta::new(another_pubkey, true), + AccountMeta::new(presigner_pubkey, true), + ], + ); + let mut tx = Transaction::new_unsigned_instructions(vec![ix]); + + let res = tx.try_sign(&signers, Hash::default()); + assert!(res.is_err()); + assert_eq!( + tx.signatures, + vec![Signature::default(), Signature::default()] + ); + } }