diff --git a/core/src/entry.rs b/core/src/entry.rs index b4986a310a..a7f8d044f8 100644 --- a/core/src/entry.rs +++ b/core/src/entry.rs @@ -596,7 +596,7 @@ mod tests { let one = hash(&zero.as_ref()); let keypair = Keypair::new(); let vote_account = Keypair::new(); - let tx0 = VoteTransaction::new_vote(&vote_account, 1, one, 1); + let tx0 = VoteTransaction::new_vote(vote_account.pubkey(), &vote_account, 1, one, 1); let tx1 = BudgetTransaction::new_timestamp( &keypair, keypair.pubkey(), @@ -644,7 +644,8 @@ mod tests { let next_hash = solana_sdk::hash::hash(&hash.as_ref()); let keypair = Keypair::new(); let vote_account = Keypair::new(); - let tx_small = VoteTransaction::new_vote(&vote_account, 1, next_hash, 2); + let tx_small = + VoteTransaction::new_vote(vote_account.pubkey(), &vote_account, 1, next_hash, 2); let tx_large = BudgetTransaction::new_payment(&keypair, keypair.pubkey(), 1, next_hash, 0); let tx_small_size = tx_small.serialized_size().unwrap() as usize; diff --git a/core/src/fullnode.rs b/core/src/fullnode.rs index c6fdf0292d..dbe25bd9d2 100644 --- a/core/src/fullnode.rs +++ b/core/src/fullnode.rs @@ -80,6 +80,7 @@ impl Fullnode { mut node: Node, keypair: &Arc, ledger_path: &str, + vote_account: Pubkey, voting_keypair: T, entrypoint_info_option: Option<&NodeInfo>, config: &FullnodeConfig, @@ -182,7 +183,7 @@ impl Fullnode { .collect(), }; - let voting_keypair_option = if config.voting_disabled { + let voting_keypair = if config.voting_disabled { None } else { Some(Arc::new(voting_keypair)) @@ -190,7 +191,8 @@ impl Fullnode { // Setup channel for rotation indications let tvu = Tvu::new( - voting_keypair_option, + vote_account, + voting_keypair, &bank_forks, &bank_forks_info, &cluster_info, @@ -344,7 +346,13 @@ pub fn make_active_set_entries( let new_vote_account_entry = next_entry_mut(&mut last_entry_hash, 1, vec![new_vote_account_tx]); // 3) Create vote entry - let vote_tx = VoteTransaction::new_vote(&voting_keypair, slot_to_vote_on, *blockhash, 0); + let vote_tx = VoteTransaction::new_vote( + voting_keypair.pubkey(), + &voting_keypair, + slot_to_vote_on, + *blockhash, + 0, + ); let vote_entry = next_entry_mut(&mut last_entry_hash, 1, vec![vote_tx]); // 4) Create `num_ending_ticks` empty ticks @@ -372,11 +380,13 @@ mod tests { GenesisBlock::new_with_leader(10_000, leader_keypair.pubkey(), 1000); let (validator_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block); + let voting_keypair = Keypair::new(); let validator = Fullnode::new( validator_node, &Arc::new(validator_keypair), &validator_ledger_path, - Keypair::new(), + voting_keypair.pubkey(), + voting_keypair, Some(&leader_node.info), &FullnodeConfig::default(), ); @@ -398,11 +408,13 @@ mod tests { GenesisBlock::new_with_leader(10_000, leader_keypair.pubkey(), 1000); let (validator_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block); ledger_paths.push(validator_ledger_path.clone()); + let voting_keypair = Keypair::new(); Fullnode::new( validator_node, &Arc::new(validator_keypair), &validator_ledger_path, - Keypair::new(), + voting_keypair.pubkey(), + voting_keypair, Some(&leader_node.info), &FullnodeConfig::default(), ) diff --git a/core/src/leader_confirmation_service.rs b/core/src/leader_confirmation_service.rs index 25d85db18a..9aaea59ac5 100644 --- a/core/src/leader_confirmation_service.rs +++ b/core/src/leader_confirmation_service.rs @@ -172,7 +172,8 @@ mod tests { // Get another validator to vote, so we now have 2/3 consensus let voting_keypair = &vote_accounts[7].0; - let vote_tx = VoteTransaction::new_vote(voting_keypair, 7, blockhash, 0); + let vote_tx = + VoteTransaction::new_vote(voting_keypair.pubkey(), voting_keypair, 7, blockhash, 0); bank.process_transaction(&vote_tx).unwrap(); LeaderConfirmationService::compute_confirmation(&bank, &mut last_confirmation_time); diff --git a/core/src/local_cluster.rs b/core/src/local_cluster.rs index 2ac11d5f54..80b09ba537 100644 --- a/core/src/local_cluster.rs +++ b/core/src/local_cluster.rs @@ -52,6 +52,7 @@ impl LocalCluster { leader_node, &leader_keypair, &leader_ledger_path, + voting_keypair.pubkey(), voting_keypair, None, fullnode_config, @@ -87,6 +88,7 @@ impl LocalCluster { validator_node, &validator_keypair, &ledger_path, + voting_keypair.pubkey(), voting_keypair, Some(&leader_node_info), fullnode_config, diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 1d52d39c78..bddfefedfe 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -54,6 +54,7 @@ impl ReplayStage { #[allow(clippy::new_ret_no_self, clippy::too_many_arguments)] pub fn new( my_id: Pubkey, + vote_account: Pubkey, voting_keypair: Option>, blocktree: Arc, bank_forks: &Arc>, @@ -131,6 +132,7 @@ impl ReplayStage { if let Some(ref voting_keypair) = voting_keypair { let keypair = voting_keypair.as_ref(); let vote = VoteTransaction::new_vote( + vote_account, keypair, *latest_slot_vote, parent.last_blockhash(), @@ -387,6 +389,7 @@ mod test { let (exit, poh_recorder, poh_service, _entry_receiver) = create_test_recorder(&bank); let (replay_stage, _slot_full_receiver, ledger_writer_recv) = ReplayStage::new( my_keypair.pubkey(), + voting_keypair.pubkey(), Some(voting_keypair.clone()), blocktree.clone(), &Arc::new(RwLock::new(bank_forks)), @@ -398,7 +401,8 @@ mod test { ); let keypair = voting_keypair.as_ref(); - let vote = VoteTransaction::new_vote(keypair, 0, bank.last_blockhash(), 0); + let vote = + VoteTransaction::new_vote(keypair.pubkey(), keypair, 0, bank.last_blockhash(), 0); cluster_info_me.write().unwrap().push_vote(vote); info!("Send ReplayStage an entry, should see it on the ledger writer receiver"); diff --git a/core/src/storage_stage.rs b/core/src/storage_stage.rs index 4579303dca..230fb45960 100644 --- a/core/src/storage_stage.rs +++ b/core/src/storage_stage.rs @@ -597,7 +597,8 @@ mod tests { } let mut vote_txs: Vec<_> = Vec::new(); let keypair = Keypair::new(); - let vote_tx = VoteTransaction::new_vote(&keypair, 123456, Hash::default(), 1); + let vote_tx = + VoteTransaction::new_vote(keypair.pubkey(), &keypair, 123456, Hash::default(), 1); vote_txs.push(vote_tx); let vote_entries = vec![Entry::new(&Hash::default(), 1, vote_txs)]; storage_entry_sender.send(vote_entries).unwrap(); diff --git a/core/src/thin_client.rs b/core/src/thin_client.rs index 60d0d06a44..dea0dfd376 100644 --- a/core/src/thin_client.rs +++ b/core/src/thin_client.rs @@ -431,6 +431,7 @@ pub fn new_fullnode() -> (Fullnode, NodeInfo, Keypair, String) { node, &node_keypair, &ledger_path, + voting_keypair.pubkey(), voting_keypair, None, &FullnodeConfig::default(), diff --git a/core/src/tvu.rs b/core/src/tvu.rs index 320b533142..4ca8f1141d 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -24,6 +24,7 @@ use crate::retransmit_stage::RetransmitStage; use crate::rpc_subscriptions::RpcSubscriptions; use crate::service::Service; use crate::storage_stage::{StorageStage, StorageState}; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; use std::net::UdpSocket; use std::sync::atomic::AtomicBool; @@ -54,6 +55,7 @@ impl Tvu { /// * `blocktree` - the ledger itself #[allow(clippy::new_ret_no_self, clippy::too_many_arguments)] pub fn new( + vote_account: Pubkey, voting_keypair: Option>, bank_forks: &Arc>, bank_forks_info: &[BankForksInfo], @@ -106,6 +108,7 @@ impl Tvu { let (replay_stage, slot_full_receiver, forward_entry_receiver) = ReplayStage::new( keypair.pubkey(), + vote_account, voting_keypair, blocktree.clone(), &bank_forks, @@ -202,8 +205,10 @@ pub mod tests { .expect("Expected to successfully open ledger"); let bank = bank_forks.working_bank(); let (exit, poh_recorder, poh_service, _entry_receiver) = create_test_recorder(&bank); + let voting_keypair = Keypair::new(); let tvu = Tvu::new( - Some(Arc::new(Keypair::new())), + voting_keypair.pubkey(), + Some(Arc::new(voting_keypair)), &Arc::new(RwLock::new(bank_forks)), &bank_forks_info, &cref1, diff --git a/core/src/voting_keypair.rs b/core/src/voting_keypair.rs index 0baaee9f2b..d91d7295ac 100644 --- a/core/src/voting_keypair.rs +++ b/core/src/voting_keypair.rs @@ -115,7 +115,8 @@ pub mod tests { pub fn push_vote(voting_keypair: &T, bank: &Bank, slot: u64) { let blockhash = bank.last_blockhash(); - let tx = VoteTransaction::new_vote(voting_keypair, slot, blockhash, 0); + let tx = + VoteTransaction::new_vote(voting_keypair.pubkey(), voting_keypair, slot, blockhash, 0); bank.process_transaction(&tx).unwrap(); } diff --git a/fullnode/src/main.rs b/fullnode/src/main.rs index cd99c413f8..d03ffc5565 100644 --- a/fullnode/src/main.rs +++ b/fullnode/src/main.rs @@ -32,11 +32,18 @@ fn main() { .help("File containing an identity (keypair)"), ) .arg( - Arg::with_name("staker_keypair") - .long("staker-keypair") + Arg::with_name("staking_account") + .long("staking-account") + .value_name("PUBKEY_BASE58_STR") + .takes_value(true) + .help("Public key of the staking account, where to send votes"), + ) + .arg( + Arg::with_name("voting_keypair") + .long("voting-keypair") .value_name("PATH") .takes_value(true) - .help("File containing the staker's keypair"), + .help("File containing the authorized voting keypair"), ) .arg( Arg::with_name("init_complete_file") @@ -68,11 +75,10 @@ fn main() { .help("Disable leader rotation"), ) .arg( - Arg::with_name("no_signer") - .long("no-signer") + Arg::with_name("no_voting") + .long("no-voting") .takes_value(false) - .conflicts_with("signer") - .help("Launch node without vote signer"), + .help("Launch node without voting"), ) .arg( Arg::with_name("no_sigverify") @@ -141,7 +147,7 @@ fn main() { } else { Keypair::new() }; - let staker_keypair = if let Some(identity) = matches.value_of("staker_keypair") { + let voting_keypair = if let Some(identity) = matches.value_of("voting_keypair") { read_keypair(identity).unwrap_or_else(|err| { eprintln!("{}: Unable to open keypair file: {}", err, identity); exit(1); @@ -149,11 +155,19 @@ fn main() { } else { Keypair::new() }; + + let staking_account = matches + .value_of("staking_account") + .map_or(voting_keypair.pubkey(), |pubkey| { + pubkey.parse().expect("failed to parse staking_account") + }); + let ledger_path = matches.value_of("ledger").unwrap(); fullnode_config.sigverify_disabled = matches.is_present("no_sigverify"); - let no_signer = matches.is_present("no_signer"); - fullnode_config.voting_disabled = no_signer; + + fullnode_config.voting_disabled = matches.is_present("no_voting"); + let use_only_bootstrap_leader = matches.is_present("no_leader_rotation"); if matches.is_present("enable_rpc_exit") { @@ -227,7 +241,8 @@ fn main() { node, &keypair, ledger_path, - staker_keypair, + staking_account, + voting_keypair, cluster_entrypoint.as_ref(), &fullnode_config, ); diff --git a/multinode-demo/bootstrap-leader.sh b/multinode-demo/bootstrap-leader.sh index 62b62e99e8..0c10b7b244 100755 --- a/multinode-demo/bootstrap-leader.sh +++ b/multinode-demo/bootstrap-leader.sh @@ -25,11 +25,19 @@ fi tune_system -trap 'kill "$pid" && wait "$pid"' INT TERM $solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger verify + +bootstrap_leader_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json +bootstrap_leader_staker_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-staker-id.json +bootstrap_leader_staker_id=$($solana_wallet --keypair "$bootstrap_leader_staker_id_path" address) + +set -x +trap 'kill "$pid" && wait "$pid"' INT TERM ERR $program \ - --identity "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json \ + --identity "$bootstrap_leader_id_path" \ + --voting-keypair "$bootstrap_leader_staker_id_path" \ + --staking-account "$bootstrap_leader_staker_id" \ --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger \ --accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts \ --rpc-port 8899 \ @@ -38,4 +46,7 @@ $program \ > >($bootstrap_leader_logger) 2>&1 & pid=$! oom_score_adj "$pid" 1000 + +setup_fullnode_staking 127.0.0.1 "$bootstrap_leader_id_path" "$bootstrap_leader_staker_id_path" + wait "$pid" diff --git a/multinode-demo/common.sh b/multinode-demo/common.sh index 7806dc47f5..280fed09e7 100644 --- a/multinode-demo/common.sh +++ b/multinode-demo/common.sh @@ -110,6 +110,68 @@ tune_system() { fi } +airdrop() { + declare keypair_file=$1 + declare host=$2 + declare amount=$3 + + declare address + address=$($solana_wallet --keypair "$keypair_file" address) + + # TODO: Until https://github.com/solana-labs/solana/issues/2355 is resolved + # a fullnode needs N lamports as its vote account gets re-created on every + # node restart, costing it lamports + declare retries=5 + + while ! $solana_wallet --keypair "$keypair_file" --host "$host" airdrop "$amount"; do + + # TODO: Consider moving this retry logic into `solana-wallet airdrop` + # itself, currently it does not retry on "Connection refused" errors. + ((retries--)) + if [[ $retries -le 0 ]]; then + echo "Airdrop to $address failed." + return 1 + fi + echo "Airdrop to $address failed. Remaining retries: $retries" + sleep 1 + done + + return 0 +} + +setup_fullnode_staking() { + declare drone_address=$1 + declare fullnode_id_path=$2 + declare staker_id_path=$3 + + declare fullnode_id + fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address) + + declare staker_id + staker_id=$($solana_wallet --keypair "$staker_id_path" address) + + # A fullnode requires 43 lamports to function: + # - one lamport to keep the node identity public key valid. TODO: really?? + # - 42 more for the staker account we fund + airdrop "$fullnode_id_path" "$drone_address" 43 || return $? + + # A little wrong, fund the staking account from the + # to the node. Maybe next time consider doing this the opposite + # way or use an ephemeral account + $solana_wallet --keypair "$fullnode_id_path" \ + create-staking-account "$staker_id" 42 || return $? + + # as the staker, set the node as the delegate and the staker as + # the vote-signer + $solana_wallet --keypair "$staker_id_path" \ + configure-staking-account \ + --delegate-account "$fullnode_id" \ + --authorize-voter "$staker_id" || return $? + + return 0 +} + + # The directory on the bootstrap leader that is rsynced by other full nodes as # they boot (TODO: Eventually this should go away) SOLANA_RSYNC_CONFIG_DIR=$PWD/config diff --git a/multinode-demo/drone.sh b/multinode-demo/drone.sh index 8f43fc49a3..cb3d00e99e 100755 --- a/multinode-demo/drone.sh +++ b/multinode-demo/drone.sh @@ -28,7 +28,7 @@ usage() { set -ex -trap 'kill "$pid" && wait "$pid"' INT TERM +trap 'kill "$pid" && wait "$pid"' INT TERM ERR $solana_drone \ --keypair "$SOLANA_CONFIG_DIR"/mint-id.json \ > >($drone_logger) 2>&1 & diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index 7fae130144..8386b99821 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -130,12 +130,16 @@ if ((!self_setup)); then exit 1 } fullnode_id_path=$SOLANA_CONFIG_DIR/fullnode-id.json + fullnode_staker_id_path=$SOLANA_CONFIG_DIR/fullnode-staker-id.json ledger_config_dir=$SOLANA_CONFIG_DIR/fullnode-ledger accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts else mkdir -p "$SOLANA_CONFIG_DIR" fullnode_id_path=$SOLANA_CONFIG_DIR/fullnode-id-x$self_setup_label.json + fullnode_staker_id_path=$SOLANA_CONFIG_DIR/fullnode-staker-id-x$self_setup_label.json + [[ -f "$fullnode_id_path" ]] || $solana_keygen -o "$fullnode_id_path" + [[ -f "$fullnode_staker_id_path" ]] || $solana_keygen -o "$fullnode_staker_id_path" echo "Finding a port.." # Find an available port in the range 9100-9899 @@ -153,6 +157,10 @@ else accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts-x$self_setup_label fi +fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address) +fullnode_staker_id=$($solana_wallet --keypair "$fullnode_staker_id_path" address) + + [[ -r $fullnode_id_path ]] || { echo "$fullnode_id_path does not exist" exit 1 @@ -179,6 +187,7 @@ rsync_url() { # adds the 'rsync://` prefix to URLs that need it echo "rsync://$url" } + rsync_leader_url=$(rsync_url "$leader") set -ex if [[ ! -d "$ledger_config_dir" ]]; then @@ -189,36 +198,14 @@ if [[ ! -d "$ledger_config_dir" ]]; then } $solana_ledger_tool --ledger "$ledger_config_dir" verify - $solana_wallet --keypair "$fullnode_id_path" address - - # A fullnode requires 3 lamports to function: - # - one lamport to create an instance of the vote_program with - # - one lamport for the transaction fee - # - one lamport to keep the node identity public key valid. - retries=5 - while true; do - # TODO: Until https://github.com/solana-labs/solana/issues/2355 is resolved - # a fullnode needs N lamports as its vote account gets re-created on every - # node restart, costing it lamports - if $solana_wallet --keypair "$fullnode_id_path" --host "${leader_address%:*}" airdrop 1000000; then - break - fi - - # TODO: Consider moving this retry logic into `solana-wallet airdrop` itself, - # currently it does not retry on "Connection refused" errors. - retries=$((retries - 1)) - if [[ $retries -le 0 ]]; then - exit 1 - fi - echo "Airdrop failed. Remaining retries: $retries" - sleep 1 - done fi -trap 'kill "$pid" && wait "$pid"' INT TERM +trap 'kill "$pid" && wait "$pid"' INT TERM ERR $program \ --gossip-port "$gossip_port" \ --identity "$fullnode_id_path" \ + --voting-keypair "$fullnode_staker_id_path" \ + --staking-account "$fullnode_staker_id" \ --network "$leader_address" \ --ledger "$ledger_config_dir" \ --accounts "$accounts_config_dir" \ @@ -227,4 +214,7 @@ $program \ > >($fullnode_logger) 2>&1 & pid=$! oom_score_adj "$pid" 1000 + +setup_fullnode_staking "${leader_address%:*}" "$fullnode_id_path" "$fullnode_staker_id_path" + wait "$pid" diff --git a/multinode-demo/setup.sh b/multinode-demo/setup.sh index 60bed461b2..fdd8a682f6 100755 --- a/multinode-demo/setup.sh +++ b/multinode-demo/setup.sh @@ -76,6 +76,7 @@ if $bootstrap_leader; then set -x $solana_keygen -o "$SOLANA_CONFIG_DIR"/mint-id.json $solana_keygen -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json + $solana_keygen -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-staker-id.json $solana_genesis \ --bootstrap-leader-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json \ --ledger "$SOLANA_RSYNC_CONFIG_DIR"/ledger \ @@ -89,5 +90,6 @@ if $fullnode; then ( set -x $solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-id.json + $solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-staker-id.json ) fi diff --git a/programs/rewards/tests/rewards.rs b/programs/rewards/tests/rewards.rs index 318a526be2..159df52a09 100644 --- a/programs/rewards/tests/rewards.rs +++ b/programs/rewards/tests/rewards.rs @@ -39,9 +39,15 @@ impl<'a> RewardsBank<'a> { self.bank.process_transaction(&tx) } - fn submit_vote(&self, vote_keypair: &Keypair, tick_height: u64) -> Result { + fn submit_vote( + &self, + staking_account: Pubkey, + vote_keypair: &Keypair, + tick_height: u64, + ) -> Result { let blockhash = self.bank.last_blockhash(); - let tx = VoteTransaction::new_vote(vote_keypair, tick_height, blockhash, 0); + let tx = + VoteTransaction::new_vote(staking_account, vote_keypair, tick_height, blockhash, 0); self.bank.process_transaction(&tx)?; self.bank.register_tick(&hash(blockhash.as_ref())); @@ -80,11 +86,17 @@ fn test_redeem_vote_credits_via_bank() { // The validator submits votes to accumulate credits. for i in 0..vote_state::MAX_LOCKOUT_HISTORY { - let vote_state = rewards_bank.submit_vote(&vote_keypair, i as u64).unwrap(); + let vote_state = rewards_bank + .submit_vote(vote_id, &vote_keypair, i as u64) + .unwrap(); assert_eq!(vote_state.credits(), 0); } let vote_state = rewards_bank - .submit_vote(&vote_keypair, vote_state::MAX_LOCKOUT_HISTORY as u64 + 1) + .submit_vote( + vote_id, + &vote_keypair, + vote_state::MAX_LOCKOUT_HISTORY as u64 + 1, + ) .unwrap(); assert_eq!(vote_state.credits(), 1); diff --git a/programs/vote/tests/vote.rs b/programs/vote/tests/vote.rs index c5b1c67c6f..d61af067a1 100644 --- a/programs/vote/tests/vote.rs +++ b/programs/vote/tests/vote.rs @@ -51,9 +51,15 @@ impl<'a> VoteBank<'a> { self.bank.process_transaction(&tx) } - fn submit_vote(&self, vote_keypair: &Keypair, tick_height: u64) -> Result { + fn submit_vote( + &self, + staking_account: Pubkey, + vote_keypair: &Keypair, + tick_height: u64, + ) -> Result { let blockhash = self.bank.last_blockhash(); - let tx = VoteTransaction::new_vote(vote_keypair, tick_height, blockhash, 0); + let tx = + VoteTransaction::new_vote(staking_account, vote_keypair, tick_height, blockhash, 0); self.bank.process_transaction(&tx)?; self.bank.register_tick(&hash(blockhash.as_ref())); @@ -74,7 +80,7 @@ fn test_vote_bank_basic() { .create_vote_account(&from_keypair, vote_id, 100) .unwrap(); - let vote_state = vote_bank.submit_vote(&vote_keypair, 0).unwrap(); + let vote_state = vote_bank.submit_vote(vote_id, &vote_keypair, 0).unwrap(); assert_eq!(vote_state.votes.len(), 1); } diff --git a/programs/vote_api/src/vote_transaction.rs b/programs/vote_api/src/vote_transaction.rs index e317124cbe..82b2d847f3 100644 --- a/programs/vote_api/src/vote_transaction.rs +++ b/programs/vote_api/src/vote_transaction.rs @@ -15,21 +15,22 @@ pub struct VoteTransaction {} impl VoteTransaction { pub fn new_vote( - voting_keypair: &T, + staking_account: Pubkey, + authorized_voter_keypair: &T, slot: u64, recent_blockhash: Hash, fee: u64, ) -> Transaction { let vote = Vote { slot }; TransactionBuilder::new(fee) - .push(VoteInstruction::new_vote(voting_keypair.pubkey(), vote)) - .sign(&[voting_keypair], recent_blockhash) + .push(VoteInstruction::new_vote(staking_account, vote)) + .sign(&[authorized_voter_keypair], recent_blockhash) } /// Fund or create the staking account with lamports pub fn new_account( from_keypair: &Keypair, - voter_id: Pubkey, + staker_id: Pubkey, recent_blockhash: Hash, lamports: u64, fee: u64, @@ -39,12 +40,12 @@ impl VoteTransaction { TransactionBuilder::new(fee) .push(SystemInstruction::new_program_account( from_id, - voter_id, + staker_id, lamports, space, id(), )) - .push(VoteInstruction::new_initialize_account(voter_id)) + .push(VoteInstruction::new_initialize_account(staker_id)) .sign(&[from_keypair], recent_blockhash) } @@ -131,7 +132,8 @@ mod tests { let keypair = Keypair::new(); let slot = 1; let recent_blockhash = Hash::default(); - let transaction = VoteTransaction::new_vote(&keypair, slot, recent_blockhash, 0); + let transaction = + VoteTransaction::new_vote(keypair.pubkey(), &keypair, slot, recent_blockhash, 0); assert_eq!( VoteTransaction::get_votes(&transaction), vec![(keypair.pubkey(), Vote::new(slot), recent_blockhash)] diff --git a/sdk/src/pubkey.rs b/sdk/src/pubkey.rs index e0189c318d..ea95ec955c 100644 --- a/sdk/src/pubkey.rs +++ b/sdk/src/pubkey.rs @@ -2,11 +2,34 @@ use bs58; use generic_array::typenum::U32; use generic_array::GenericArray; use std::fmt; +use std::mem; +use std::str::FromStr; #[repr(C)] #[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Pubkey(GenericArray); +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ParsePubkeyError { + WrongSize, + Invalid, +} + +impl FromStr for Pubkey { + type Err = ParsePubkeyError; + + fn from_str(s: &str) -> Result { + let pubkey_vec = bs58::decode(s) + .into_vec() + .map_err(|_| ParsePubkeyError::Invalid)?; + if pubkey_vec.len() != mem::size_of::() { + Err(ParsePubkeyError::WrongSize) + } else { + Ok(Pubkey::new(&pubkey_vec)) + } + } +} + impl Pubkey { pub fn new(pubkey_vec: &[u8]) -> Self { Pubkey(GenericArray::clone_from_slice(&pubkey_vec)) @@ -30,3 +53,46 @@ impl fmt::Display for Pubkey { write!(f, "{}", bs58::encode(self.0).into_string()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::signature::{Keypair, KeypairUtil}; + use bs58; + + #[test] + fn pubkey_fromstr() { + let pubkey = Keypair::new().pubkey(); + let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string(); + + dbg!(&pubkey_base58_str); + + assert_eq!(pubkey_base58_str.parse::(), Ok(pubkey)); + + pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string()); + assert_eq!( + pubkey_base58_str.parse::(), + Err(ParsePubkeyError::WrongSize) + ); + + pubkey_base58_str.truncate(pubkey_base58_str.len() / 2); + assert_eq!(pubkey_base58_str.parse::(), Ok(pubkey)); + + pubkey_base58_str.truncate(pubkey_base58_str.len() / 2); + assert_eq!( + pubkey_base58_str.parse::(), + Err(ParsePubkeyError::WrongSize) + ); + + let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string(); + assert_eq!(pubkey_base58_str.parse::(), Ok(pubkey)); + + // throw some non-base58 stuff in there + pubkey_base58_str.replace_range(..1, "I"); + assert_eq!( + pubkey_base58_str.parse::(), + Err(ParsePubkeyError::Invalid) + ); + } + +} diff --git a/tests/replicator.rs b/tests/replicator.rs index 450f527cbf..de4687a247 100644 --- a/tests/replicator.rs +++ b/tests/replicator.rs @@ -56,6 +56,7 @@ fn test_replicator_startup_basic() { leader_node, &leader_keypair, &leader_ledger_path, + voting_keypair.pubkey(), voting_keypair, None, &fullnode_config, @@ -83,6 +84,7 @@ fn test_replicator_startup_basic() { validator_node, &validator_keypair, &validator_ledger_path, + voting_keypair.pubkey(), voting_keypair, Some(&leader_info), &fullnode_config, @@ -287,6 +289,7 @@ fn test_replicator_startup_ledger_hang() { leader_node, &leader_keypair, &leader_ledger_path, + voting_keypair.pubkey(), voting_keypair, None, &fullnode_config, @@ -300,6 +303,7 @@ fn test_replicator_startup_ledger_hang() { validator_node, &validator_keypair, &validator_ledger_path, + voting_keypair.pubkey(), voting_keypair, Some(&leader_info), &FullnodeConfig::default(), diff --git a/tests/tvu.rs b/tests/tvu.rs index 373121db2a..7486e83036 100644 --- a/tests/tvu.rs +++ b/tests/tvu.rs @@ -112,6 +112,7 @@ fn test_replay() { let (poh_service_exit, poh_recorder, poh_service, _entry_receiver) = create_test_recorder(&bank); let tvu = Tvu::new( + voting_keypair.pubkey(), Some(Arc::new(voting_keypair)), &Arc::new(RwLock::new(bank_forks)), &bank_forks_info,