diff --git a/core/src/entry.rs b/core/src/entry.rs index 718b869db8..93edca4e47 100644 --- a/core/src/entry.rs +++ b/core/src/entry.rs @@ -438,8 +438,6 @@ mod tests { use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_transaction; - use solana_vote_api::vote_instruction; - use solana_vote_api::vote_state::Vote; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction { @@ -460,12 +458,6 @@ mod tests { Transaction::new_signed_instructions(&[keypair], vec![ix], hash) } - fn create_sample_vote(keypair: &Keypair, hash: Hash) -> Transaction { - let pubkey = keypair.pubkey(); - let ix = vote_instruction::vote(&pubkey, vec![Vote::new(1)]); - Transaction::new_signed_instructions(&[keypair], vec![ix], hash) - } - #[test] fn test_entry_verify() { let zero = Hash::default(); @@ -647,8 +639,7 @@ mod tests { let hash = Hash::default(); let next_hash = solana_sdk::hash::hash(&hash.as_ref()); let keypair = Keypair::new(); - let vote_account = Keypair::new(); - let tx_small = create_sample_vote(&vote_account, next_hash); + let tx_small = create_sample_timestamp(&keypair, next_hash); let tx_large = create_sample_payment(&keypair, next_hash); let tx_small_size = serialized_size(&tx_small).unwrap() as usize; diff --git a/core/src/fullnode.rs b/core/src/fullnode.rs index bb3bb3c2e7..b4e5093749 100644 --- a/core/src/fullnode.rs +++ b/core/src/fullnode.rs @@ -71,19 +71,16 @@ pub struct Fullnode { } impl Fullnode { - pub fn new( + pub fn new( mut node: Node, keypair: &Arc, ledger_path: &str, vote_account: &Pubkey, - voting_keypair: &Arc, + voting_keypair: &Arc, storage_keypair: &Arc, entrypoint_info_option: Option<&ContactInfo>, config: &FullnodeConfig, - ) -> Self - where - T: 'static + KeypairUtil + Sync + Send, - { + ) -> Self { info!("creating bank..."); let id = keypair.pubkey(); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index fa6893612b..bc4bf48d8f 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -95,7 +95,6 @@ impl ReplayStage { let bank_forks = bank_forks.clone(); let poh_recorder = poh_recorder.clone(); let my_id = *my_id; - let vote_account = *vote_account; let mut ticks_per_slot = 0; let mut locktower = Locktower::new_from_forks(&bank_forks.read().unwrap(), &my_id); if let Some(root) = locktower.root() { @@ -105,6 +104,7 @@ impl ReplayStage { } // Start the replay stage loop let leader_schedule_cache = leader_schedule_cache.clone(); + let vote_account = *vote_account; let voting_keypair = voting_keypair.cloned(); let t_replay = Builder::new() .name("solana-replay-stage".to_string()) @@ -152,8 +152,8 @@ impl ReplayStage { &bank_forks, &mut locktower, &mut progress, - &voting_keypair, &vote_account, + &voting_keypair, &cluster_info, &blocktree, &leader_schedule_cache, @@ -297,8 +297,8 @@ impl ReplayStage { bank_forks: &Arc>, locktower: &mut Locktower, progress: &mut HashMap, + vote_account: &Pubkey, voting_keypair: &Option>, - vote_account_pubkey: &Pubkey, cluster_info: &Arc>, blocktree: &Arc, leader_schedule_cache: &Arc, @@ -322,13 +322,20 @@ impl ReplayStage { } locktower.update_epoch(&bank); if let Some(ref voting_keypair) = voting_keypair { + let node_keypair = cluster_info.read().unwrap().keypair.clone(); + // Send our last few votes along with the new one - let vote_ix = vote_instruction::vote(vote_account_pubkey, locktower.recent_votes()); - let vote_tx = Transaction::new_signed_instructions( - &[voting_keypair.as_ref()], - vec![vote_ix], - bank.last_blockhash(), + let vote_ix = vote_instruction::vote( + &node_keypair.pubkey(), + &vote_account, + &voting_keypair.pubkey(), + locktower.recent_votes(), ); + + let mut vote_tx = Transaction::new_unsigned_instructions(vec![vote_ix]); + let blockhash = bank.last_blockhash(); + vote_tx.partial_sign(&[node_keypair.as_ref()], blockhash); + vote_tx.partial_sign(&[voting_keypair.as_ref()], blockhash); cluster_info.write().unwrap().push_vote(vote_tx); } Ok(()) diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index 70e905a54b..6b14710232 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -25,8 +25,16 @@ pub enum VoteInstruction { Vote(Vec), } -fn initialize_account(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Instruction { - let account_metas = vec![AccountMeta::new(*vote_id, false)]; +fn initialize_account( + from_id: &Pubkey, + vote_id: &Pubkey, + node_id: &Pubkey, + commission: u32, +) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*from_id, true), + AccountMeta::new(*vote_id, false), + ]; Instruction::new( id(), &VoteInstruction::InitializeAccount(*node_id, commission), @@ -42,22 +50,51 @@ pub fn create_account( lamports: u64, ) -> Vec { let space = VoteState::size_of() as u64; - let create_ix = system_instruction::create_account(&from_id, vote_id, lamports, space, &id()); - let init_ix = initialize_account(vote_id, node_id, commission); + let create_ix = system_instruction::create_account(from_id, vote_id, lamports, space, &id()); + let init_ix = initialize_account(from_id, vote_id, node_id, commission); vec![create_ix, init_ix] } -pub fn authorize_voter(vote_id: &Pubkey, authorized_voter_id: &Pubkey) -> Instruction { - let account_metas = vec![AccountMeta::new(*vote_id, true)]; +fn metas_for_authorized_signer( + from_id: &Pubkey, + vote_id: &Pubkey, + authorized_voter_id: &Pubkey, // currently authorized +) -> Vec { + let mut account_metas = vec![AccountMeta::new(*from_id, true)]; // sender + + let is_own_signer = authorized_voter_id == vote_id; + + account_metas.push(AccountMeta::new(*vote_id, is_own_signer)); // vote account + + if !is_own_signer { + account_metas.push(AccountMeta::new(*authorized_voter_id, true)) // signer + } + account_metas +} + +pub fn authorize_voter( + from_id: &Pubkey, + vote_id: &Pubkey, + authorized_voter_id: &Pubkey, // currently authorized + new_authorized_voter_id: &Pubkey, +) -> Instruction { + let account_metas = metas_for_authorized_signer(from_id, vote_id, authorized_voter_id); + Instruction::new( id(), - &VoteInstruction::AuthorizeVoter(*authorized_voter_id), + &VoteInstruction::AuthorizeVoter(*new_authorized_voter_id), account_metas, ) } -pub fn vote(vote_id: &Pubkey, recent_votes: Vec) -> Instruction { - let account_metas = vec![AccountMeta::new(*vote_id, true)]; +pub fn vote( + from_id: &Pubkey, + vote_id: &Pubkey, + authorized_voter_id: &Pubkey, + recent_votes: Vec, +) -> Instruction { + let account_metas = metas_for_authorized_signer(from_id, vote_id, authorized_voter_id); + Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas) } @@ -72,27 +109,25 @@ pub fn process_instruction( trace!("process_instruction: {:?}", data); trace!("keyed_accounts: {:?}", keyed_accounts); - if keyed_accounts.is_empty() { + if keyed_accounts.len() < 2 { Err(InstructionError::InvalidInstructionData)?; } + // 0th index is the guy who paid for the transaction + let (me, other_signers) = &mut keyed_accounts.split_at_mut(2); + let me = &mut me[1]; + + // TODO: data-driven unpack and dispatch of KeyedAccounts match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { VoteInstruction::InitializeAccount(node_id, commission) => { - let vote_account = &mut keyed_accounts[0]; - vote_state::initialize_account(vote_account, &node_id, commission) + vote_state::initialize_account(me, &node_id, commission) } VoteInstruction::AuthorizeVoter(voter_id) => { - let (vote_account, other_signers) = keyed_accounts.split_at_mut(1); - let vote_account = &mut vote_account[0]; - - vote_state::authorize_voter(vote_account, other_signers, &voter_id) + vote_state::authorize_voter(me, other_signers, &voter_id) } VoteInstruction::Vote(votes) => { datapoint_warn!("vote-native", ("count", 1, i64)); - let (vote_account, other_signers) = keyed_accounts.split_at_mut(1); - let vote_account = &mut vote_account[0]; - - vote_state::process_votes(vote_account, other_signers, &votes) + vote_state::process_votes(me, other_signers, &votes) } } } @@ -102,7 +137,7 @@ mod tests { use super::*; use solana_sdk::account::Account; - // these are for 100% coverage + // these are for 100% coverage in this file #[test] fn test_vote_process_instruction_decode_bail() { assert_eq!( @@ -146,11 +181,21 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&vote(&Pubkey::default(), vec![Vote::default()])), + process_instruction(&vote( + &Pubkey::default(), + &Pubkey::default(), + &Pubkey::default(), + vec![Vote::default()] + )), Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&authorize_voter(&Pubkey::default(), &Pubkey::default())), + process_instruction(&authorize_voter( + &Pubkey::default(), + &Pubkey::default(), + &Pubkey::default(), + &Pubkey::default(), + )), Err(InstructionError::InvalidAccountData), ); } diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index de029c4c59..d3b9217d7f 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -44,7 +44,7 @@ pub enum WalletCommand { Balance(Pubkey), Cancel(Pubkey), Confirm(Signature), - AuthorizeVoter(Pubkey), + AuthorizeVoter(Pubkey, Keypair, Pubkey), CreateVoteAccount(Pubkey, Pubkey, u32, u64), ShowVoteAccount(Pubkey), CreateStakeAccount(Pubkey, u64), @@ -193,8 +193,16 @@ pub fn parse_command( )) } ("authorize-voter", Some(matches)) => { - let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap(); - Ok(WalletCommand::AuthorizeVoter(authorized_voter_id)) + let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap(); + let authorized_voter_keypair = + keypair_of(matches, "authorized_voter_keypair_file").unwrap(); + let new_authorized_voter_id = pubkey_of(matches, "new_authorized_voter_id").unwrap(); + + Ok(WalletCommand::AuthorizeVoter( + voting_account_id, + authorized_voter_keypair, + new_authorized_voter_id, + )) } ("show-vote-account", Some(matches)) => { let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap(); @@ -392,15 +400,23 @@ fn process_create_vote_account( fn process_authorize_voter( rpc_client: &RpcClient, config: &WalletConfig, - authorized_voter_id: &Pubkey, + voting_account_id: &Pubkey, + authorized_voter_keypair: &Keypair, + new_authorized_voter_id: &Pubkey, ) -> ProcessResult { let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let ixs = vec![vote_instruction::authorize_voter( - &config.keypair.pubkey(), - authorized_voter_id, + &config.keypair.pubkey(), // from + voting_account_id, // vote account to update + &authorized_voter_keypair.pubkey(), // current authorized voter (often the vote account itself) + new_authorized_voter_id, // new vote signer )]; - let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); + let mut tx = Transaction::new_signed_instructions( + &[&config.keypair, &authorized_voter_keypair], + ixs, + recent_blockhash, + ); let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; Ok(signature_str.to_string()) } @@ -776,9 +792,17 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { ) } // Configure staking account already created - WalletCommand::AuthorizeVoter(authorized_voter_id) => { - process_authorize_voter(&rpc_client, config, &authorized_voter_id) - } + WalletCommand::AuthorizeVoter( + voting_account_id, + authorized_voter_keypair, + new_authorized_voter_id, + ) => process_authorize_voter( + &rpc_client, + config, + &voting_account_id, + &authorized_voter_keypair, + &new_authorized_voter_id, + ), // Show a vote account WalletCommand::ShowVoteAccount(voting_account_id) => { process_show_vote_account(&rpc_client, config, &voting_account_id) @@ -996,15 +1020,32 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' ) .subcommand( SubCommand::with_name("authorize-voter") - .about("Authorize a different voter for this vote account") + .about("Authorize a new vote signing keypair for the given vote account") .arg( - Arg::with_name("authorized_voter_id") + Arg::with_name("voting_account_id") .index(1) .value_name("PUBKEY") .takes_value(true) .required(true) .validator(is_pubkey) - .help("Vote signer to authorize"), + .help("Vote account in which to set the authorized voter"), + ) + .arg( + Arg::with_name("authorized_voter_keypair_file") + .index(2) + .value_name("KEYPAIR_FILE") + .takes_value(true) + .required(true) + .help("Keypair file for the currently authorized vote signer"), + ) + .arg( + Arg::with_name("new_authorized_voter_id") + .index(3) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("New vote signer to authorize"), ), ) .subcommand( @@ -1313,13 +1354,20 @@ mod tests { assert!(parse_command(&pubkey, &test_bad_signature).is_err()); // Test AuthorizeVoter Subcommand - let test_authorize_voter = - test_commands - .clone() - .get_matches_from(vec!["test", "authorize-voter", &pubkey_string]); + let keypair_file = make_tmp_path("keypair_file"); + gen_keypair_file(&keypair_file).unwrap(); + let keypair = read_keypair(&keypair_file).unwrap(); + + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "authorize-voter", + &pubkey_string, + &keypair_file, + &pubkey_string, + ]); assert_eq!( parse_command(&pubkey, &test_authorize_voter).unwrap(), - WalletCommand::AuthorizeVoter(pubkey) + WalletCommand::AuthorizeVoter(pubkey, keypair, pubkey) ); // Test CreateVoteAccount SubCommand @@ -1545,7 +1593,8 @@ mod tests { let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); - config.command = WalletCommand::AuthorizeVoter(bob_pubkey); + let bob_keypair = Keypair::new(); + config.command = WalletCommand::AuthorizeVoter(bob_pubkey, bob_keypair, bob_pubkey); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -1663,7 +1712,7 @@ mod tests { config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10); assert!(process_command(&config).is_err()); - config.command = WalletCommand::AuthorizeVoter(bob_pubkey); + config.command = WalletCommand::AuthorizeVoter(bob_pubkey, Keypair::new(), bob_pubkey); assert!(process_command(&config).is_err()); config.command = WalletCommand::GetTransactionCount;