vote_api cleanup (#3710)

* vote_api cleanup

* fixups

* fixup

* remove unused code

* revert removal of serialize and deserialize

* ...

* increase coverage, bootstrap staking

* Sagar's STAKE to my VOTE
This commit is contained in:
Rob Walker 2019-04-10 17:52:47 -07:00 committed by GitHub
parent 1b5845ac3e
commit f1e7237c09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 638 additions and 667 deletions

View File

@ -451,7 +451,8 @@ mod tests {
use solana_sdk::hash::hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use solana_vote_api::vote_instruction::{self, Vote};
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 {

View File

@ -28,7 +28,8 @@ use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use solana_sdk::timing::timestamp;
use solana_sdk::transaction::Transaction;
use solana_vote_api::vote_instruction::{self, Vote};
use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::Vote;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver};
@ -334,6 +335,8 @@ pub fn make_active_set_entries(
let new_vote_account_ixs = vote_instruction::create_account(
&active_keypair.pubkey(),
&vote_account_id,
&active_keypair.pubkey(),
0,
stake.saturating_sub(2),
);
let new_vote_account_tx = Transaction::new_signed_instructions(

View File

@ -100,7 +100,7 @@ mod tests {
use crate::blocktree::get_tmp_ledger_path;
use crate::blocktree::tests::make_slot_entries;
use crate::staking_utils;
use crate::voting_keypair::tests::new_vote_account_with_delegate;
use crate::voting_keypair::tests::new_vote_account;
use solana_sdk::genesis_block::{GenesisBlock, BOOTSTRAP_LEADER_LAMPORTS};
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::sync::Arc;
@ -224,7 +224,7 @@ mod tests {
// Create new vote account
let new_voting_keypair = Keypair::new();
new_vote_account_with_delegate(
new_vote_account(
&mint_keypair,
&new_voting_keypair,
&delegate_id,

View File

@ -334,13 +334,15 @@ impl LocalCluster {
amount: u64,
) -> Result<()> {
let vote_account_pubkey = vote_account.pubkey();
let delegate_id = from_account.pubkey();
let node_id = from_account.pubkey();
// Create the vote account if necessary
if client.poll_get_balance(&vote_account_pubkey).unwrap_or(0) == 0 {
// 1) Create vote account
let instructions = vote_instruction::create_account(
&from_account.pubkey(),
&vote_account_pubkey,
&node_id,
0,
amount,
);
let mut transaction = Transaction::new_signed_instructions(
@ -355,27 +357,12 @@ impl LocalCluster {
client
.wait_for_balance(&vote_account_pubkey, Some(amount))
.expect("get balance");
// 2) Set delegate for new vote account
let vote_instruction =
vote_instruction::delegate_stake(&vote_account_pubkey, &delegate_id);
let mut transaction = Transaction::new_signed_instructions(
&[vote_account],
vec![vote_instruction],
client.get_recent_blockhash().unwrap(),
);
client
.retry_transfer(&vote_account, &mut transaction, 5)
.expect("client transfer 2");
}
info!("Checking for vote account registration");
let vote_account_user_data = client.get_account_data(&vote_account_pubkey);
if let Ok(Some(vote_account_user_data)) = vote_account_user_data {
if let Ok(vote_state) = VoteState::deserialize(&vote_account_user_data) {
if vote_state.delegate_id == delegate_id {
if vote_state.node_id == node_id {
return Ok(());
}
}

View File

@ -5,8 +5,7 @@ use solana_metrics::influxdb;
use solana_runtime::bank::Bank;
use solana_sdk::account::Account;
use solana_sdk::pubkey::Pubkey;
use solana_vote_api::vote_instruction::Vote;
use solana_vote_api::vote_state::{Lockout, VoteState, MAX_LOCKOUT_HISTORY};
use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY};
use std::sync::Arc;
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
@ -117,7 +116,7 @@ impl Locktower {
.expect("bank should always have valid VoteState data");
if key == self.epoch_stakes.delegate_id
|| vote_state.delegate_id == self.epoch_stakes.delegate_id
|| vote_state.node_id == self.epoch_stakes.delegate_id
{
debug!("vote state {:?}", vote_state);
debug!(
@ -141,7 +140,7 @@ impl Locktower {
);
}
let start_root = vote_state.root_slot;
vote_state.process_vote(Vote { slot: bank_slot });
vote_state.process_vote(&Vote { slot: bank_slot });
for vote in &vote_state.votes {
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
}
@ -237,7 +236,7 @@ impl Locktower {
pub fn record_vote(&mut self, slot: u64) -> Option<u64> {
let root_slot = self.lockouts.root_slot;
self.lockouts.process_vote(Vote { slot });
self.lockouts.process_vote(&Vote { slot });
solana_metrics::submit(
influxdb::Point::new("counter-locktower-vote")
.add_field("latest", influxdb::Value::Integer(slot as i64))
@ -281,7 +280,7 @@ impl Locktower {
pub fn is_locked_out(&self, slot: u64, descendants: &HashMap<u64, HashSet<u64>>) -> bool {
let mut lockouts = self.lockouts.clone();
lockouts.process_vote(Vote { slot });
lockouts.process_vote(&Vote { slot });
for vote in &lockouts.votes {
if vote.slot == slot {
continue;
@ -303,7 +302,7 @@ impl Locktower {
stake_lockouts: &HashMap<u64, StakeLockout>,
) -> bool {
let mut lockouts = self.lockouts.clone();
lockouts.process_vote(Vote { slot });
lockouts.process_vote(&Vote { slot });
let vote = lockouts.nth_recent_vote(self.threshold_depth);
if let Some(vote) = vote {
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
@ -398,7 +397,7 @@ mod test {
account.lamports = *lamports;
let mut vote_state = VoteState::default();
for slot in *votes {
vote_state.process_vote(Vote { slot: *slot });
vote_state.process_vote(&Vote { slot: *slot });
}
vote_state
.serialize(&mut account.data)

View File

@ -21,7 +21,8 @@ use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::KeypairUtil;
use solana_sdk::timing::{self, duration_as_ms};
use solana_sdk::transaction::Transaction;
use solana_vote_api::vote_instruction::{self, Vote};
use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::Vote;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
use std::sync::{Arc, Mutex, RwLock};

View File

@ -76,7 +76,7 @@ pub fn node_staked_accounts_at_epoch(
fn filter_no_delegate(account_id: &Pubkey, account: &Account) -> bool {
VoteState::deserialize(&account.data)
.map(|vote_state| vote_state.delegate_id != *account_id)
.map(|vote_state| vote_state.node_id != *account_id)
.unwrap_or(false)
}
@ -104,7 +104,7 @@ fn to_delegated_stakes(
) -> HashMap<Pubkey, u64> {
let mut map: HashMap<Pubkey, u64> = HashMap::new();
node_staked_accounts.for_each(|(stake, state)| {
let delegate = &state.delegate_id;
let delegate = &state.node_id;
map.entry(*delegate)
.and_modify(|s| *s += stake)
.or_insert(stake);
@ -200,7 +200,7 @@ mod tests {
// Make a mint vote account. Because the mint has nonzero stake, this
// should show up in the active set
voting_keypair_tests::new_vote_account_with_delegate(
voting_keypair_tests::new_vote_account(
&mint_keypair,
&bank_voter,
&mint_keypair.pubkey(),
@ -283,11 +283,11 @@ mod tests {
// Delegate 1 has stake of 3
for i in 0..3 {
stakes.push((i, VoteState::new(&delegate1)));
stakes.push((i, VoteState::new(&Pubkey::new_rand(), &delegate1, 0)));
}
// Delegate 1 has stake of 5
stakes.push((5, VoteState::new(&delegate2)));
stakes.push((5, VoteState::new(&Pubkey::new_rand(), &delegate2, 0)));
let result = to_delegated_stakes(stakes.into_iter());
assert_eq!(result.len(), 2);

View File

@ -110,7 +110,8 @@ pub mod tests {
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::transaction::Transaction;
use solana_vote_api::vote_instruction::{self, Vote};
use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::Vote;
fn process_instructions<T: KeypairUtil>(bank: &Bank, keypairs: &[&T], ixs: Vec<Instruction>) {
let blockhash = bank.last_blockhash();
@ -119,27 +120,21 @@ pub mod tests {
}
pub fn new_vote_account(
from_keypair: &Keypair,
voting_pubkey: &Pubkey,
bank: &Bank,
lamports: u64,
) {
let ixs = vote_instruction::create_account(&from_keypair.pubkey(), voting_pubkey, lamports);
process_instructions(bank, &[from_keypair], ixs);
}
pub fn new_vote_account_with_delegate(
from_keypair: &Keypair,
voting_keypair: &Keypair,
delegate: &Pubkey,
node_id: &Pubkey,
bank: &Bank,
lamports: u64,
) {
let voting_pubkey = voting_keypair.pubkey();
let mut ixs =
vote_instruction::create_account(&from_keypair.pubkey(), &voting_pubkey, lamports);
ixs.push(vote_instruction::delegate_stake(&voting_pubkey, delegate));
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
let ixs = vote_instruction::create_account(
&from_keypair.pubkey(),
&voting_pubkey,
node_id,
0,
lamports,
);
process_instructions(bank, &[from_keypair], ixs);
}
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot: u64) {
@ -150,13 +145,19 @@ pub mod tests {
pub fn new_vote_account_with_vote<T: KeypairUtil>(
from_keypair: &T,
voting_keypair: &T,
node_id: &Pubkey,
bank: &Bank,
lamports: u64,
slot: u64,
) {
let voting_pubkey = voting_keypair.pubkey();
let mut ixs =
vote_instruction::create_account(&from_keypair.pubkey(), &voting_pubkey, lamports);
let mut ixs = vote_instruction::create_account(
&from_keypair.pubkey(),
&voting_pubkey,
node_id,
0,
lamports,
);
ixs.push(vote_instruction::vote(&voting_pubkey, Vote::new(slot)));
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
}

View File

@ -32,11 +32,11 @@ fn main() {
.help("File containing an identity (keypair)"),
)
.arg(
Arg::with_name("staking_account")
.long("staking-account")
Arg::with_name("vote_account")
.long("vote-account")
.value_name("PUBKEY_BASE58_STR")
.takes_value(true)
.help("Public key of the staking account, where to send votes"),
.help("Public key of the vote account, where to send votes"),
)
.arg(
Arg::with_name("voting_keypair")

View File

@ -54,10 +54,10 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.help("Path to file containing keys of the mint"),
)
.arg(
Arg::with_name("bootstrap_stake_keypair_file")
Arg::with_name("bootstrap_vote_keypair_file")
.short("s")
.long("bootstrap-stake-keypair")
.value_name("BOOTSTRAP STAKE KEYPAIR")
.long("bootstrap-vote-keypair")
.value_name("BOOTSTRAP VOTE KEYPAIR")
.takes_value(true)
.required(true)
.help("Path to file containing the bootstrap leader's staking keypair"),
@ -65,13 +65,13 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.get_matches();
let bootstrap_leader_keypair_file = matches.value_of("bootstrap_leader_keypair_file").unwrap();
let bootstrap_stake_keypair_file = matches.value_of("bootstrap_stake_keypair_file").unwrap();
let bootstrap_vote_keypair_file = matches.value_of("bootstrap_vote_keypair_file").unwrap();
let ledger_path = matches.value_of("ledger_path").unwrap();
let mint_keypair_file = matches.value_of("mint_keypair_file").unwrap();
let lamports = value_t_or_exit!(matches, "lamports", u64);
let bootstrap_leader_keypair = read_keypair(bootstrap_leader_keypair_file)?;
let bootstrap_stake_keypair = read_keypair(bootstrap_stake_keypair_file)?;
let bootstrap_vote_keypair = read_keypair(bootstrap_vote_keypair_file)?;
let mint_keypair = read_keypair(mint_keypair_file)?;
let (mut genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(
@ -80,7 +80,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
BOOTSTRAP_LEADER_LAMPORTS,
);
genesis_block.mint_id = mint_keypair.pubkey();
genesis_block.bootstrap_leader_vote_account_id = bootstrap_stake_keypair.pubkey();
genesis_block.bootstrap_leader_vote_account_id = bootstrap_vote_keypair.pubkey();
genesis_block
.native_instruction_processors
.extend_from_slice(&[

View File

@ -64,14 +64,14 @@ tune_system
$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)
bootstrap_leader_vote_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json
bootstrap_leader_vote_id=$($solana_wallet --keypair "$bootstrap_leader_vote_id_path" address)
trap 'kill "$pid" && wait "$pid"' INT TERM ERR
$program \
--identity "$bootstrap_leader_id_path" \
--voting-keypair "$bootstrap_leader_staker_id_path" \
--staking-account "$bootstrap_leader_staker_id" \
--voting-keypair "$bootstrap_leader_vote_id_path" \
--vote-account "$bootstrap_leader_vote_id" \
--ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger \
--accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts \
--rpc-port 8899 \

View File

@ -142,42 +142,32 @@ airdrop() {
return 0
}
setup_fullnode_staking() {
setup_vote_account() {
declare drone_address=$1
declare fullnode_id_path=$2
declare staker_id_path=$3
declare node_id_path=$2
declare vote_id_path=$3
declare fullnode_id
fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address)
declare node_id
node_id=$($solana_wallet --keypair "$node_id_path" address)
declare staker_id
staker_id=$($solana_wallet --keypair "$staker_id_path" address)
declare vote_id
vote_id=$($solana_wallet --keypair "$vote_id_path" address)
if [[ -f "$staker_id_path".configured ]]; then
echo "Staking account has already been configured"
if [[ -f "$vote_id_path".configured ]]; then
echo "Vote account has already been configured"
return 0
fi
# 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 $?
# - 42 more for the vote account we fund
airdrop "$node_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" --host "$drone_address" \
create-staking-account "$staker_id" 42 || return $?
# Fund the vote account from the node, with the node as the node_id
$solana_wallet --keypair "$node_id_path" --host "$drone_address" \
create-vote-account "$vote_id" "$node_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" --host "$drone_address" \
configure-staking-account \
--delegate-account "$fullnode_id" \
--authorize-voter "$staker_id" || return $?
touch "$staker_id_path".configured
touch "$vote_id_path".configured
return 0
}

View File

@ -114,16 +114,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
fullnode_vote_id_path=$SOLANA_CONFIG_DIR/fullnode-vote-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
fullnode_vote_id_path=$SOLANA_CONFIG_DIR/fullnode-vote-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"
[[ -f "$fullnode_vote_id_path" ]] || $solana_keygen -o "$fullnode_vote_id_path"
echo "Finding a port.."
# Find an available port in the range 9100-9899
@ -141,8 +141,7 @@ 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)
fullnode_vote_id=$($solana_wallet --keypair "$fullnode_vote_id_path" address)
[[ -r $fullnode_id_path ]] || {
@ -192,8 +191,8 @@ while true; do
$program \
--gossip-port "$gossip_port" \
--identity "$fullnode_id_path" \
--voting-keypair "$fullnode_staker_id_path" \
--staking-account "$fullnode_staker_id" \
--voting-keypair "$fullnode_vote_id_path" \
--vote-account "$fullnode_vote_id" \
--network "$leader_address" \
--ledger "$ledger_config_dir" \
--accounts "$accounts_config_dir" \
@ -204,7 +203,7 @@ while true; do
oom_score_adj "$pid" 1000
if ((setup_stakes)); then
setup_fullnode_staking "${leader_address%:*}" "$fullnode_id_path" "$fullnode_staker_id_path"
setup_vote_account "${leader_address%:*}" "$fullnode_id_path" "$fullnode_vote_id_path"
fi
set +x

View File

@ -76,10 +76,10 @@ 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_keygen -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json
$solana_genesis \
--bootstrap-leader-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json \
--bootstrap-stake-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-staker-id.json \
--bootstrap-vote-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json \
--ledger "$SOLANA_RSYNC_CONFIG_DIR"/ledger \
--mint "$SOLANA_CONFIG_DIR"/mint-id.json \
--lamports "$lamports"
@ -91,6 +91,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
$solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-vote-id.json
)
fi

View File

@ -153,20 +153,21 @@ mod tests {
use super::*;
use crate::id;
use solana_sdk::account::Account;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_vote_api::vote_instruction::Vote;
use solana_vote_api::vote_state::create_vote_account;
use solana_vote_api::vote_state::{self, Vote};
#[test]
fn test_stake_delegate_stake() {
let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default();
for i in 0..1000 {
vote_state.process_vote(Vote::new(i));
vote_state.process_vote(&Vote::new(i));
}
let vote_pubkey = vote_keypair.pubkey();
let mut vote_account = create_vote_account(100);
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&vote_state).unwrap();
@ -206,7 +207,7 @@ mod tests {
// put a credit in the vote_state
while vote_state.credits() == 0 {
vote_state.process_vote(Vote::new(vote_i));
vote_state.process_vote(&Vote::new(vote_i));
vote_i += 1;
}
// this guy can't collect now, not enough stake to get paid on 1 credit
@ -225,7 +226,7 @@ mod tests {
// put more credit in the vote_state
while vote_state.credits() < 10 {
vote_state.process_vote(Vote::new(vote_i));
vote_state.process_vote(&Vote::new(vote_i));
vote_i += 1;
}
vote_state.commission = 0;
@ -252,11 +253,12 @@ mod tests {
let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default();
for i in 0..1000 {
vote_state.process_vote(Vote::new(i));
vote_state.process_vote(&Vote::new(i));
}
let vote_pubkey = vote_keypair.pubkey();
let mut vote_account = create_vote_account(100);
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&vote_state).unwrap();
@ -294,7 +296,7 @@ mod tests {
.is_ok());
// move the vote account forward
vote_state.process_vote(Vote::new(1000));
vote_state.process_vote(&Vote::new(1000));
vote_keyed_account.set_state(&vote_state).unwrap();
// now, no lamports in the pool!
@ -322,11 +324,12 @@ mod tests {
let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default();
for i in 0..1000 {
vote_state.process_vote(Vote::new(i));
vote_state.process_vote(&Vote::new(i));
}
let vote_pubkey = vote_keypair.pubkey();
let mut vote_account = create_vote_account(100);
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&vote_state).unwrap();
@ -349,7 +352,7 @@ mod tests {
let mut vote_state = VoteState::default();
for i in 0..100 {
// go back in time, previous state had 1000 votes
vote_state.process_vote(Vote::new(i));
vote_state.process_vote(&Vote::new(i));
}
vote_keyed_account.set_state(&vote_state).unwrap();
// voter credits lower than stake_delegate credits... TODO: is this an error?
@ -361,7 +364,8 @@ mod tests {
let vote1_keypair = Keypair::new();
let vote1_pubkey = vote1_keypair.pubkey();
let mut vote1_account = create_vote_account(100);
let mut vote1_account =
vote_state::create_account(&vote1_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote1_keyed_account = KeyedAccount::new(&vote1_pubkey, false, &mut vote1_account);
vote1_keyed_account.set_state(&vote_state).unwrap();

View File

@ -1,5 +1,4 @@
pub mod vote_instruction;
pub mod vote_processor;
pub mod vote_state;
use solana_sdk::pubkey::Pubkey;

View File

@ -1,68 +1,50 @@
//! Vote program
//! Receive and processes votes from validators
use crate::id;
use crate::vote_state::VoteState;
use crate::vote_state::{self, Vote, VoteState};
use bincode::deserialize;
use log::*;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::account::KeyedAccount;
use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::system_instruction;
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote {
// TODO: add signature of the state here as well
/// A vote for height slot
pub slot: u64,
}
impl Vote {
pub fn new(slot: u64) -> Self {
Self { slot }
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum VoteInstruction {
/// Initialize the VoteState for this `vote account`
/// * Instruction::keys[0] - the new "vote account" to be associated with the delegate
InitializeAccount,
/// `Delegate` or `Assign` a vote account to a particular node
DelegateStake(Pubkey),
/// takes a node_id and commission
InitializeAccount(Pubkey, u32),
/// Authorize a voter to send signed votes.
AuthorizeVoter(Pubkey),
Vote(Vote),
/// Clear the credits in the vote account
/// * Transaction::keys[0] - the "vote account"
ClearCredits,
}
fn initialize_account(vote_id: &Pubkey) -> Instruction {
fn initialize_account(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, false)];
Instruction::new(id(), &VoteInstruction::InitializeAccount, account_metas)
}
pub fn create_account(from_id: &Pubkey, staker_id: &Pubkey, lamports: u64) -> Vec<Instruction> {
let space = VoteState::max_size() as u64;
let create_ix = system_instruction::create_account(&from_id, staker_id, lamports, space, &id());
let init_ix = initialize_account(staker_id);
vec![create_ix, init_ix]
}
pub fn clear_credits(vote_id: &Pubkey) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, true)];
Instruction::new(id(), &VoteInstruction::ClearCredits, account_metas)
}
pub fn delegate_stake(vote_id: &Pubkey, delegate_id: &Pubkey) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, true)];
Instruction::new(
id(),
&VoteInstruction::DelegateStake(*delegate_id),
&VoteInstruction::InitializeAccount(*node_id, commission),
account_metas,
)
}
pub fn create_account(
from_id: &Pubkey,
vote_id: &Pubkey,
node_id: &Pubkey,
commission: u32,
lamports: u64,
) -> Vec<Instruction> {
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);
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)];
Instruction::new(
@ -76,3 +58,161 @@ pub fn vote(vote_id: &Pubkey, vote: Vote) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, true)];
Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas)
}
pub fn process_instruction(
_program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
_tick_height: u64,
) -> Result<(), InstructionError> {
solana_logger::setup();
trace!("process_instruction: {:?}", data);
trace!("keyed_accounts: {:?}", keyed_accounts);
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
VoteInstruction::InitializeAccount(node_id, commission) => {
let mut vote_account = &mut keyed_accounts[0];
vote_state::initialize_account(&mut vote_account, &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)
}
VoteInstruction::Vote(vote) => {
solana_metrics::submit(
solana_metrics::influxdb::Point::new("vote-native")
.add_field("count", solana_metrics::influxdb::Value::Integer(1))
.to_owned(),
);
let (vote_account, other_signers) = keyed_accounts.split_at_mut(1);
let vote_account = &mut vote_account[0];
vote_state::process_vote(vote_account, other_signers, &vote)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::id;
use crate::vote_instruction;
use crate::vote_state::{Vote, VoteState};
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::instruction::InstructionError;
use solana_sdk::message::Message;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::sync_client::SyncClient;
use solana_sdk::system_instruction;
use solana_sdk::transaction::{Result, TransactionError};
fn create_bank(lamports: u64) -> (Bank, Keypair) {
let (genesis_block, mint_keypair) = GenesisBlock::new(lamports);
let mut bank = Bank::new(&genesis_block);
bank.add_instruction_processor(id(), process_instruction);
(bank, mint_keypair)
}
fn create_vote_account(
bank_client: &BankClient,
from_keypair: &Keypair,
vote_id: &Pubkey,
lamports: u64,
) -> Result<()> {
let ixs = vote_instruction::create_account(
&from_keypair.pubkey(),
vote_id,
&Pubkey::new_rand(),
0,
lamports,
);
let message = Message::new(ixs);
bank_client
.send_message(&[from_keypair], message)
.map_err(|err| err.unwrap())?;
Ok(())
}
fn submit_vote(
bank_client: &BankClient,
vote_keypair: &Keypair,
tick_height: u64,
) -> Result<()> {
let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), Vote::new(tick_height));
bank_client
.send_instruction(vote_keypair, vote_ix)
.map_err(|err| err.unwrap())?;
Ok(())
}
#[test]
fn test_vote_bank_basic() {
let (bank, from_keypair) = create_bank(10_000);
let bank_client = BankClient::new(&bank);
let vote_keypair = Keypair::new();
let vote_id = vote_keypair.pubkey();
create_vote_account(&bank_client, &from_keypair, &vote_id, 100).unwrap();
submit_vote(&bank_client, &vote_keypair, 0).unwrap();
let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap();
let vote_state = VoteState::deserialize(&vote_account_data).unwrap();
assert_eq!(vote_state.votes.len(), 1);
}
#[test]
fn test_vote_via_bank_authorize_voter() {
let (bank, mallory_keypair) = create_bank(10_000);
let bank_client = BankClient::new(&bank);
let vote_keypair = Keypair::new();
let vote_id = vote_keypair.pubkey();
create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap();
let mallory_id = mallory_keypair.pubkey();
let vote_ix = vote_instruction::authorize_voter(&vote_id, &mallory_id);
let message = Message::new(vec![vote_ix]);
assert!(bank_client.send_message(&[&vote_keypair], message).is_ok());
}
#[test]
fn test_vote_via_bank_with_no_signature() {
let (bank, mallory_keypair) = create_bank(10_000);
let bank_client = BankClient::new(&bank);
let vote_keypair = Keypair::new();
let vote_id = vote_keypair.pubkey();
create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap();
let mallory_id = mallory_keypair.pubkey();
let mut vote_ix = vote_instruction::vote(&vote_id, Vote::new(0));
vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required.
// Sneak in an instruction so that the transaction is signed but
// the 0th account in the second instruction is not! The program
// needs to check that it's signed.
let move_ix = system_instruction::transfer(&mallory_id, &vote_id, 1);
let message = Message::new(vec![move_ix, vote_ix]);
let result = bank_client.send_message(&[&mallory_keypair], message);
// And ensure there's no vote.
let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap();
let vote_state = VoteState::deserialize(&vote_account_data).unwrap();
assert_eq!(vote_state.votes.len(), 0);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(1, InstructionError::MissingRequiredSignature)
);
}
}

View File

@ -1,175 +0,0 @@
//! Vote program
//! Receive and processes votes from validators
use crate::vote_instruction::VoteInstruction;
use crate::vote_state;
use bincode::deserialize;
use log::*;
use solana_sdk::account::KeyedAccount;
use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey;
pub fn process_instruction(
_program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
_tick_height: u64,
) -> Result<(), InstructionError> {
solana_logger::setup();
trace!("process_instruction: {:?}", data);
trace!("keyed_accounts: {:?}", keyed_accounts);
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
VoteInstruction::InitializeAccount => vote_state::initialize_account(keyed_accounts),
VoteInstruction::DelegateStake(delegate_id) => {
vote_state::delegate_stake(keyed_accounts, &delegate_id)
}
VoteInstruction::AuthorizeVoter(voter_id) => {
vote_state::authorize_voter(keyed_accounts, &voter_id)
}
VoteInstruction::Vote(vote) => {
debug!("{:?} by {}", vote, keyed_accounts[0].signer_key().unwrap());
solana_metrics::submit(
solana_metrics::influxdb::Point::new("vote-native")
.add_field("count", solana_metrics::influxdb::Value::Integer(1))
.to_owned(),
);
vote_state::process_vote(keyed_accounts, vote)
}
VoteInstruction::ClearCredits => vote_state::clear_credits(keyed_accounts),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::id;
use crate::vote_instruction::{self, Vote};
use crate::vote_state::VoteState;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::instruction::InstructionError;
use solana_sdk::message::Message;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::sync_client::SyncClient;
use solana_sdk::system_instruction;
use solana_sdk::transaction::{Result, TransactionError};
fn create_bank(lamports: u64) -> (Bank, Keypair) {
let (genesis_block, mint_keypair) = GenesisBlock::new(lamports);
let mut bank = Bank::new(&genesis_block);
bank.add_instruction_processor(id(), process_instruction);
(bank, mint_keypair)
}
fn create_vote_account(
bank_client: &BankClient,
from_keypair: &Keypair,
vote_id: &Pubkey,
lamports: u64,
) -> Result<()> {
let ixs = vote_instruction::create_account(&from_keypair.pubkey(), vote_id, lamports);
let message = Message::new(ixs);
bank_client
.send_message(&[from_keypair], message)
.map_err(|err| err.unwrap())?;
Ok(())
}
fn create_vote_account_with_delegate(
bank_client: &BankClient,
from_keypair: &Keypair,
vote_keypair: &Keypair,
delegate_id: &Pubkey,
lamports: u64,
) -> Result<()> {
let vote_id = vote_keypair.pubkey();
let mut ixs = vote_instruction::create_account(&from_keypair.pubkey(), &vote_id, lamports);
let delegate_ix = vote_instruction::delegate_stake(&vote_id, delegate_id);
ixs.push(delegate_ix);
let message = Message::new(ixs);
bank_client
.send_message(&[from_keypair, vote_keypair], message)
.map_err(|err| err.unwrap())?;
Ok(())
}
fn submit_vote(
bank_client: &BankClient,
vote_keypair: &Keypair,
tick_height: u64,
) -> Result<()> {
let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), Vote::new(tick_height));
bank_client
.send_instruction(vote_keypair, vote_ix)
.map_err(|err| err.unwrap())?;
Ok(())
}
#[test]
fn test_vote_bank_basic() {
let (bank, from_keypair) = create_bank(10_000);
let bank_client = BankClient::new(&bank);
let vote_keypair = Keypair::new();
let vote_id = vote_keypair.pubkey();
create_vote_account(&bank_client, &from_keypair, &vote_id, 100).unwrap();
submit_vote(&bank_client, &vote_keypair, 0).unwrap();
let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap();
let vote_state = VoteState::deserialize(&vote_account_data).unwrap();
assert_eq!(vote_state.votes.len(), 1);
}
#[test]
fn test_vote_bank_delegate() {
let (bank, from_keypair) = create_bank(10_000);
let vote_keypair = Keypair::new();
let bank_client = BankClient::new(&bank);
let delegate_id = Pubkey::new_rand();
create_vote_account_with_delegate(
&bank_client,
&from_keypair,
&vote_keypair,
&delegate_id,
100,
)
.unwrap();
}
#[test]
fn test_vote_via_bank_with_no_signature() {
let (bank, mallory_keypair) = create_bank(10_000);
let bank_client = BankClient::new(&bank);
let vote_keypair = Keypair::new();
let vote_id = vote_keypair.pubkey();
create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap();
let mallory_id = mallory_keypair.pubkey();
let mut vote_ix = vote_instruction::vote(&vote_id, Vote::new(0));
vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required.
// Sneak in an instruction so that the transaction is signed but
// the 0th account in the second instruction is not! The program
// needs to check that it's signed.
let move_ix = system_instruction::transfer(&mallory_id, &vote_id, 1);
let message = Message::new(vec![move_ix, vote_ix]);
let result = bank_client.send_message(&[&mallory_keypair], message);
// And ensure there's no vote.
let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap();
let vote_state = VoteState::deserialize(&vote_account_data).unwrap();
assert_eq!(vote_state.votes.len(), 0);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(1, InstructionError::InvalidArgument)
);
}
}

View File

@ -1,13 +1,11 @@
//! Vote stte
//! Vote state, vote program
//! Receive and processes votes from validators
use crate::vote_instruction::Vote;
use crate::{check_id, id};
use crate::id;
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
use log::*;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::account::{Account, KeyedAccount};
use solana_sdk::instruction::InstructionError;
use solana_sdk::instruction_processor_utils::State;
use solana_sdk::pubkey::Pubkey;
use std::collections::VecDeque;
@ -15,6 +13,19 @@ use std::collections::VecDeque;
pub const MAX_LOCKOUT_HISTORY: usize = 31;
pub const INITIAL_LOCKOUT: usize = 2;
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote {
// TODO: add signature of the state here as well
/// A vote for height slot
pub slot: u64,
}
impl Vote {
pub fn new(slot: u64) -> Self {
Self { slot }
}
}
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Lockout {
pub slot: u64,
@ -47,7 +58,7 @@ impl Lockout {
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct VoteState {
pub votes: VecDeque<Lockout>,
pub delegate_id: Pubkey,
pub node_id: Pubkey,
pub authorized_voter_id: Pubkey,
/// fraction of std::u32::MAX that represents what part of a rewards
/// payout should be given to this VoteAccount
@ -57,24 +68,23 @@ pub struct VoteState {
}
impl VoteState {
pub fn new(staker_id: &Pubkey) -> Self {
pub fn new(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Self {
let votes = VecDeque::new();
let credits = 0;
let root_slot = None;
let commission = 0;
Self {
votes,
delegate_id: *staker_id,
authorized_voter_id: *staker_id,
node_id: *node_id,
authorized_voter_id: *vote_id,
credits,
commission,
root_slot,
}
}
pub fn max_size() -> usize {
pub fn size_of() -> usize {
// Upper limit on the size of the Vote State. Equal to
// sizeof(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY
// size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY
let mut vote_state = Self::default();
vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]);
vote_state.root_slot = Some(std::u64::MAX);
@ -107,7 +117,7 @@ impl VoteState {
}
}
pub fn process_vote(&mut self, vote: Vote) {
pub fn process_vote(&mut self, vote: &Vote) {
// Ignore votes for slots earlier than we already have votes for
if self
.votes
@ -148,11 +158,6 @@ impl VoteState {
self.credits
}
/// Clear any credits.
pub fn clear_credits(&mut self) {
self.credits = 0;
}
fn pop_expired_votes(&mut self, slot: u64) {
loop {
if self.votes.back().map_or(false, |v| v.is_expired(slot)) {
@ -175,248 +180,257 @@ impl VoteState {
}
}
pub fn delegate_stake(
keyed_accounts: &mut [KeyedAccount],
node_id: &Pubkey,
) -> Result<(), InstructionError> {
if !check_id(&keyed_accounts[0].account.owner) {
error!("account[0] is not assigned to the VOTE_PROGRAM");
Err(InstructionError::InvalidArgument)?;
}
if keyed_accounts[0].signer_key().is_none() {
error!("account[0] should sign the transaction");
Err(InstructionError::InvalidArgument)?;
}
let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data);
if let Ok(mut vote_state) = vote_state {
vote_state.delegate_id = *node_id;
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
} else {
error!("account[0] does not valid data");
Err(InstructionError::InvalidAccountData)?;
}
Ok(())
}
/// Authorize the given pubkey to sign votes. This may be called multiple times,
/// but will implicitly withdraw authorization from the previously authorized
/// voter. The default voter is the owner of the vote account's pubkey.
pub fn authorize_voter(
keyed_accounts: &mut [KeyedAccount],
voter_id: &Pubkey,
vote_account: &mut KeyedAccount,
other_signers: &[KeyedAccount],
authorized_voter_id: &Pubkey,
) -> Result<(), InstructionError> {
if !check_id(&keyed_accounts[0].account.owner) {
error!("account[0] is not assigned to the VOTE_PROGRAM");
Err(InstructionError::InvalidArgument)?;
let mut vote_state: VoteState = vote_account.state()?;
// current authorized signer must say "yay"
let authorized = Some(&vote_state.authorized_voter_id);
if vote_account.signer_key() != authorized
&& other_signers
.iter()
.all(|account| account.signer_key() != authorized)
{
return Err(InstructionError::MissingRequiredSignature);
}
if keyed_accounts[0].signer_key().is_none() {
error!("account[0] should sign the transaction");
Err(InstructionError::InvalidArgument)?;
}
let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data);
if let Ok(mut vote_state) = vote_state {
vote_state.authorized_voter_id = *voter_id;
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
} else {
error!("account[0] does not valid data");
Err(InstructionError::InvalidAccountData)?;
}
Ok(())
vote_state.authorized_voter_id = *authorized_voter_id;
vote_account.set_state(&vote_state)
}
/// Initialize the vote_state for a vote account
/// Assumes that the account is being init as part of a account creation or balance transfer and
/// that the transaction must be signed by the staker's keys
pub fn initialize_account(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> {
if !check_id(&keyed_accounts[0].account.owner) {
error!("account[0] is not assigned to the VOTE_PROGRAM");
Err(InstructionError::InvalidArgument)?;
}
pub fn initialize_account(
vote_account: &mut KeyedAccount,
node_id: &Pubkey,
commission: u32,
) -> Result<(), InstructionError> {
let vote_state: VoteState = vote_account.state()?;
let staker_id = keyed_accounts[0].unsigned_key();
let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data);
if let Ok(vote_state) = vote_state {
if vote_state.delegate_id == Pubkey::default() {
let vote_state = VoteState::new(staker_id);
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
} else {
error!("account[0] data already initialized");
Err(InstructionError::InvalidAccountData)?;
}
} else {
error!("account[0] does not have valid data");
Err(InstructionError::InvalidAccountData)?;
if vote_state.authorized_voter_id != Pubkey::default() {
return Err(InstructionError::AccountAlreadyInitialized);
}
Ok(())
vote_account.set_state(&VoteState::new(
vote_account.unsigned_key(),
node_id,
commission,
))
}
pub fn process_vote(
keyed_accounts: &mut [KeyedAccount],
vote: Vote,
vote_account: &mut KeyedAccount,
other_signers: &[KeyedAccount],
vote: &Vote,
) -> Result<(), InstructionError> {
if !check_id(&keyed_accounts[0].account.owner) {
error!("account[0] is not assigned to the VOTE_PROGRAM");
Err(InstructionError::InvalidArgument)?;
let mut vote_state: VoteState = vote_account.state()?;
if vote_state.authorized_voter_id == Pubkey::default() {
return Err(InstructionError::UninitializedAccount);
}
let mut vote_state = VoteState::deserialize(&keyed_accounts[0].account.data)?;
// If no voter was authorized, expect account[0] to be the signer, otherwise account[1].
let signer_index = if vote_state.authorized_voter_id == *keyed_accounts[0].unsigned_key() {
0
} else {
1
};
if keyed_accounts.get(signer_index).is_none() {
error!("account[{}] not provided", signer_index);
Err(InstructionError::InvalidArgument)?;
}
if keyed_accounts[signer_index].signer_key().is_none() {
error!("account[{}] should sign the transaction", signer_index);
Err(InstructionError::InvalidArgument)?;
let authorized = Some(&vote_state.authorized_voter_id);
// find a signer that matches the authorized_voter_id
if vote_account.signer_key() != authorized
&& other_signers
.iter()
.all(|account| account.signer_key() != authorized)
{
return Err(InstructionError::MissingRequiredSignature);
}
vote_state.process_vote(vote);
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
Ok(())
vote_account.set_state(&vote_state)
}
pub fn clear_credits(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> {
if !check_id(&keyed_accounts[0].account.owner) {
error!("account[0] is not assigned to the VOTE_PROGRAM");
Err(InstructionError::InvalidArgument)?;
}
// utility function, used by Bank, tests
pub fn create_account(
vote_id: &Pubkey,
node_id: &Pubkey,
commission: u32,
lamports: u64,
) -> Account {
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
let mut vote_state = VoteState::deserialize(&keyed_accounts[0].account.data)?;
vote_state.clear_credits();
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
Ok(())
initialize_account(
&mut KeyedAccount::new(vote_id, false, &mut vote_account),
node_id,
commission,
)
.unwrap();
vote_account
}
pub fn create_vote_account(lamports: u64) -> Account {
let space = VoteState::max_size();
Account::new(lamports, space, &id())
}
pub fn initialize_and_deserialize(
// utility function, used by Bank, tests
pub fn vote(
vote_id: &Pubkey,
vote_account: &mut Account,
vote: &Vote,
) -> Result<VoteState, InstructionError> {
let mut keyed_accounts = [KeyedAccount::new(vote_id, false, vote_account)];
initialize_account(&mut keyed_accounts)?;
let vote_state = VoteState::deserialize(&vote_account.data).unwrap();
Ok(vote_state)
}
pub fn vote_and_deserialize(
vote_id: &Pubkey,
vote_account: &mut Account,
vote: Vote,
) -> Result<VoteState, InstructionError> {
let mut keyed_accounts = [KeyedAccount::new(vote_id, true, vote_account)];
process_vote(&mut keyed_accounts, vote)?;
let vote_state = VoteState::deserialize(&vote_account.data).unwrap();
Ok(vote_state)
process_vote(
&mut KeyedAccount::new(vote_id, true, vote_account),
&[],
vote,
)?;
vote_account.state()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vote_state;
#[test]
fn test_initialize_vote_account() {
let vote_account_id = Pubkey::new_rand();
let mut vote_account = create_vote_account(100);
let mut vote_account = Account::new(100, VoteState::size_of(), &id());
let bogus_account_id = Pubkey::new_rand();
let mut bogus_account = Account::new(100, 0, &id());
let mut keyed_accounts = [KeyedAccount::new(
&bogus_account_id,
false,
&mut bogus_account,
)];
let node_id = Pubkey::new_rand();
//init should pass
keyed_accounts[0] = KeyedAccount::new(&vote_account_id, false, &mut vote_account);
let res = initialize_account(&mut keyed_accounts);
let mut vote_account = KeyedAccount::new(&vote_account_id, false, &mut vote_account);
let res = initialize_account(&mut vote_account, &node_id, 0);
assert_eq!(res, Ok(()));
// reinit should fail
let res = initialize_account(&mut keyed_accounts);
assert_eq!(res, Err(InstructionError::InvalidAccountData));
let res = initialize_account(&mut vote_account, &node_id, 0);
assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized));
}
fn create_test_account() -> (Pubkey, Account) {
let vote_id = Pubkey::new_rand();
(
vote_id,
vote_state::create_account(&vote_id, &Pubkey::new_rand(), 0, 100),
)
}
#[test]
fn test_vote_serialize() {
let mut buffer: Vec<u8> = vec![0; VoteState::max_size()];
let mut buffer: Vec<u8> = vec![0; VoteState::size_of()];
let mut vote_state = VoteState::default();
vote_state
.votes
.resize(MAX_LOCKOUT_HISTORY, Lockout::default());
assert!(vote_state.serialize(&mut buffer[0..4]).is_err());
vote_state.serialize(&mut buffer).unwrap();
assert_eq!(VoteState::deserialize(&buffer).unwrap(), vote_state);
}
#[test]
fn test_voter_registration() {
let vote_id = Pubkey::new_rand();
let mut vote_account = create_vote_account(100);
let (vote_id, vote_account) = create_test_account();
let vote_state = initialize_and_deserialize(&vote_id, &mut vote_account).unwrap();
assert_eq!(vote_state.delegate_id, vote_id);
let vote_state: VoteState = vote_account.state().unwrap();
assert_eq!(vote_state.authorized_voter_id, vote_id);
assert!(vote_state.votes.is_empty());
}
#[test]
fn test_vote() {
let vote_id = Pubkey::new_rand();
let mut vote_account = create_vote_account(100);
initialize_and_deserialize(&vote_id, &mut vote_account).unwrap();
let (vote_id, mut vote_account) = create_test_account();
let vote = Vote::new(1);
let vote_state = vote_and_deserialize(&vote_id, &mut vote_account, vote.clone()).unwrap();
let vote_state = vote_state::vote(&vote_id, &mut vote_account, &vote).unwrap();
assert_eq!(vote_state.votes, vec![Lockout::new(&vote)]);
assert_eq!(vote_state.credits(), 0);
}
#[test]
fn test_vote_signature() {
let vote_id = Pubkey::new_rand();
let mut vote_account = create_vote_account(100);
initialize_and_deserialize(&vote_id, &mut vote_account).unwrap();
let (vote_id, mut vote_account) = create_test_account();
let vote = Vote::new(1);
let mut keyed_accounts = [KeyedAccount::new(&vote_id, false, &mut vote_account)];
let res = process_vote(&mut keyed_accounts, vote);
assert_eq!(res, Err(InstructionError::InvalidArgument));
// unsigned
let res = process_vote(
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
&[],
&vote,
);
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
// unsigned
let res = process_vote(
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
&[],
&vote,
);
assert_eq!(res, Ok(()));
// another voter
let authorized_voter_id = Pubkey::new_rand();
let res = authorize_voter(
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
&[],
&authorized_voter_id,
);
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
let res = authorize_voter(
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
&[],
&authorized_voter_id,
);
assert_eq!(res, Ok(()));
// verify authorized_voter_id can authorize authorized_voter_id ;)
let res = authorize_voter(
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
&[KeyedAccount::new(
&authorized_voter_id,
true,
&mut Account::default(),
)],
&authorized_voter_id,
);
assert_eq!(res, Ok(()));
// not signed by authorized voter
let vote = Vote::new(2);
let res = process_vote(
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
&[],
&vote,
);
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
// signed by authorized voter
let vote = Vote::new(2);
let res = process_vote(
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
&[KeyedAccount::new(
&authorized_voter_id,
true,
&mut Account::default(),
)],
&vote,
);
assert_eq!(res, Ok(()));
}
#[test]
fn test_vote_without_initialization() {
let vote_id = Pubkey::new_rand();
let mut vote_account = create_vote_account(100);
let mut vote_account = Account::new(100, VoteState::size_of(), &id());
let vote = Vote::new(1);
let res = vote_and_deserialize(&vote_id, &mut vote_account, vote.clone());
assert_eq!(res, Err(InstructionError::InvalidArgument));
let res = vote_state::vote(&vote_id, &mut vote_account, &Vote::new(1));
assert_eq!(res, Err(InstructionError::UninitializedAccount));
}
#[test]
fn test_vote_lockout() {
let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id);
let (_vote_id, vote_account) = create_test_account();
let mut vote_state: VoteState = vote_account.state().unwrap();
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
vote_state.process_vote(Vote::new((INITIAL_LOCKOUT as usize * i) as u64));
vote_state.process_vote(&Vote::new((INITIAL_LOCKOUT as usize * i) as u64));
}
// The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
@ -428,14 +442,14 @@ mod tests {
// the root_slot should change to the
// second vote
let top_vote = vote_state.votes.front().unwrap().slot;
vote_state.process_vote(Vote::new(
vote_state.process_vote(&Vote::new(
vote_state.votes.back().unwrap().expiration_slot(),
));
assert_eq!(Some(top_vote), vote_state.root_slot);
// Expire everything except the first vote
let vote = Vote::new(vote_state.votes.front().unwrap().expiration_slot());
vote_state.process_vote(vote);
vote_state.process_vote(&vote);
// First vote and new vote are both stored for a total of 2 votes
assert_eq!(vote_state.votes.len(), 2);
}
@ -443,11 +457,11 @@ mod tests {
#[test]
fn test_vote_double_lockout_after_expiration() {
let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id);
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..3 {
let vote = Vote::new(i as u64);
vote_state.process_vote(vote);
vote_state.process_vote(&vote);
}
check_lockouts(&vote_state);
@ -455,35 +469,35 @@ mod tests {
// Expire the third vote (which was a vote for slot 2). The height of the
// vote stack is unchanged, so none of the previous votes should have
// doubled in lockout
vote_state.process_vote(Vote::new((2 + INITIAL_LOCKOUT + 1) as u64));
vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 1) as u64));
check_lockouts(&vote_state);
// Vote again, this time the vote stack depth increases, so the lockouts should
// double for everybody
vote_state.process_vote(Vote::new((2 + INITIAL_LOCKOUT + 2) as u64));
vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 2) as u64));
check_lockouts(&vote_state);
// Vote again, this time the vote stack depth increases, so the lockouts should
// double for everybody
vote_state.process_vote(Vote::new((2 + INITIAL_LOCKOUT + 3) as u64));
vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 3) as u64));
check_lockouts(&vote_state);
}
#[test]
fn test_expire_multiple_votes() {
let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id);
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..3 {
let vote = Vote::new(i as u64);
vote_state.process_vote(vote);
vote_state.process_vote(&vote);
}
assert_eq!(vote_state.votes[0].confirmation_count, 3);
// Expire the second and third votes
let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1;
vote_state.process_vote(Vote::new(expire_slot));
vote_state.process_vote(&Vote::new(expire_slot));
assert_eq!(vote_state.votes.len(), 2);
// Check that the old votes expired
@ -491,7 +505,7 @@ mod tests {
assert_eq!(vote_state.votes[1].slot, expire_slot);
// Process one more vote
vote_state.process_vote(Vote::new(expire_slot + 1));
vote_state.process_vote(&Vote::new(expire_slot + 1));
// Confirmation count for the older first vote should remain unchanged
assert_eq!(vote_state.votes[0].confirmation_count, 3);
@ -504,31 +518,29 @@ mod tests {
#[test]
fn test_vote_credits() {
let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id);
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..MAX_LOCKOUT_HISTORY {
vote_state.process_vote(Vote::new(i as u64));
vote_state.process_vote(&Vote::new(i as u64));
}
assert_eq!(vote_state.credits, 0);
vote_state.process_vote(Vote::new(MAX_LOCKOUT_HISTORY as u64 + 1));
vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 1));
assert_eq!(vote_state.credits, 1);
vote_state.process_vote(Vote::new(MAX_LOCKOUT_HISTORY as u64 + 2));
vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 2));
assert_eq!(vote_state.credits(), 2);
vote_state.process_vote(Vote::new(MAX_LOCKOUT_HISTORY as u64 + 3));
vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 3));
assert_eq!(vote_state.credits(), 3);
vote_state.clear_credits();
assert_eq!(vote_state.credits(), 0);
}
#[test]
fn test_duplicate_vote() {
let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id);
vote_state.process_vote(Vote::new(0));
vote_state.process_vote(Vote::new(1));
vote_state.process_vote(Vote::new(0));
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
vote_state.process_vote(&Vote::new(0));
vote_state.process_vote(&Vote::new(1));
vote_state.process_vote(&Vote::new(0));
assert_eq!(vote_state.nth_recent_vote(0).unwrap().slot, 1);
assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
assert!(vote_state.nth_recent_vote(2).is_none());
@ -537,9 +549,9 @@ mod tests {
#[test]
fn test_nth_recent_vote() {
let voter_id = Pubkey::new_rand();
let mut vote_state = VoteState::new(&voter_id);
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
for i in 0..MAX_LOCKOUT_HISTORY {
vote_state.process_vote(Vote::new(i as u64));
vote_state.process_vote(&Vote::new(i as u64));
}
for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
assert_eq!(

View File

@ -1,3 +1,3 @@
use solana_vote_api::vote_processor::process_instruction;
use solana_vote_api::vote_instruction::process_instruction;
solana_sdk::solana_entrypoint!(process_instruction);

7
run.sh
View File

@ -87,12 +87,7 @@ trap abort INT TERM EXIT
solana-wallet --keypair "$dataDir"/config/leader-keypair.json airdrop 42
solana-wallet \
--keypair "$dataDir"/config/leader-keypair.json \
create-staking-account "$leaderStakingAccountPubkey" 42
solana-wallet \
--keypair "$dataDir"/config/leader-staking-account-keypair.json \
configure-staking-account \
--delegate-account "$leaderPubkey" \
--authorize-voter "$leaderStakingAccountPubkey"
create-vote-account "$leaderStakingAccountPubkey" "$leaderPubkey" 42
solana-wallet --keypair "$dataDir"/config/leader-keypair.json balance
wait "$fullnode"

View File

@ -23,8 +23,7 @@ use solana_sdk::signature::{Keypair, Signature};
use solana_sdk::system_transaction;
use solana_sdk::timing::{duration_as_ms, duration_as_us, MAX_RECENT_BLOCKHASHES};
use solana_sdk::transaction::{Result, Transaction, TransactionError};
use solana_vote_api::vote_instruction::Vote;
use solana_vote_api::vote_state::{Lockout, VoteState};
use solana_vote_api::vote_state::{self, Vote};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::time::Instant;
@ -321,19 +320,19 @@ impl Bank {
// Construct a vote account for the bootstrap_leader such that the leader_scheduler
// will be forced to select it as the leader for height 0
let mut bootstrap_leader_vote_account = Account {
lamports: bootstrap_leader_stake,
data: vec![0; VoteState::max_size() as usize],
owner: solana_vote_api::id(),
executable: false,
};
let mut bootstrap_leader_vote_account = vote_state::create_account(
&genesis_block.bootstrap_leader_vote_account_id,
&genesis_block.bootstrap_leader_id,
0,
bootstrap_leader_stake,
);
let mut vote_state = VoteState::new(&genesis_block.bootstrap_leader_id);
vote_state.votes.push_back(Lockout::new(&Vote::new(0)));
vote_state.authorized_voter_id = genesis_block.bootstrap_leader_vote_account_id;
vote_state
.serialize(&mut bootstrap_leader_vote_account.data)
.unwrap();
vote_state::vote(
&genesis_block.bootstrap_leader_vote_account_id,
&mut bootstrap_leader_vote_account,
&Vote::new(0),
)
.unwrap();
self.store(
&genesis_block.bootstrap_leader_vote_account_id,
@ -1037,6 +1036,7 @@ mod tests {
use solana_sdk::system_instruction;
use solana_sdk::system_transaction;
use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::VoteState;
#[test]
fn test_bank_new() {
@ -1654,7 +1654,7 @@ mod tests {
.iter()
.filter_map(|(pubkey, account)| {
if let Ok(vote_state) = VoteState::deserialize(&account.data) {
if vote_state.delegate_id == leader_id {
if vote_state.node_id == leader_id {
Some((*pubkey, true))
} else {
None
@ -1886,8 +1886,13 @@ mod tests {
// to have a vote account
let vote_keypair = Keypair::new();
let instructions =
vote_instruction::create_account(&mint_keypair.pubkey(), &vote_keypair.pubkey(), 10);
let instructions = vote_instruction::create_account(
&mint_keypair.pubkey(),
&vote_keypair.pubkey(),
&mint_keypair.pubkey(),
0,
10,
);
let transaction = Transaction::new_signed_instructions(
&[&mint_keypair],

View File

@ -1,6 +1,5 @@
use clap::{
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgGroup, ArgMatches,
SubCommand,
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand,
};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
@ -207,33 +206,20 @@ fn main() -> Result<(), Box<dyn error::Error>> {
),
)
.subcommand(
SubCommand::with_name("configure-staking-account")
.about("Configure staking account for node")
.group(
ArgGroup::with_name("options")
.args(&["delegate", "authorize"])
.multiple(true)
.required(true),
)
SubCommand::with_name("authorize-voter")
.about("Authorize a different voter for this account")
.arg(
Arg::with_name("delegate")
.long("delegate-account")
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey)
.help("Address to delegate this vote account to"),
)
.arg(
Arg::with_name("authorize")
.long("authorize-voter")
Arg::with_name("authorized-voter-id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote signer to authorize"),
),
)
.subcommand(
SubCommand::with_name("create-staking-account")
SubCommand::with_name("create-vote-account")
.about("Create staking account for node")
.arg(
Arg::with_name("voting_account_id")
@ -245,13 +231,29 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("lamports")
Arg::with_name("node_id")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("lamports")
.index(3)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to staking account"),
)
.arg(
Arg::with_name("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission on rewards this vote account should take, defaults to zero")
),
)
.subcommand(
SubCommand::with_name("deploy")

View File

@ -36,8 +36,8 @@ pub enum WalletCommand {
Cancel(Pubkey),
Confirm(Signature),
// ConfigureStakingAccount(delegate_id, authorized_voter_id)
ConfigureStakingAccount(Option<Pubkey>, Option<Pubkey>),
CreateStakingAccount(Pubkey, u64),
AuthorizeVoter(Pubkey),
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
Deploy(String),
GetTransactionCount,
// Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable)
@ -164,19 +164,23 @@ pub fn parse_command(
Err(WalletError::BadParameter("Invalid signature".to_string()))
}
}
("configure-staking-account", Some(staking_config_matches)) => {
let delegate_id = pubkey_of(staking_config_matches, "delegate");
let authorized_voter_id = pubkey_of(staking_config_matches, "authorize");
Ok(WalletCommand::ConfigureStakingAccount(
delegate_id,
authorized_voter_id,
))
("authorize-voter", Some(matches)) => {
let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap();
Ok(WalletCommand::AuthorizeVoter(authorized_voter_id))
}
("create-staking-account", Some(staking_matches)) => {
let voting_account_id = pubkey_of(staking_matches, "voting_account_id").unwrap();
let lamports = staking_matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateStakingAccount(
("create-vote-account", Some(matches)) => {
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
let node_id = pubkey_of(matches, "node_id").unwrap();
let commission = if let Some(commission) = matches.value_of("commission") {
commission.parse()?
} else {
0
};
let lamports = matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateVoteAccount(
voting_account_id,
node_id,
commission,
lamports,
))
}
@ -327,26 +331,17 @@ fn process_confirm(rpc_client: &RpcClient, signature: Signature) -> ProcessResul
}
}
fn process_configure_staking(
fn process_authorize_voter(
rpc_client: &RpcClient,
config: &WalletConfig,
delegate_option: Option<Pubkey>,
authorized_voter_option: Option<Pubkey>,
authorized_voter_id: Pubkey,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let mut ixs = vec![];
if let Some(delegate_id) = delegate_option {
ixs.push(vote_instruction::delegate_stake(
&config.keypair.pubkey(),
&delegate_id,
));
}
if let Some(authorized_voter_id) = authorized_voter_option {
ixs.push(vote_instruction::authorize_voter(
&config.keypair.pubkey(),
&authorized_voter_id,
));
}
let ixs = vec![vote_instruction::authorize_voter(
&config.keypair.pubkey(),
&authorized_voter_id,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?;
Ok(signature_str.to_string())
@ -356,11 +351,18 @@ fn process_create_staking(
rpc_client: &RpcClient,
config: &WalletConfig,
voting_account_id: &Pubkey,
node_id: &Pubkey,
commission: u32,
lamports: u64,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let ixs =
vote_instruction::create_account(&config.keypair.pubkey(), voting_account_id, lamports);
let ixs = vote_instruction::create_account(
&config.keypair.pubkey(),
voting_account_id,
node_id,
commission,
lamports,
);
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?;
Ok(signature_str.to_string())
@ -617,18 +619,20 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
// Configure staking account already created
WalletCommand::ConfigureStakingAccount(delegate_option, authorized_voter_option) => {
process_configure_staking(
&rpc_client,
config,
delegate_option,
authorized_voter_option,
)
WalletCommand::AuthorizeVoter(authorized_voter_id) => {
process_authorize_voter(&rpc_client, config, authorized_voter_id)
}
// Create staking account
WalletCommand::CreateStakingAccount(voting_account_id, lamports) => {
process_create_staking(&rpc_client, config, &voting_account_id, lamports)
WalletCommand::CreateVoteAccount(voting_account_id, node_id, commission, lamports) => {
process_create_staking(
&rpc_client,
config,
&voting_account_id,
&node_id,
commission,
lamports,
)
}
// Deploy a custom program to the chain
@ -723,7 +727,7 @@ pub fn request_and_confirm_airdrop(
#[cfg(test)]
mod tests {
use super::*;
use clap::{App, Arg, ArgGroup, SubCommand};
use clap::{App, Arg, SubCommand};
use serde_json::Value;
use solana_client::mock_rpc_client_request::SIGNATURE;
use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil};
@ -802,31 +806,19 @@ mod tests {
),
)
.subcommand(
SubCommand::with_name("configure-staking-account")
SubCommand::with_name("authorize-voter")
.about("Configure staking account for node")
.group(
ArgGroup::with_name("options")
.args(&["delegate", "authorize"])
.multiple(true)
.required(true),
)
.arg(
Arg::with_name("delegate")
.long("delegate-account")
Arg::with_name("authorized_voter_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("Address to delegate this vote account to"),
)
.arg(
Arg::with_name("authorize")
.long("authorize-voter")
.value_name("PUBKEY")
.takes_value(true)
.help("Vote signer to authorize"),
),
)
.subcommand(
SubCommand::with_name("create-staking-account")
SubCommand::with_name("create-vote-account")
.about("Create staking account for node")
.arg(
Arg::with_name("voting_account_id")
@ -837,12 +829,27 @@ mod tests {
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("lamports")
Arg::with_name("node_id")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("Node that will vote in this account"),
)
.arg(
Arg::with_name("lamports")
.index(3)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to staking account"),
)
.arg(
Arg::with_name("commission")
.long("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission taken on reward redemption"),
),
)
.subcommand(
@ -1004,42 +1011,42 @@ mod tests {
.get_matches_from(vec!["test", "confirm", "deadbeef"]);
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
// Test ConfigureStakingAccount Subcommand
let second_pubkey = Pubkey::new_rand();
let second_pubkey_string = format!("{}", second_pubkey);
let test_configure_staking_account = test_commands.clone().get_matches_from(vec![
"test",
"configure-staking-account",
"--delegate-account",
&pubkey_string,
"--authorize-voter",
&second_pubkey_string,
]);
// Test AuthorizeVoter Subcommand
let test_authorize_voter =
test_commands
.clone()
.get_matches_from(vec!["test", "authorize-voter", &pubkey_string]);
assert_eq!(
parse_command(&pubkey, &test_configure_staking_account).unwrap(),
WalletCommand::ConfigureStakingAccount(Some(pubkey), Some(second_pubkey))
);
let test_configure_staking_account = test_commands.clone().get_matches_from(vec![
"test",
"configure-staking-account",
"--delegate-account",
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_configure_staking_account).unwrap(),
WalletCommand::ConfigureStakingAccount(Some(pubkey), None)
parse_command(&pubkey, &test_authorize_voter).unwrap(),
WalletCommand::AuthorizeVoter(pubkey)
);
// Test CreateStakingAccount SubCommand
let test_create_staking_account = test_commands.clone().get_matches_from(vec![
// Test CreateVoteAccount SubCommand
let node_id = Pubkey::new_rand();
let node_id_string = format!("{}", node_id);
let test_create_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"create-staking-account",
"create-vote-account",
&pubkey_string,
&node_id_string,
"50",
"--commission",
"10",
]);
assert_eq!(
parse_command(&pubkey, &test_create_vote_account).unwrap(),
WalletCommand::CreateVoteAccount(pubkey, node_id, 10, 50)
);
let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
"test",
"create-vote-account",
&pubkey_string,
&node_id_string,
"50",
]);
assert_eq!(
parse_command(&pubkey, &test_create_staking_account).unwrap(),
WalletCommand::CreateStakingAccount(pubkey, 50)
parse_command(&pubkey, &test_create_vote_account2).unwrap(),
WalletCommand::CreateVoteAccount(pubkey, node_id, 0, 50)
);
// Test Deploy Subcommand
@ -1191,11 +1198,12 @@ mod tests {
assert_eq!(process_command(&config).unwrap(), "Confirmed");
let bob_pubkey = Pubkey::new_rand();
config.command = WalletCommand::ConfigureStakingAccount(None, Some(bob_pubkey));
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = WalletCommand::CreateStakingAccount(bob_pubkey, 10);
let node_id = Pubkey::new_rand();
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -1300,10 +1308,10 @@ mod tests {
config.command = WalletCommand::Balance(config.keypair.pubkey());
assert!(process_command(&config).is_err());
config.command = WalletCommand::ConfigureStakingAccount(None, Some(bob_pubkey));
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
assert!(process_command(&config).is_err());
config.command = WalletCommand::CreateStakingAccount(bob_pubkey, 10);
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
assert!(process_command(&config).is_err());
config.command = WalletCommand::GetTransactionCount;