CLI: dynamic signing reboot (#8384)

* Add keypair_util_from_path helper

* Cli: impl config.keypair as a trait object

* SDK: Add Debug and PartialEq for dyn Signer

* ClapUtils: Arg parsing from pubkey+signers to Presigner

* Impl Signers for &dyn Signer collections

* CLI: Add helper for getting signers from args

* CLI: Replace SigningAuthority with Signer trait-objs

* CLI: Drop disused signers command field

* CLI: Drop redundant tests

* Add clap validator that handles all current signer types

* clap_utils: Factor Presigner resolution to helper

* SDK: `From` for boxing Signer implementors to trait objects

* SDK: Derive `Clone` for `Presigner`

* Remove panic

* Cli: dedup signers in transfer for remote-wallet ergonomics

* Update docs vis-a-vis ASK changes

* Cli: update transaction types to use new dynamic-signer methods

* CLI: Fix tests No. 1

what to do about write_keypair outstanding

* Work around `CliConfig`'s signer not necessarily being a `Keypair`

* CLI: Fix tests No. 2

* Remove unused arg

* Remove unused methods

* Move offline arg constants upstream

* Make cli signing fallible

Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>
This commit is contained in:
Tyera Eulberg 2020-02-21 14:55:53 -07:00 committed by GitHub
parent aa80f69171
commit 4ddbf8d509
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 911 additions and 1147 deletions

View File

@ -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 <STAKE_ACCOUNT_PUBKEY> <VOTE_ACCOUNT_PUBKEY>
solana delegate-stake --keypair ASK <STAKE_ACCOUNT_PUBKEY> <VOTE_ACCOUNT_PUBKEY>
[keypair] seed phrase: 🔒
[keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:

View File

@ -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<Vec<(Pubk
})
}
// Return a signer from matches at `name`
pub fn signer_of(
name: &str,
matches: &ArgMatches<'_>,
) -> Result<Option<Box<dyn Signer>>, Box<dyn std::error::Error>> {
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<u64> {
value_of(matches, name).map(sol_to_lamports)
}

View File

@ -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('=');

View File

@ -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<Presigner> {
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<dyn Signer>, Box<dyn error::Error>> {
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";

View File

@ -26,3 +26,4 @@ pub struct ArgConstant<'a> {
pub mod input_parsers;
pub mod input_validators;
pub mod keypair;
pub mod offline;

19
clap-utils/src/offline.rs Normal file
View File

@ -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",
};

File diff suppressed because it is too large Load Diff

View File

@ -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(),

View File

@ -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<CliConfig, Box<dyn error::
} = parse_command(&matches)?;
let (keypair, keypair_path) = if require_keypair {
let KeypairWithSource { keypair, source } = keypair_input(&matches, "keypair")?;
match source {
keypair::Source::Path => (
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<dyn error::Error>> {
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<dyn error::Error>> {
.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)

View File

@ -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<CliCommandInfo, CliError> {
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<CliComm
}
pub fn parse_nonce_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
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<CliCommandInfo, CliEr
pub fn parse_new_nonce(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
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::<NonceError>(result)
}
pub fn process_create_nonce_account(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Keypair,
nonce_account: &dyn Signer,
seed: Option<String>,
nonce_authority: Option<Pubkey>,
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::<SystemError>(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::<NonceError>(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()

View File

@ -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),

File diff suppressed because it is too large Load Diff

View File

@ -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::<SystemError>(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(),

View File

@ -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(),

View File

@ -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::<VoteError>(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::<VoteError>(result)
}

View File

@ -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<SigningAuthority> {
fn read_keypair_from_option(keypair_file: &Option<&str>) -> Option<Box<dyn Signer>> {
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,
};

View File

@ -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,

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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(

View File

@ -341,7 +341,7 @@ fn extend_and_serialize(derivation_path: &DerivationPath) -> Vec<u8> {
pub fn get_ledger_from_info(
info: RemoteWalletInfo,
) -> Result<Arc<LedgerWallet>, 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<Pubkey>, Vec<String>) = devices

View File

@ -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<RemoteWalletManager> {
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().unwrap()));
RemoteWalletManager::new(hidapi)
pub fn initialize_wallet_manager() -> Result<Arc<RemoteWalletManager>, RemoteWalletError> {
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new()?));
Ok(RemoteWalletManager::new(hidapi))
}
#[cfg(test)]

View File

@ -139,6 +139,18 @@ pub trait Signer {
fn try_sign_message(&self, message: &[u8]) -> Result<Signature, SignerError>;
}
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<T> From<T> for Box<dyn Signer>
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,

View File

@ -48,6 +48,34 @@ impl Signers for [Box<dyn Signer>] {
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<T: Signer> 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()],
);
}
}

View File

@ -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()]
);
}
}