浏览代码

plumb staking_account and voting_keypair from multinode-demo to Vote (#3199)

* plumb staking_account and voting_keypair from bash to Vote
Rob Walker 6 年之前
父节点
当前提交
0acdbc0d03

+ 3 - 2
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;

+ 17 - 5
core/src/fullnode.rs

@@ -80,6 +80,7 @@ impl Fullnode {
         mut node: Node,
         keypair: &Arc<Keypair>,
         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(),
                 )

+ 2 - 1
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);

+ 2 - 0
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,

+ 5 - 1
core/src/replay_stage.rs

@@ -54,6 +54,7 @@ impl ReplayStage {
     #[allow(clippy::new_ret_no_self, clippy::too_many_arguments)]
     pub fn new<T>(
         my_id: Pubkey,
+        vote_account: Pubkey,
         voting_keypair: Option<Arc<T>>,
         blocktree: Arc<Blocktree>,
         bank_forks: &Arc<RwLock<BankForks>>,
@@ -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");

+ 2 - 1
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();

+ 1 - 0
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(),

+ 6 - 1
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<T>(
+        vote_account: Pubkey,
         voting_keypair: Option<Arc<T>>,
         bank_forks: &Arc<RwLock<BankForks>>,
         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,

+ 2 - 1
core/src/voting_keypair.rs

@@ -115,7 +115,8 @@ pub mod tests {
 
     pub fn push_vote<T: KeypairUtil>(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();
     }
 

+ 26 - 11
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,
     );

+ 13 - 2
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"

+ 62 - 0
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

+ 1 - 1
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 &

+ 15 - 25
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"

+ 2 - 0
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

+ 16 - 4
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<VoteState> {
+    fn submit_vote(
+        &self,
+        staking_account: Pubkey,
+        vote_keypair: &Keypair,
+        tick_height: u64,
+    ) -> Result<VoteState> {
         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);
 

+ 9 - 3
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<VoteState> {
+    fn submit_vote(
+        &self,
+        staking_account: Pubkey,
+        vote_keypair: &Keypair,
+        tick_height: u64,
+    ) -> Result<VoteState> {
         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);
 }
 

+ 9 - 7
programs/vote_api/src/vote_transaction.rs

@@ -15,21 +15,22 @@ pub struct VoteTransaction {}
 
 impl VoteTransaction {
     pub fn new_vote<T: KeypairUtil>(
-        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)]

+ 66 - 0
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<u8, U32>);
 
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ParsePubkeyError {
+    WrongSize,
+    Invalid,
+}
+
+impl FromStr for Pubkey {
+    type Err = ParsePubkeyError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let pubkey_vec = bs58::decode(s)
+            .into_vec()
+            .map_err(|_| ParsePubkeyError::Invalid)?;
+        if pubkey_vec.len() != mem::size_of::<Pubkey>() {
+            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::<Pubkey>(), Ok(pubkey));
+
+        pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
+        assert_eq!(
+            pubkey_base58_str.parse::<Pubkey>(),
+            Err(ParsePubkeyError::WrongSize)
+        );
+
+        pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
+        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
+
+        pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
+        assert_eq!(
+            pubkey_base58_str.parse::<Pubkey>(),
+            Err(ParsePubkeyError::WrongSize)
+        );
+
+        let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
+        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
+
+        // throw some non-base58 stuff in there
+        pubkey_base58_str.replace_range(..1, "I");
+        assert_eq!(
+            pubkey_base58_str.parse::<Pubkey>(),
+            Err(ParsePubkeyError::Invalid)
+        );
+    }
+
+}

+ 4 - 0
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(),

+ 1 - 0
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,