diff --git a/cli/src/cli.rs b/cli/src/cli.rs index d6e6c95957..e1c18ce649 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1,10 +1,10 @@ use crate::{ checks::*, - cli_output::{CliAccount, CliSignOnlyData, CliSignature, OutputFormat}, + cli_output::{CliAccount, CliSignature, OutputFormat}, cluster_query::*, display::{new_spinner_progress_bar, println_name_value, println_transaction}, nonce::*, - offline::blockhash_query::BlockhashQuery, + offline::{blockhash_query::BlockhashQuery, return_signers}, spend_utils::*, stake::*, validator_info::*, @@ -909,35 +909,6 @@ pub fn get_blockhash_and_fee_calculator( }) } -pub fn return_signers(tx: &Transaction, output_format: &OutputFormat) -> ProcessResult { - let verify_results = tx.verify_with_results(); - let mut signers = Vec::new(); - let mut absent = Vec::new(); - let mut bad_sig = Vec::new(); - tx.signatures - .iter() - .zip(tx.message.account_keys.iter()) - .zip(verify_results.into_iter()) - .for_each(|((sig, key), res)| { - if res { - signers.push(format!("{}={}", key, sig)) - } else if *sig == Signature::default() { - absent.push(key.to_string()); - } else { - bad_sig.push(key.to_string()); - } - }); - - let cli_command = CliSignOnlyData { - blockhash: tx.message.recent_blockhash.to_string(), - signers, - absent, - bad_sig, - }; - - Ok(output_format.formatted_string(&cli_command)) -} - pub fn parse_create_address_with_seed( matches: &ArgMatches<'_>, default_signer_path: &str, @@ -2351,9 +2322,7 @@ mod tests { use solana_client::mock_sender::SIGNATURE; use solana_sdk::{ pubkey::Pubkey, - signature::{ - keypair_from_seed, read_keypair_file, write_keypair_file, NullSigner, Presigner, - }, + signature::{keypair_from_seed, read_keypair_file, write_keypair_file, Presigner}, transaction::TransactionError, }; use std::path::PathBuf; @@ -3133,52 +3102,4 @@ mod tests { } ); } - - #[test] - fn test_return_signers() { - struct BadSigner { - pubkey: Pubkey, - } - - impl BadSigner { - pub fn new(pubkey: Pubkey) -> Self { - Self { pubkey } - } - } - - impl Signer for BadSigner { - fn try_pubkey(&self) -> Result { - Ok(self.pubkey) - } - - fn try_sign_message(&self, _message: &[u8]) -> Result { - Ok(Signature::new(&[1u8; 64])) - } - } - - let present: Box = Box::new(keypair_from_seed(&[2u8; 32]).unwrap()); - let absent: Box = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32]))); - let bad: Box = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32]))); - let to = Pubkey::new(&[5u8; 32]); - let nonce = Pubkey::new(&[6u8; 32]); - let from = present.pubkey(); - let fee_payer = absent.pubkey(); - let nonce_auth = bad.pubkey(); - let mut tx = Transaction::new_unsigned(Message::new_with_nonce( - vec![system_instruction::transfer(&from, &to, 42)], - Some(&fee_payer), - &nonce, - &nonce_auth, - )); - - let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()]; - let blockhash = Hash::new(&[7u8; 32]); - tx.try_partial_sign(&signers, blockhash).unwrap(); - let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap(); - let sign_only = parse_sign_only_reply_string(&res); - assert_eq!(sign_only.blockhash, blockhash); - assert_eq!(sign_only.present_signers[0].0, present.pubkey()); - assert_eq!(sign_only.absent_signers[0], absent.pubkey()); - assert_eq!(sign_only.bad_signers[0], bad.pubkey()); - } } diff --git a/cli/src/offline/mod.rs b/cli/src/offline/mod.rs index f00cb8f1e2..1e1d3a0bfe 100644 --- a/cli/src/offline/mod.rs +++ b/cli/src/offline/mod.rs @@ -1,5 +1,6 @@ pub mod blockhash_query; +use crate::cli_output::{CliSignOnlyData, OutputFormat}; use serde_json::Value; use solana_clap_utils::keypair::presigner_from_pubkey_sigs; use solana_client::rpc_client::RpcClient; @@ -8,9 +9,42 @@ use solana_sdk::{ hash::Hash, pubkey::Pubkey, signature::{Presigner, Signature}, + transaction::Transaction, }; use std::str::FromStr; +pub fn return_signers( + tx: &Transaction, + output_format: &OutputFormat, +) -> Result> { + let verify_results = tx.verify_with_results(); + let mut signers = Vec::new(); + let mut absent = Vec::new(); + let mut bad_sig = Vec::new(); + tx.signatures + .iter() + .zip(tx.message.account_keys.iter()) + .zip(verify_results.into_iter()) + .for_each(|((sig, key), res)| { + if res { + signers.push(format!("{}={}", key, sig)) + } else if *sig == Signature::default() { + absent.push(key.to_string()); + } else { + bad_sig.push(key.to_string()); + } + }); + + let cli_command = CliSignOnlyData { + blockhash: tx.message.recent_blockhash.to_string(), + signers, + absent, + bad_sig, + }; + + Ok(output_format.formatted_string(&cli_command)) +} + pub struct SignOnly { pub blockhash: Hash, pub present_signers: Vec<(Pubkey, Signature)>, @@ -80,3 +114,63 @@ pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly { bad_signers, } } + +#[cfg(test)] +mod tests { + use super::*; + use solana_sdk::{ + message::Message, + pubkey::Pubkey, + signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError}, + system_instruction, + transaction::Transaction, + }; + + #[test] + fn test_return_signers() { + struct BadSigner { + pubkey: Pubkey, + } + + impl BadSigner { + pub fn new(pubkey: Pubkey) -> Self { + Self { pubkey } + } + } + + impl Signer for BadSigner { + fn try_pubkey(&self) -> Result { + Ok(self.pubkey) + } + + fn try_sign_message(&self, _message: &[u8]) -> Result { + Ok(Signature::new(&[1u8; 64])) + } + } + + let present: Box = Box::new(keypair_from_seed(&[2u8; 32]).unwrap()); + let absent: Box = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32]))); + let bad: Box = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32]))); + let to = Pubkey::new(&[5u8; 32]); + let nonce = Pubkey::new(&[6u8; 32]); + let from = present.pubkey(); + let fee_payer = absent.pubkey(); + let nonce_auth = bad.pubkey(); + let mut tx = Transaction::new_unsigned(Message::new_with_nonce( + vec![system_instruction::transfer(&from, &to, 42)], + Some(&fee_payer), + &nonce, + &nonce_auth, + )); + + let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()]; + let blockhash = Hash::new(&[7u8; 32]); + tx.try_partial_sign(&signers, blockhash).unwrap(); + let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap(); + let sign_only = parse_sign_only_reply_string(&res); + assert_eq!(sign_only.blockhash, blockhash); + assert_eq!(sign_only.present_signers[0].0, present.pubkey()); + assert_eq!(sign_only.absent_signers[0], absent.pubkey()); + assert_eq!(sign_only.bad_signers[0], bad.pubkey()); + } +} diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 85024cb41c..17cf4d52d2 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1,12 +1,12 @@ use crate::{ checks::{check_account_for_fee_with_commitment, check_unique_pubkeys}, cli::{ - generate_unique_signers, log_instruction_custom_error, return_signers, CliCommand, - CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, + generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo, + CliConfig, CliError, ProcessResult, SignerIndex, }, cli_output::{CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType}, nonce::check_nonce_account, - offline::blockhash_query::BlockhashQuery, + offline::{blockhash_query::BlockhashQuery, return_signers}, spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, }; use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};