From 6775e83420905e60be8001aa085df9fb55f75347 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Thu, 9 Jan 2020 15:22:48 -0800 Subject: [PATCH] Add create with seed to cli (#7713) * Add create with seed to cli * nonce and vote, too --- cli/src/cli.rs | 12 ++++ cli/src/nonce.rs | 63 +++++++++++++++----- cli/src/stake.rs | 63 +++++++++++++++----- cli/src/vote.rs | 84 +++++++++++++++++++++------ cli/tests/nonce.rs | 73 ++++++++++++++++++----- cli/tests/pay.rs | 1 + cli/tests/stake.rs | 105 +++++++++++++++++++++++++++++++++- sdk/src/system_instruction.rs | 37 +++++++++++- 8 files changed, 376 insertions(+), 62 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 50ed961e42..24e61e23b5 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -137,6 +137,7 @@ pub enum CliCommand { }, CreateNonceAccount { nonce_account: KeypairEq, + seed: Option, nonce_authority: Option, lamports: u64, }, @@ -160,6 +161,7 @@ pub enum CliCommand { // Stake Commands CreateStakeAccount { stake_account: KeypairEq, + seed: Option, staker: Option, withdrawer: Option, lockup: Lockup, @@ -214,6 +216,7 @@ pub enum CliCommand { // Vote Commands CreateVoteAccount { vote_account: KeypairEq, + seed: Option, node_pubkey: Pubkey, authorized_voter: Option, authorized_withdrawer: Option, @@ -1201,12 +1204,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Create nonce account CliCommand::CreateNonceAccount { nonce_account, + seed, nonce_authority, lamports, } => process_create_nonce_account( &rpc_client, config, nonce_account, + seed.clone(), *nonce_authority, *lamports, ), @@ -1256,6 +1261,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Create stake account CliCommand::CreateStakeAccount { stake_account, + seed, staker, withdrawer, lockup, @@ -1264,6 +1270,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, stake_account, + seed, staker, withdrawer, lockup, @@ -1401,6 +1408,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Create vote account CliCommand::CreateVoteAccount { vote_account, + seed, node_pubkey, authorized_voter, authorized_withdrawer, @@ -1409,6 +1417,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, vote_account, + seed, &node_pubkey, authorized_voter, authorized_withdrawer, @@ -2536,6 +2545,7 @@ mod tests { let node_pubkey = Pubkey::new_rand(); config.command = CliCommand::CreateVoteAccount { vote_account: bob_keypair.into(), + seed: None, node_pubkey, authorized_voter: Some(bob_pubkey), authorized_withdrawer: Some(bob_pubkey), @@ -2567,6 +2577,7 @@ mod tests { let custodian = Pubkey::new_rand(); config.command = CliCommand::CreateStakeAccount { stake_account: bob_keypair.into(), + seed: None, staker: None, withdrawer: None, lockup: Lockup { @@ -2774,6 +2785,7 @@ mod tests { let bob_keypair = Keypair::new(); config.command = CliCommand::CreateVoteAccount { vote_account: bob_keypair.into(), + seed: None, node_pubkey, authorized_voter: Some(bob_pubkey), authorized_withdrawer: Some(bob_pubkey), diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index 0927f798de..6bd1a91472 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -14,8 +14,8 @@ use solana_sdk::{ pubkey::Pubkey, signature::{Keypair, KeypairUtil}, system_instruction::{ - create_nonce_account, nonce_advance, nonce_authorize, nonce_withdraw, NonceError, - SystemError, + create_address_with_seed, create_nonce_account, create_nonce_account_with_seed, + nonce_advance, nonce_authorize, nonce_withdraw, NonceError, SystemError, }, system_program, transaction::Transaction, @@ -81,6 +81,13 @@ impl NonceSubCommands for App<'_, '_> { .validator(is_pubkey_or_keypair) .help("Account to be granted authority of the nonce account"), ) + .arg( + Arg::with_name("seed") + .long("seed") + .value_name("SEED STRING") + .takes_value(true) + .help("Seed for address generation; if specified, the resulting account will be at a derived address of the NONCE_ACCOUNT pubkey") + ) .arg(nonce_authority_arg()), ) .subcommand( @@ -227,12 +234,14 @@ pub fn parse_authorize_nonce_account(matches: &ArgMatches<'_>) -> Result) -> Result { let nonce_account = keypair_of(matches, "nonce_account_keypair").unwrap(); + let seed = matches.value_of("seed").map(|s| s.to_string()); let lamports = required_lamports_from(matches, "amount", "unit")?; let nonce_authority = pubkey_of(matches, "nonce_authority"); Ok(CliCommandInfo { command: CliCommand::CreateNonceAccount { nonce_account: nonce_account.into(), + seed, nonce_authority, lamports, }, @@ -354,16 +363,23 @@ pub fn process_create_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Keypair, + seed: Option, nonce_authority: Option, lamports: u64, ) -> ProcessResult { let nonce_account_pubkey = nonce_account.pubkey(); + let nonce_account_address = if let Some(seed) = seed.clone() { + create_address_with_seed(&nonce_account_pubkey, &seed, &system_program::id())? + } else { + nonce_account_pubkey + }; + check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), - (&nonce_account_pubkey, "nonce_account_pubkey".to_string()), + (&nonce_account_address, "nonce_account".to_string()), )?; - if rpc_client.get_account(&nonce_account_pubkey).is_ok() { + if rpc_client.get_account(&nonce_account_address).is_ok() { return Err(CliError::BadParameter(format!( "Unable to create nonce account. Nonce account already exists: {}", nonce_account_pubkey, @@ -381,17 +397,37 @@ pub fn process_create_nonce_account( } let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.pubkey()); - let ixs = create_nonce_account( - &config.keypair.pubkey(), - &nonce_account_pubkey, - &nonce_authority, - lamports, - ); + + let ixs = if let Some(seed) = seed { + create_nonce_account_with_seed( + &config.keypair.pubkey(), // from + &nonce_account_address, // to + &nonce_account_pubkey, // base + &seed, // seed + &nonce_authority, + lamports, + ) + } else { + create_nonce_account( + &config.keypair.pubkey(), + &nonce_account_pubkey, + &nonce_authority, + lamports, + ) + }; + 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 + } else { + vec![&config.keypair] // 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()), - &[&config.keypair, nonce_account], + &signers, recent_blockhash, ); check_account_for_fee( @@ -400,8 +436,7 @@ pub fn process_create_nonce_account( &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_account]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers); log_instruction_custom_error::(result) } @@ -626,6 +661,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().into(), + seed: None, nonce_authority: None, lamports: 50, }, @@ -648,6 +684,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateNonceAccount { nonce_account: 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/stake.rs b/cli/src/stake.rs index 5ef01abae3..f49b9f3840 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -17,7 +17,7 @@ use solana_sdk::{ hash::Hash, pubkey::Pubkey, signature::KeypairUtil, - system_instruction::SystemError, + system_instruction::{create_address_with_seed, SystemError}, sysvar::{ stake_history::{self, StakeHistory}, Sysvar, @@ -75,6 +75,13 @@ impl StakeSubCommands for App<'_, '_> { .validator(is_pubkey_or_keypair) .help("Identity of the custodian (can withdraw before lockup expires)") ) + .arg( + Arg::with_name("seed") + .long("seed") + .value_name("SEED STRING") + .takes_value(true) + .help("Seed for address generation; if specified, the resulting account will be at a derived address of the STAKE ACCOUNT pubkey") + ) .arg( Arg::with_name("lockup_epoch") .long("lockup-epoch") @@ -367,6 +374,7 @@ impl StakeSubCommands for App<'_, '_> { pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result { let stake_account = keypair_of(matches, "stake_account").unwrap(); + let seed = matches.value_of("seed").map(|s| s.to_string()); let epoch = value_of(&matches, "lockup_epoch").unwrap_or(0); let unix_timestamp = unix_timestamp_from_rfc3339_datetime(&matches, "lockup_date").unwrap_or(0); let custodian = pubkey_of(matches, "custodian").unwrap_or_default(); @@ -377,6 +385,7 @@ pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result, staker: &Option, withdrawer: &Option, lockup: &Lockup, lamports: u64, ) -> ProcessResult { let stake_account_pubkey = stake_account.pubkey(); + let stake_account_address = if let Some(seed) = seed { + create_address_with_seed(&stake_account_pubkey, &seed, &solana_stake_program::id())? + } else { + stake_account_pubkey + }; check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), - (&stake_account_pubkey, "stake_account_pubkey".to_string()), + (&stake_account_address, "stake_account".to_string()), )?; - if rpc_client.get_account(&stake_account_pubkey).is_ok() { + if rpc_client.get_account(&stake_account_address).is_ok() { return Err(CliError::BadParameter(format!( "Unable to create stake account. Stake account already exists: {}", - stake_account_pubkey + stake_account_address )) .into()); } @@ -551,18 +566,37 @@ pub fn process_create_stake_account( withdrawer: withdrawer.unwrap_or(config.keypair.pubkey()), }; - let ixs = stake_instruction::create_account( - &config.keypair.pubkey(), - &stake_account_pubkey, - &authorized, - lockup, - lamports, - ); + let ixs = if let Some(seed) = seed { + stake_instruction::create_account_with_seed( + &config.keypair.pubkey(), // from + &stake_account_address, // to + &stake_account_pubkey, // base + seed, // seed + &authorized, + lockup, + lamports, + ) + } else { + stake_instruction::create_account( + &config.keypair.pubkey(), + &stake_account_pubkey, + &authorized, + lockup, + lamports, + ) + }; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; + + let signers = if stake_account_pubkey != config.keypair.pubkey() { + vec![&config.keypair, stake_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 + }; + let mut tx = Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair, stake_account], + &signers, recent_blockhash, ); check_account_for_fee( @@ -571,8 +605,7 @@ pub fn process_create_stake_account( &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, stake_account]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers); log_instruction_custom_error::(result) } @@ -1023,6 +1056,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateStakeAccount { stake_account: stake_account_keypair.into(), + seed: None, staker: Some(authorized), withdrawer: Some(authorized), lockup: Lockup { @@ -1055,6 +1089,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateStakeAccount { stake_account: stake_account_keypair.into(), + seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 88a8804fbd..ca49fe5311 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -11,7 +11,10 @@ use solana_clap_utils::{input_parsers::*, input_validators::*}; use solana_client::rpc_client::RpcClient; use solana_sdk::signature::Keypair; use solana_sdk::{ - account::Account, pubkey::Pubkey, signature::KeypairUtil, system_instruction::SystemError, + account::Account, + pubkey::Pubkey, + signature::KeypairUtil, + system_instruction::{create_address_with_seed, SystemError}, transaction::Transaction, }; use solana_vote_program::{ @@ -69,6 +72,13 @@ impl VoteSubCommands for App<'_, '_> { .takes_value(true) .validator(is_pubkey_or_keypair) .help("Public key of the authorized withdrawer (defaults to cli config pubkey)"), + ) + .arg( + Arg::with_name("seed") + .long("seed") + .value_name("SEED STRING") + .takes_value(true) + .help("Seed for address generation; if specified, the resulting account will be at a derived address of the VOTE ACCOUNT pubkey") ), ) .subcommand( @@ -195,6 +205,7 @@ impl VoteSubCommands for App<'_, '_> { pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result { let vote_account = keypair_of(matches, "vote_account").unwrap(); + let seed = matches.value_of("seed").map(|s| s.to_string()); let identity_pubkey = pubkey_of(matches, "identity_pubkey").unwrap(); let commission = value_t_or_exit!(matches, "commission", u8); let authorized_voter = pubkey_of(matches, "authorized_voter"); @@ -203,6 +214,7 @@ pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result, identity_pubkey: &Pubkey, authorized_voter: &Option, authorized_withdrawer: &Option, commission: u8, ) -> ProcessResult { let vote_account_pubkey = vote_account.pubkey(); - check_unique_pubkeys( - (&vote_account_pubkey, "vote_account_pubkey".to_string()), - (&identity_pubkey, "identity_pubkey".to_string()), - )?; + let vote_account_address = if let Some(seed) = seed { + create_address_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())? + } else { + vote_account_pubkey + }; check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), - (&vote_account_pubkey, "vote_account_pubkey".to_string()), + (&vote_account_address, "vote_account".to_string()), )?; + check_unique_pubkeys( + (&vote_account_address, "vote_account".to_string()), + (&identity_pubkey, "identity_pubkey".to_string()), + )?; + + if rpc_client.get_account(&vote_account_address).is_ok() { + return Err(CliError::BadParameter(format!( + "Unable to create vote account. Vote account already exists: {}", + vote_account_address + )) + .into()); + } + let required_balance = rpc_client .get_minimum_balance_for_rent_exemption(VoteState::size_of())? .max(1); @@ -302,28 +329,43 @@ pub fn process_create_vote_account( let vote_init = VoteInit { node_pubkey: *identity_pubkey, authorized_voter: authorized_voter.unwrap_or(vote_account_pubkey), - authorized_withdrawer: authorized_withdrawer.unwrap_or(config.keypair.pubkey()), + authorized_withdrawer: authorized_withdrawer.unwrap_or(vote_account_pubkey), commission, }; - let ixs = vote_instruction::create_account( - &config.keypair.pubkey(), - &vote_account_pubkey, - &vote_init, - required_balance, - ); + + let ixs = if let Some(seed) = seed { + vote_instruction::create_account_with_seed( + &config.keypair.pubkey(), // from + &vote_account_address, // to + &vote_account_pubkey, // base + seed, // seed + &vote_init, + required_balance, + ) + } else { + vote_instruction::create_account( + &config.keypair.pubkey(), + &vote_account_pubkey, + &vote_init, + required_balance, + ) + }; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let mut tx = Transaction::new_signed_instructions( - &[&config.keypair, vote_account], - ixs, - recent_blockhash, - ); + + let signers = if vote_account_pubkey != config.keypair.pubkey() { + vec![&config.keypair, 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 + }; + + let mut tx = Transaction::new_signed_instructions(&signers, ixs, 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, vote_account]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers); log_instruction_custom_error::(result) } @@ -583,6 +625,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateVoteAccount { vote_account: keypair.into(), + seed: None, node_pubkey, authorized_voter: None, authorized_withdrawer: None, @@ -607,6 +650,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateVoteAccount { vote_account: keypair.into(), + seed: None, node_pubkey, authorized_voter: None, authorized_withdrawer: None, @@ -635,6 +679,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateVoteAccount { vote_account: keypair.into(), + seed: None, node_pubkey, authorized_voter: Some(authed), authorized_withdrawer: None, @@ -661,6 +706,7 @@ mod tests { CliCommandInfo { command: CliCommand::CreateVoteAccount { vote_account: keypair.into(), + seed: None, node_pubkey, authorized_voter: None, authorized_withdrawer: Some(authed), diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index c5861b4aa6..bda64a59ab 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -7,6 +7,8 @@ use solana_sdk::{ hash::Hash, pubkey::Pubkey, signature::{read_keypair_file, write_keypair, Keypair, KeypairUtil}, + system_instruction::create_address_with_seed, + system_program, }; use std::fs::remove_dir_all; use std::sync::mpsc::channel; @@ -62,6 +64,40 @@ fn test_nonce() { &mut config_nonce, &keypair_file, None, + None, + ); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} + +#[test] +fn test_nonce_with_seed() { + let (server, leader_data, alice, ledger_path) = new_validator_for_tests(); + let (sender, receiver) = channel(); + run_local_faucet(alice, sender, None); + let faucet_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_socket(leader_data.rpc); + + let mut config_payer = CliConfig::default(); + config_payer.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + + let mut config_nonce = CliConfig::default(); + 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, + &faucet_addr, + &mut config_payer, + &mut config_nonce, + &keypair_file, + Some(String::from("seed")), + None, ); server.close().unwrap(); @@ -97,6 +133,7 @@ fn test_nonce_with_authority() { &mut config_payer, &mut config_nonce, &nonce_keypair_file, + None, Some(&authority_keypair_file), ); @@ -114,6 +151,7 @@ fn full_battery_tests( config_payer: &mut CliConfig, config_nonce: &mut CliConfig, nonce_keypair_file: &str, + seed: Option, authority_keypair_file: Option<&str>, ) { request_and_confirm_airdrop( @@ -125,24 +163,33 @@ fn full_battery_tests( .unwrap(); check_balance(2000, &rpc_client, &config_payer.keypair.pubkey()); + let nonce_account = if let Some(seed) = seed.as_ref() { + create_address_with_seed(&config_nonce.keypair.pubkey(), seed, &system_program::id()) + .unwrap() + } else { + config_nonce.keypair.pubkey() + }; + // Create nonce account config_payer.command = CliCommand::CreateNonceAccount { nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + seed, nonce_authority: read_keypair_from_option(&authority_keypair_file) .map(|na: KeypairEq| na.pubkey()), lamports: 1000, }; + process_command(&config_payer).unwrap(); check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); - check_balance(1000, &rpc_client, &config_nonce.keypair.pubkey()); + check_balance(1000, &rpc_client, &nonce_account); // Get nonce - config_payer.command = CliCommand::GetNonce(config_nonce.keypair.pubkey()); + config_payer.command = CliCommand::GetNonce(nonce_account); let first_nonce_string = process_command(&config_payer).unwrap(); let first_nonce = first_nonce_string.parse::().unwrap(); // Get nonce - config_payer.command = CliCommand::GetNonce(config_nonce.keypair.pubkey()); + config_payer.command = CliCommand::GetNonce(nonce_account); let second_nonce_string = process_command(&config_payer).unwrap(); let second_nonce = second_nonce_string.parse::().unwrap(); @@ -150,13 +197,13 @@ fn full_battery_tests( // New nonce config_payer.command = CliCommand::NewNonce { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_account, nonce_authority: read_keypair_from_option(&authority_keypair_file), }; process_command(&config_payer).unwrap(); // Get nonce - config_payer.command = CliCommand::GetNonce(config_nonce.keypair.pubkey()); + config_payer.command = CliCommand::GetNonce(nonce_account); let third_nonce_string = process_command(&config_payer).unwrap(); let third_nonce = third_nonce_string.parse::().unwrap(); @@ -165,19 +212,19 @@ fn full_battery_tests( // Withdraw from nonce account let payee_pubkey = Pubkey::new_rand(); config_payer.command = CliCommand::WithdrawFromNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_account, nonce_authority: read_keypair_from_option(&authority_keypair_file), destination_account_pubkey: payee_pubkey, lamports: 100, }; process_command(&config_payer).unwrap(); check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); - check_balance(900, &rpc_client, &config_nonce.keypair.pubkey()); + check_balance(900, &rpc_client, &nonce_account); check_balance(100, &rpc_client, &payee_pubkey); // Show nonce account config_payer.command = CliCommand::ShowNonceAccount { - nonce_account_pubkey: config_nonce.keypair.pubkey(), + nonce_account_pubkey: nonce_account, use_lamports_unit: true, }; process_command(&config_payer).unwrap(); @@ -187,7 +234,7 @@ fn full_battery_tests( let (new_authority_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&new_authority, tmp_file.as_file_mut()).unwrap(); config_payer.command = CliCommand::AuthorizeNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_account, nonce_authority: read_keypair_from_option(&authority_keypair_file), new_authority: read_keypair_file(&new_authority_keypair_file) .unwrap() @@ -197,14 +244,14 @@ fn full_battery_tests( // Old authority fails now config_payer.command = CliCommand::NewNonce { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_account, nonce_authority: read_keypair_from_option(&authority_keypair_file), }; process_command(&config_payer).unwrap_err(); // New authority can advance nonce config_payer.command = CliCommand::NewNonce { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_account, nonce_authority: Some( read_keypair_file(&new_authority_keypair_file) .unwrap() @@ -215,7 +262,7 @@ fn full_battery_tests( // New authority can withdraw from nonce account config_payer.command = CliCommand::WithdrawFromNonceAccount { - nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_account, nonce_authority: Some( read_keypair_file(&new_authority_keypair_file) .unwrap() @@ -226,6 +273,6 @@ fn full_battery_tests( }; process_command(&config_payer).unwrap(); check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); - check_balance(800, &rpc_client, &config_nonce.keypair.pubkey()); + check_balance(800, &rpc_client, &nonce_account); check_balance(200, &rpc_client, &payee_pubkey); } diff --git a/cli/tests/pay.rs b/cli/tests/pay.rs index b1b8d74860..d40ebece49 100644 --- a/cli/tests/pay.rs +++ b/cli/tests/pay.rs @@ -368,6 +368,7 @@ fn test_nonced_pay_tx() { write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { nonce_account: 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/stake.rs b/cli/tests/stake.rs index 0e8b5b8efb..2d6985180d 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -8,6 +8,7 @@ use solana_sdk::{ nonce_state::NonceState, pubkey::Pubkey, signature::{read_keypair_file, write_keypair, Keypair, KeypairUtil, Signature}, + system_instruction::create_address_with_seed, }; use solana_stake_program::stake_state::Lockup; use std::fs::remove_dir_all; @@ -39,6 +40,101 @@ fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) { }); } +#[test] +fn test_seed_stake_delegation_and_deactivation() { + solana_logger::setup(); + + let (server, leader_data, alice, ledger_path) = new_validator_for_tests(); + let (sender, receiver) = channel(); + run_local_faucet(alice, sender, None); + let faucet_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_socket(leader_data.rpc); + + let mut config_validator = CliConfig::default(); + 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_vote = CliConfig::default(); + config_vote.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let (vote_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_vote.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()); + + request_and_confirm_airdrop( + &rpc_client, + &faucet_addr, + &config_validator.keypair.pubkey(), + 100_000, + ) + .unwrap(); + check_balance(100_000, &rpc_client, &config_validator.keypair.pubkey()); + + // Create vote account + config_validator.command = CliCommand::CreateVoteAccount { + vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), + seed: None, + node_pubkey: config_validator.keypair.pubkey(), + authorized_voter: None, + authorized_withdrawer: None, + commission: 0, + }; + process_command(&config_validator).unwrap(); + + let stake_address = create_address_with_seed( + &config_validator.keypair.pubkey(), + "hi there", + &solana_stake_program::id(), + ) + .expect("bad seed"); + + // 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(), + seed: Some("hi there".to_string()), + staker: None, + withdrawer: None, + lockup: Lockup::default(), + lamports: 50_000, + }; + process_command(&config_validator).unwrap(); + + // Delegate stake + config_validator.command = CliCommand::DelegateStake { + stake_account_pubkey: stake_address, + vote_account_pubkey: config_vote.keypair.pubkey(), + force: true, + sign_only: false, + signers: None, + blockhash: None, + nonce_account: None, + nonce_authority: None, + }; + process_command(&config_validator).unwrap(); + + // Deactivate stake + config_validator.command = CliCommand::DeactivateStake { + stake_account_pubkey: stake_address, + sign_only: false, + signers: None, + blockhash: None, + nonce_account: None, + nonce_authority: None, + }; + process_command(&config_validator).unwrap(); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} + #[test] fn test_stake_delegation_and_deactivation() { solana_logger::setup(); @@ -78,6 +174,7 @@ fn test_stake_delegation_and_deactivation() { // Create vote account config_validator.command = CliCommand::CreateVoteAccount { vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), + seed: None, node_pubkey: config_validator.keypair.pubkey(), authorized_voter: None, authorized_withdrawer: None, @@ -88,6 +185,7 @@ fn test_stake_delegation_and_deactivation() { // Create stake account config_validator.command = CliCommand::CreateStakeAccount { stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), @@ -124,7 +222,7 @@ fn test_stake_delegation_and_deactivation() { } #[test] -fn test_stake_delegation_and_deactivation_offline() { +fn test_offline_stake_delegation_and_deactivation() { solana_logger::setup(); let (server, leader_data, alice, ledger_path) = new_validator_for_tests(); @@ -166,6 +264,7 @@ fn test_stake_delegation_and_deactivation_offline() { // Create vote account config_validator.command = CliCommand::CreateVoteAccount { vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), + seed: None, node_pubkey: config_validator.keypair.pubkey(), authorized_voter: None, authorized_withdrawer: None, @@ -176,6 +275,7 @@ fn test_stake_delegation_and_deactivation_offline() { // Create stake account config_validator.command = CliCommand::CreateStakeAccount { stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), @@ -286,6 +386,7 @@ fn test_nonced_stake_delegation_and_deactivation() { write_keypair(&vote_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateVoteAccount { vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), + seed: None, node_pubkey: config.keypair.pubkey(), authorized_voter: None, authorized_withdrawer: None, @@ -299,6 +400,7 @@ fn test_nonced_stake_delegation_and_deactivation() { write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateStakeAccount { stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + seed: None, staker: None, withdrawer: None, lockup: Lockup::default(), @@ -312,6 +414,7 @@ fn test_nonced_stake_delegation_and_deactivation() { write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(), + seed: None, nonce_authority: Some(config.keypair.pubkey()), lamports: minimum_nonce_balance, }; diff --git a/sdk/src/system_instruction.rs b/sdk/src/system_instruction.rs index 3a4e717255..f55409011f 100644 --- a/sdk/src/system_instruction.rs +++ b/sdk/src/system_instruction.rs @@ -161,9 +161,11 @@ pub fn create_account( ) } +// we accept `to` as a parameter so that callers do their own error handling when +// calling create_address_with_seed() pub fn create_account_with_seed( from_pubkey: &Pubkey, - to_pubkey: &Pubkey, + to_pubkey: &Pubkey, // must match create_address_with_seed(base, seed, program_id) base: &Pubkey, seed: &str, lamports: u64, @@ -173,7 +175,8 @@ pub fn create_account_with_seed( let account_metas = vec![ AccountMeta::new(*from_pubkey, true), AccountMeta::new(*to_pubkey, false), - ]; + ] + .with_signer(base); Instruction::new( system_program::id(), @@ -233,6 +236,36 @@ pub fn create_address_with_seed( )) } +pub fn create_nonce_account_with_seed( + from_pubkey: &Pubkey, + nonce_pubkey: &Pubkey, + base: &Pubkey, + seed: &str, + authority: &Pubkey, + lamports: u64, +) -> Vec { + vec![ + create_account_with_seed( + from_pubkey, + nonce_pubkey, + base, + seed, + lamports, + NonceState::size() as u64, + &system_program::id(), + ), + Instruction::new( + system_program::id(), + &SystemInstruction::NonceInitialize(*authority), + vec![ + AccountMeta::new(*nonce_pubkey, false), + AccountMeta::new_readonly(recent_blockhashes::id(), false), + AccountMeta::new_readonly(rent::id(), false), + ], + ), + ] +} + pub fn create_nonce_account( from_pubkey: &Pubkey, nonce_pubkey: &Pubkey,