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:
parent
1b5845ac3e
commit
f1e7237c09
|
@ -451,7 +451,8 @@ mod tests {
|
||||||
use solana_sdk::hash::hash;
|
use solana_sdk::hash::hash;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::system_transaction;
|
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};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction {
|
fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction {
|
||||||
|
|
|
@ -28,7 +28,8 @@ use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::system_transaction;
|
use solana_sdk::system_transaction;
|
||||||
use solana_sdk::timing::timestamp;
|
use solana_sdk::timing::timestamp;
|
||||||
use solana_sdk::transaction::Transaction;
|
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::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::{channel, Receiver};
|
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(
|
let new_vote_account_ixs = vote_instruction::create_account(
|
||||||
&active_keypair.pubkey(),
|
&active_keypair.pubkey(),
|
||||||
&vote_account_id,
|
&vote_account_id,
|
||||||
|
&active_keypair.pubkey(),
|
||||||
|
0,
|
||||||
stake.saturating_sub(2),
|
stake.saturating_sub(2),
|
||||||
);
|
);
|
||||||
let new_vote_account_tx = Transaction::new_signed_instructions(
|
let new_vote_account_tx = Transaction::new_signed_instructions(
|
||||||
|
|
|
@ -100,7 +100,7 @@ mod tests {
|
||||||
use crate::blocktree::get_tmp_ledger_path;
|
use crate::blocktree::get_tmp_ledger_path;
|
||||||
use crate::blocktree::tests::make_slot_entries;
|
use crate::blocktree::tests::make_slot_entries;
|
||||||
use crate::staking_utils;
|
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::genesis_block::{GenesisBlock, BOOTSTRAP_LEADER_LAMPORTS};
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -224,7 +224,7 @@ mod tests {
|
||||||
|
|
||||||
// Create new vote account
|
// Create new vote account
|
||||||
let new_voting_keypair = Keypair::new();
|
let new_voting_keypair = Keypair::new();
|
||||||
new_vote_account_with_delegate(
|
new_vote_account(
|
||||||
&mint_keypair,
|
&mint_keypair,
|
||||||
&new_voting_keypair,
|
&new_voting_keypair,
|
||||||
&delegate_id,
|
&delegate_id,
|
||||||
|
|
|
@ -334,13 +334,15 @@ impl LocalCluster {
|
||||||
amount: u64,
|
amount: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vote_account_pubkey = vote_account.pubkey();
|
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
|
// Create the vote account if necessary
|
||||||
if client.poll_get_balance(&vote_account_pubkey).unwrap_or(0) == 0 {
|
if client.poll_get_balance(&vote_account_pubkey).unwrap_or(0) == 0 {
|
||||||
// 1) Create vote account
|
// 1) Create vote account
|
||||||
let instructions = vote_instruction::create_account(
|
let instructions = vote_instruction::create_account(
|
||||||
&from_account.pubkey(),
|
&from_account.pubkey(),
|
||||||
&vote_account_pubkey,
|
&vote_account_pubkey,
|
||||||
|
&node_id,
|
||||||
|
0,
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
let mut transaction = Transaction::new_signed_instructions(
|
let mut transaction = Transaction::new_signed_instructions(
|
||||||
|
@ -355,27 +357,12 @@ impl LocalCluster {
|
||||||
client
|
client
|
||||||
.wait_for_balance(&vote_account_pubkey, Some(amount))
|
.wait_for_balance(&vote_account_pubkey, Some(amount))
|
||||||
.expect("get balance");
|
.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");
|
info!("Checking for vote account registration");
|
||||||
let vote_account_user_data = client.get_account_data(&vote_account_pubkey);
|
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(Some(vote_account_user_data)) = vote_account_user_data {
|
||||||
if let Ok(vote_state) = VoteState::deserialize(&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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ use solana_metrics::influxdb;
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_vote_api::vote_instruction::Vote;
|
use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY};
|
||||||
use solana_vote_api::vote_state::{Lockout, VoteState, MAX_LOCKOUT_HISTORY};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
||||||
|
@ -117,7 +116,7 @@ impl Locktower {
|
||||||
.expect("bank should always have valid VoteState data");
|
.expect("bank should always have valid VoteState data");
|
||||||
|
|
||||||
if key == self.epoch_stakes.delegate_id
|
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!("vote state {:?}", vote_state);
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -141,7 +140,7 @@ impl Locktower {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let start_root = vote_state.root_slot;
|
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 {
|
for vote in &vote_state.votes {
|
||||||
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
|
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> {
|
pub fn record_vote(&mut self, slot: u64) -> Option<u64> {
|
||||||
let root_slot = self.lockouts.root_slot;
|
let root_slot = self.lockouts.root_slot;
|
||||||
self.lockouts.process_vote(Vote { slot });
|
self.lockouts.process_vote(&Vote { slot });
|
||||||
solana_metrics::submit(
|
solana_metrics::submit(
|
||||||
influxdb::Point::new("counter-locktower-vote")
|
influxdb::Point::new("counter-locktower-vote")
|
||||||
.add_field("latest", influxdb::Value::Integer(slot as i64))
|
.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 {
|
pub fn is_locked_out(&self, slot: u64, descendants: &HashMap<u64, HashSet<u64>>) -> bool {
|
||||||
let mut lockouts = self.lockouts.clone();
|
let mut lockouts = self.lockouts.clone();
|
||||||
lockouts.process_vote(Vote { slot });
|
lockouts.process_vote(&Vote { slot });
|
||||||
for vote in &lockouts.votes {
|
for vote in &lockouts.votes {
|
||||||
if vote.slot == slot {
|
if vote.slot == slot {
|
||||||
continue;
|
continue;
|
||||||
|
@ -303,7 +302,7 @@ impl Locktower {
|
||||||
stake_lockouts: &HashMap<u64, StakeLockout>,
|
stake_lockouts: &HashMap<u64, StakeLockout>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut lockouts = self.lockouts.clone();
|
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);
|
let vote = lockouts.nth_recent_vote(self.threshold_depth);
|
||||||
if let Some(vote) = vote {
|
if let Some(vote) = vote {
|
||||||
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
|
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
|
||||||
|
@ -398,7 +397,7 @@ mod test {
|
||||||
account.lamports = *lamports;
|
account.lamports = *lamports;
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for slot in *votes {
|
for slot in *votes {
|
||||||
vote_state.process_vote(Vote { slot: *slot });
|
vote_state.process_vote(&Vote { slot: *slot });
|
||||||
}
|
}
|
||||||
vote_state
|
vote_state
|
||||||
.serialize(&mut account.data)
|
.serialize(&mut account.data)
|
||||||
|
|
|
@ -21,7 +21,8 @@ use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::KeypairUtil;
|
use solana_sdk::signature::KeypairUtil;
|
||||||
use solana_sdk::timing::{self, duration_as_ms};
|
use solana_sdk::timing::{self, duration_as_ms};
|
||||||
use solana_sdk::transaction::Transaction;
|
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::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
|
@ -76,7 +76,7 @@ pub fn node_staked_accounts_at_epoch(
|
||||||
|
|
||||||
fn filter_no_delegate(account_id: &Pubkey, account: &Account) -> bool {
|
fn filter_no_delegate(account_id: &Pubkey, account: &Account) -> bool {
|
||||||
VoteState::deserialize(&account.data)
|
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)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ fn to_delegated_stakes(
|
||||||
) -> HashMap<Pubkey, u64> {
|
) -> HashMap<Pubkey, u64> {
|
||||||
let mut map: HashMap<Pubkey, u64> = HashMap::new();
|
let mut map: HashMap<Pubkey, u64> = HashMap::new();
|
||||||
node_staked_accounts.for_each(|(stake, state)| {
|
node_staked_accounts.for_each(|(stake, state)| {
|
||||||
let delegate = &state.delegate_id;
|
let delegate = &state.node_id;
|
||||||
map.entry(*delegate)
|
map.entry(*delegate)
|
||||||
.and_modify(|s| *s += stake)
|
.and_modify(|s| *s += stake)
|
||||||
.or_insert(stake);
|
.or_insert(stake);
|
||||||
|
@ -200,7 +200,7 @@ mod tests {
|
||||||
|
|
||||||
// Make a mint vote account. Because the mint has nonzero stake, this
|
// Make a mint vote account. Because the mint has nonzero stake, this
|
||||||
// should show up in the active set
|
// should show up in the active set
|
||||||
voting_keypair_tests::new_vote_account_with_delegate(
|
voting_keypair_tests::new_vote_account(
|
||||||
&mint_keypair,
|
&mint_keypair,
|
||||||
&bank_voter,
|
&bank_voter,
|
||||||
&mint_keypair.pubkey(),
|
&mint_keypair.pubkey(),
|
||||||
|
@ -283,11 +283,11 @@ mod tests {
|
||||||
|
|
||||||
// Delegate 1 has stake of 3
|
// Delegate 1 has stake of 3
|
||||||
for i in 0..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
|
// 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());
|
let result = to_delegated_stakes(stakes.into_iter());
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
|
|
|
@ -110,7 +110,8 @@ pub mod tests {
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::transaction::Transaction;
|
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>) {
|
fn process_instructions<T: KeypairUtil>(bank: &Bank, keypairs: &[&T], ixs: Vec<Instruction>) {
|
||||||
let blockhash = bank.last_blockhash();
|
let blockhash = bank.last_blockhash();
|
||||||
|
@ -119,27 +120,21 @@ pub mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_vote_account(
|
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,
|
from_keypair: &Keypair,
|
||||||
voting_keypair: &Keypair,
|
voting_keypair: &Keypair,
|
||||||
delegate: &Pubkey,
|
node_id: &Pubkey,
|
||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
) {
|
) {
|
||||||
let voting_pubkey = voting_keypair.pubkey();
|
let voting_pubkey = voting_keypair.pubkey();
|
||||||
let mut ixs =
|
let ixs = vote_instruction::create_account(
|
||||||
vote_instruction::create_account(&from_keypair.pubkey(), &voting_pubkey, lamports);
|
&from_keypair.pubkey(),
|
||||||
ixs.push(vote_instruction::delegate_stake(&voting_pubkey, delegate));
|
&voting_pubkey,
|
||||||
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
|
node_id,
|
||||||
|
0,
|
||||||
|
lamports,
|
||||||
|
);
|
||||||
|
process_instructions(bank, &[from_keypair], ixs);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot: u64) {
|
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>(
|
pub fn new_vote_account_with_vote<T: KeypairUtil>(
|
||||||
from_keypair: &T,
|
from_keypair: &T,
|
||||||
voting_keypair: &T,
|
voting_keypair: &T,
|
||||||
|
node_id: &Pubkey,
|
||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
slot: u64,
|
slot: u64,
|
||||||
) {
|
) {
|
||||||
let voting_pubkey = voting_keypair.pubkey();
|
let voting_pubkey = voting_keypair.pubkey();
|
||||||
let mut ixs =
|
let mut ixs = vote_instruction::create_account(
|
||||||
vote_instruction::create_account(&from_keypair.pubkey(), &voting_pubkey, lamports);
|
&from_keypair.pubkey(),
|
||||||
|
&voting_pubkey,
|
||||||
|
node_id,
|
||||||
|
0,
|
||||||
|
lamports,
|
||||||
|
);
|
||||||
ixs.push(vote_instruction::vote(&voting_pubkey, Vote::new(slot)));
|
ixs.push(vote_instruction::vote(&voting_pubkey, Vote::new(slot)));
|
||||||
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
|
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,11 @@ fn main() {
|
||||||
.help("File containing an identity (keypair)"),
|
.help("File containing an identity (keypair)"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("staking_account")
|
Arg::with_name("vote_account")
|
||||||
.long("staking-account")
|
.long("vote-account")
|
||||||
.value_name("PUBKEY_BASE58_STR")
|
.value_name("PUBKEY_BASE58_STR")
|
||||||
.takes_value(true)
|
.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(
|
||||||
Arg::with_name("voting_keypair")
|
Arg::with_name("voting_keypair")
|
||||||
|
|
|
@ -54,10 +54,10 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
.help("Path to file containing keys of the mint"),
|
.help("Path to file containing keys of the mint"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("bootstrap_stake_keypair_file")
|
Arg::with_name("bootstrap_vote_keypair_file")
|
||||||
.short("s")
|
.short("s")
|
||||||
.long("bootstrap-stake-keypair")
|
.long("bootstrap-vote-keypair")
|
||||||
.value_name("BOOTSTRAP STAKE KEYPAIR")
|
.value_name("BOOTSTRAP VOTE KEYPAIR")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Path to file containing the bootstrap leader's staking keypair"),
|
.help("Path to file containing the bootstrap leader's staking keypair"),
|
||||||
|
@ -65,13 +65,13 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let bootstrap_leader_keypair_file = matches.value_of("bootstrap_leader_keypair_file").unwrap();
|
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 ledger_path = matches.value_of("ledger_path").unwrap();
|
||||||
let mint_keypair_file = matches.value_of("mint_keypair_file").unwrap();
|
let mint_keypair_file = matches.value_of("mint_keypair_file").unwrap();
|
||||||
let lamports = value_t_or_exit!(matches, "lamports", u64);
|
let lamports = value_t_or_exit!(matches, "lamports", u64);
|
||||||
|
|
||||||
let bootstrap_leader_keypair = read_keypair(bootstrap_leader_keypair_file)?;
|
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 mint_keypair = read_keypair(mint_keypair_file)?;
|
||||||
|
|
||||||
let (mut genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(
|
let (mut genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(
|
||||||
|
@ -80,7 +80,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
BOOTSTRAP_LEADER_LAMPORTS,
|
BOOTSTRAP_LEADER_LAMPORTS,
|
||||||
);
|
);
|
||||||
genesis_block.mint_id = mint_keypair.pubkey();
|
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
|
genesis_block
|
||||||
.native_instruction_processors
|
.native_instruction_processors
|
||||||
.extend_from_slice(&[
|
.extend_from_slice(&[
|
||||||
|
|
|
@ -64,14 +64,14 @@ tune_system
|
||||||
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger verify
|
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger verify
|
||||||
|
|
||||||
bootstrap_leader_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json
|
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_vote_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json
|
||||||
bootstrap_leader_staker_id=$($solana_wallet --keypair "$bootstrap_leader_staker_id_path" address)
|
bootstrap_leader_vote_id=$($solana_wallet --keypair "$bootstrap_leader_vote_id_path" address)
|
||||||
|
|
||||||
trap 'kill "$pid" && wait "$pid"' INT TERM ERR
|
trap 'kill "$pid" && wait "$pid"' INT TERM ERR
|
||||||
$program \
|
$program \
|
||||||
--identity "$bootstrap_leader_id_path" \
|
--identity "$bootstrap_leader_id_path" \
|
||||||
--voting-keypair "$bootstrap_leader_staker_id_path" \
|
--voting-keypair "$bootstrap_leader_vote_id_path" \
|
||||||
--staking-account "$bootstrap_leader_staker_id" \
|
--vote-account "$bootstrap_leader_vote_id" \
|
||||||
--ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger \
|
--ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger \
|
||||||
--accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts \
|
--accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts \
|
||||||
--rpc-port 8899 \
|
--rpc-port 8899 \
|
||||||
|
|
|
@ -142,42 +142,32 @@ airdrop() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_fullnode_staking() {
|
setup_vote_account() {
|
||||||
declare drone_address=$1
|
declare drone_address=$1
|
||||||
declare fullnode_id_path=$2
|
declare node_id_path=$2
|
||||||
declare staker_id_path=$3
|
declare vote_id_path=$3
|
||||||
|
|
||||||
declare fullnode_id
|
declare node_id
|
||||||
fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address)
|
node_id=$($solana_wallet --keypair "$node_id_path" address)
|
||||||
|
|
||||||
declare staker_id
|
declare vote_id
|
||||||
staker_id=$($solana_wallet --keypair "$staker_id_path" address)
|
vote_id=$($solana_wallet --keypair "$vote_id_path" address)
|
||||||
|
|
||||||
if [[ -f "$staker_id_path".configured ]]; then
|
if [[ -f "$vote_id_path".configured ]]; then
|
||||||
echo "Staking account has already been configured"
|
echo "Vote account has already been configured"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# A fullnode requires 43 lamports to function:
|
# A fullnode requires 43 lamports to function:
|
||||||
# - one lamport to keep the node identity public key valid. TODO: really??
|
# - one lamport to keep the node identity public key valid. TODO: really??
|
||||||
# - 42 more for the staker account we fund
|
# - 42 more for the vote account we fund
|
||||||
airdrop "$fullnode_id_path" "$drone_address" 43 || return $?
|
airdrop "$node_id_path" "$drone_address" 43 || return $?
|
||||||
|
|
||||||
# A little wrong, fund the staking account from the
|
# Fund the vote account from the node, with the node as the node_id
|
||||||
# to the node. Maybe next time consider doing this the opposite
|
$solana_wallet --keypair "$node_id_path" --host "$drone_address" \
|
||||||
# way or use an ephemeral account
|
create-vote-account "$vote_id" "$node_id" 42 || return $?
|
||||||
$solana_wallet --keypair "$fullnode_id_path" --host "$drone_address" \
|
|
||||||
create-staking-account "$staker_id" 42 || return $?
|
|
||||||
|
|
||||||
# as the staker, set the node as the delegate and the staker as
|
touch "$vote_id_path".configured
|
||||||
# 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
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,16 +114,16 @@ if ((!self_setup)); then
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
fullnode_id_path=$SOLANA_CONFIG_DIR/fullnode-id.json
|
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
|
ledger_config_dir=$SOLANA_CONFIG_DIR/fullnode-ledger
|
||||||
accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts
|
accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts
|
||||||
else
|
else
|
||||||
mkdir -p "$SOLANA_CONFIG_DIR"
|
mkdir -p "$SOLANA_CONFIG_DIR"
|
||||||
fullnode_id_path=$SOLANA_CONFIG_DIR/fullnode-id-x$self_setup_label.json
|
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_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.."
|
echo "Finding a port.."
|
||||||
# Find an available port in the range 9100-9899
|
# 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
|
accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts-x$self_setup_label
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address)
|
fullnode_vote_id=$($solana_wallet --keypair "$fullnode_vote_id_path" address)
|
||||||
fullnode_staker_id=$($solana_wallet --keypair "$fullnode_staker_id_path" address)
|
|
||||||
|
|
||||||
|
|
||||||
[[ -r $fullnode_id_path ]] || {
|
[[ -r $fullnode_id_path ]] || {
|
||||||
|
@ -192,8 +191,8 @@ while true; do
|
||||||
$program \
|
$program \
|
||||||
--gossip-port "$gossip_port" \
|
--gossip-port "$gossip_port" \
|
||||||
--identity "$fullnode_id_path" \
|
--identity "$fullnode_id_path" \
|
||||||
--voting-keypair "$fullnode_staker_id_path" \
|
--voting-keypair "$fullnode_vote_id_path" \
|
||||||
--staking-account "$fullnode_staker_id" \
|
--vote-account "$fullnode_vote_id" \
|
||||||
--network "$leader_address" \
|
--network "$leader_address" \
|
||||||
--ledger "$ledger_config_dir" \
|
--ledger "$ledger_config_dir" \
|
||||||
--accounts "$accounts_config_dir" \
|
--accounts "$accounts_config_dir" \
|
||||||
|
@ -204,7 +203,7 @@ while true; do
|
||||||
oom_score_adj "$pid" 1000
|
oom_score_adj "$pid" 1000
|
||||||
|
|
||||||
if ((setup_stakes)); then
|
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
|
fi
|
||||||
set +x
|
set +x
|
||||||
|
|
||||||
|
|
|
@ -76,10 +76,10 @@ if $bootstrap_leader; then
|
||||||
set -x
|
set -x
|
||||||
$solana_keygen -o "$SOLANA_CONFIG_DIR"/mint-id.json
|
$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-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 \
|
$solana_genesis \
|
||||||
--bootstrap-leader-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json \
|
--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 \
|
--ledger "$SOLANA_RSYNC_CONFIG_DIR"/ledger \
|
||||||
--mint "$SOLANA_CONFIG_DIR"/mint-id.json \
|
--mint "$SOLANA_CONFIG_DIR"/mint-id.json \
|
||||||
--lamports "$lamports"
|
--lamports "$lamports"
|
||||||
|
@ -91,6 +91,6 @@ if $fullnode; then
|
||||||
(
|
(
|
||||||
set -x
|
set -x
|
||||||
$solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-id.json
|
$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
|
fi
|
||||||
|
|
|
@ -153,20 +153,21 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::id;
|
use crate::id;
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_vote_api::vote_instruction::Vote;
|
use solana_vote_api::vote_state::{self, Vote};
|
||||||
use solana_vote_api::vote_state::create_vote_account;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_delegate_stake() {
|
fn test_stake_delegate_stake() {
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
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 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);
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
|
@ -206,7 +207,7 @@ mod tests {
|
||||||
|
|
||||||
// put a credit in the vote_state
|
// put a credit in the vote_state
|
||||||
while vote_state.credits() == 0 {
|
while vote_state.credits() == 0 {
|
||||||
vote_state.process_vote(Vote::new(vote_i));
|
vote_state.process_vote(&Vote::new(vote_i));
|
||||||
vote_i += 1;
|
vote_i += 1;
|
||||||
}
|
}
|
||||||
// this guy can't collect now, not enough stake to get paid on 1 credit
|
// 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
|
// put more credit in the vote_state
|
||||||
while vote_state.credits() < 10 {
|
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_i += 1;
|
||||||
}
|
}
|
||||||
vote_state.commission = 0;
|
vote_state.commission = 0;
|
||||||
|
@ -252,11 +253,12 @@ mod tests {
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
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 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);
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
|
@ -294,7 +296,7 @@ mod tests {
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// move the vote account forward
|
// 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();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
// now, no lamports in the pool!
|
// now, no lamports in the pool!
|
||||||
|
@ -322,11 +324,12 @@ mod tests {
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
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 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);
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
|
@ -349,7 +352,7 @@ mod tests {
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
// go back in time, previous state had 1000 votes
|
// 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();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
// voter credits lower than stake_delegate credits... TODO: is this an error?
|
// 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_keypair = Keypair::new();
|
||||||
let vote1_pubkey = vote1_keypair.pubkey();
|
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);
|
let mut vote1_keyed_account = KeyedAccount::new(&vote1_pubkey, false, &mut vote1_account);
|
||||||
vote1_keyed_account.set_state(&vote_state).unwrap();
|
vote1_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod vote_instruction;
|
pub mod vote_instruction;
|
||||||
pub mod vote_processor;
|
|
||||||
pub mod vote_state;
|
pub mod vote_state;
|
||||||
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
|
@ -1,68 +1,50 @@
|
||||||
|
//! Vote program
|
||||||
|
//! Receive and processes votes from validators
|
||||||
|
|
||||||
use crate::id;
|
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 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::pubkey::Pubkey;
|
||||||
use solana_sdk::system_instruction;
|
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)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum VoteInstruction {
|
pub enum VoteInstruction {
|
||||||
/// Initialize the VoteState for this `vote account`
|
/// Initialize the VoteState for this `vote account`
|
||||||
/// * Instruction::keys[0] - the new "vote account" to be associated with the delegate
|
/// takes a node_id and commission
|
||||||
InitializeAccount,
|
InitializeAccount(Pubkey, u32),
|
||||||
|
|
||||||
/// `Delegate` or `Assign` a vote account to a particular node
|
|
||||||
DelegateStake(Pubkey),
|
|
||||||
|
|
||||||
/// Authorize a voter to send signed votes.
|
/// Authorize a voter to send signed votes.
|
||||||
AuthorizeVoter(Pubkey),
|
AuthorizeVoter(Pubkey),
|
||||||
|
|
||||||
Vote(Vote),
|
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)];
|
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(
|
Instruction::new(
|
||||||
id(),
|
id(),
|
||||||
&VoteInstruction::DelegateStake(*delegate_id),
|
&VoteInstruction::InitializeAccount(*node_id, commission),
|
||||||
account_metas,
|
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 {
|
pub fn authorize_voter(vote_id: &Pubkey, authorized_voter_id: &Pubkey) -> Instruction {
|
||||||
let account_metas = vec![AccountMeta::new(*vote_id, true)];
|
let account_metas = vec![AccountMeta::new(*vote_id, true)];
|
||||||
Instruction::new(
|
Instruction::new(
|
||||||
|
@ -76,3 +58,161 @@ pub fn vote(vote_id: &Pubkey, vote: Vote) -> Instruction {
|
||||||
let account_metas = vec![AccountMeta::new(*vote_id, true)];
|
let account_metas = vec![AccountMeta::new(*vote_id, true)];
|
||||||
Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas)
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,11 @@
|
||||||
//! Vote stte
|
//! Vote state, vote program
|
||||||
//! Receive and processes votes from validators
|
//! Receive and processes votes from validators
|
||||||
|
use crate::id;
|
||||||
use crate::vote_instruction::Vote;
|
|
||||||
use crate::{check_id, id};
|
|
||||||
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
|
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
|
||||||
use log::*;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use solana_sdk::account::{Account, KeyedAccount};
|
use solana_sdk::account::{Account, KeyedAccount};
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
|
use solana_sdk::instruction_processor_utils::State;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
@ -15,6 +13,19 @@ use std::collections::VecDeque;
|
||||||
pub const MAX_LOCKOUT_HISTORY: usize = 31;
|
pub const MAX_LOCKOUT_HISTORY: usize = 31;
|
||||||
pub const INITIAL_LOCKOUT: usize = 2;
|
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)]
|
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Lockout {
|
pub struct Lockout {
|
||||||
pub slot: u64,
|
pub slot: u64,
|
||||||
|
@ -47,7 +58,7 @@ impl Lockout {
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||||
pub struct VoteState {
|
pub struct VoteState {
|
||||||
pub votes: VecDeque<Lockout>,
|
pub votes: VecDeque<Lockout>,
|
||||||
pub delegate_id: Pubkey,
|
pub node_id: Pubkey,
|
||||||
pub authorized_voter_id: Pubkey,
|
pub authorized_voter_id: Pubkey,
|
||||||
/// fraction of std::u32::MAX that represents what part of a rewards
|
/// fraction of std::u32::MAX that represents what part of a rewards
|
||||||
/// payout should be given to this VoteAccount
|
/// payout should be given to this VoteAccount
|
||||||
|
@ -57,24 +68,23 @@ pub struct VoteState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 votes = VecDeque::new();
|
||||||
let credits = 0;
|
let credits = 0;
|
||||||
let root_slot = None;
|
let root_slot = None;
|
||||||
let commission = 0;
|
|
||||||
Self {
|
Self {
|
||||||
votes,
|
votes,
|
||||||
delegate_id: *staker_id,
|
node_id: *node_id,
|
||||||
authorized_voter_id: *staker_id,
|
authorized_voter_id: *vote_id,
|
||||||
credits,
|
credits,
|
||||||
commission,
|
commission,
|
||||||
root_slot,
|
root_slot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_size() -> usize {
|
pub fn size_of() -> usize {
|
||||||
// Upper limit on the size of the Vote State. Equal to
|
// 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();
|
let mut vote_state = Self::default();
|
||||||
vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]);
|
vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]);
|
||||||
vote_state.root_slot = Some(std::u64::MAX);
|
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
|
// Ignore votes for slots earlier than we already have votes for
|
||||||
if self
|
if self
|
||||||
.votes
|
.votes
|
||||||
|
@ -148,11 +158,6 @@ impl VoteState {
|
||||||
self.credits
|
self.credits
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear any credits.
|
|
||||||
pub fn clear_credits(&mut self) {
|
|
||||||
self.credits = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_expired_votes(&mut self, slot: u64) {
|
fn pop_expired_votes(&mut self, slot: u64) {
|
||||||
loop {
|
loop {
|
||||||
if self.votes.back().map_or(false, |v| v.is_expired(slot)) {
|
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,
|
/// Authorize the given pubkey to sign votes. This may be called multiple times,
|
||||||
/// but will implicitly withdraw authorization from the previously authorized
|
/// but will implicitly withdraw authorization from the previously authorized
|
||||||
/// voter. The default voter is the owner of the vote account's pubkey.
|
/// voter. The default voter is the owner of the vote account's pubkey.
|
||||||
pub fn authorize_voter(
|
pub fn authorize_voter(
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
vote_account: &mut KeyedAccount,
|
||||||
voter_id: &Pubkey,
|
other_signers: &[KeyedAccount],
|
||||||
|
authorized_voter_id: &Pubkey,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
if !check_id(&keyed_accounts[0].account.owner) {
|
let mut vote_state: VoteState = vote_account.state()?;
|
||||||
error!("account[0] is not assigned to the VOTE_PROGRAM");
|
|
||||||
Err(InstructionError::InvalidArgument)?;
|
// 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() {
|
vote_state.authorized_voter_id = *authorized_voter_id;
|
||||||
error!("account[0] should sign the transaction");
|
vote_account.set_state(&vote_state)
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the vote_state for a vote account
|
/// 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
|
/// 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
|
/// that the transaction must be signed by the staker's keys
|
||||||
pub fn initialize_account(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> {
|
pub fn initialize_account(
|
||||||
if !check_id(&keyed_accounts[0].account.owner) {
|
vote_account: &mut KeyedAccount,
|
||||||
error!("account[0] is not assigned to the VOTE_PROGRAM");
|
node_id: &Pubkey,
|
||||||
Err(InstructionError::InvalidArgument)?;
|
commission: u32,
|
||||||
}
|
) -> Result<(), InstructionError> {
|
||||||
|
let vote_state: VoteState = vote_account.state()?;
|
||||||
|
|
||||||
let staker_id = keyed_accounts[0].unsigned_key();
|
if vote_state.authorized_voter_id != Pubkey::default() {
|
||||||
let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data);
|
return Err(InstructionError::AccountAlreadyInitialized);
|
||||||
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 {
|
vote_account.set_state(&VoteState::new(
|
||||||
error!("account[0] does not have valid data");
|
vote_account.unsigned_key(),
|
||||||
Err(InstructionError::InvalidAccountData)?;
|
node_id,
|
||||||
}
|
commission,
|
||||||
|
))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_vote(
|
pub fn process_vote(
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
vote_account: &mut KeyedAccount,
|
||||||
vote: Vote,
|
other_signers: &[KeyedAccount],
|
||||||
|
vote: &Vote,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
if !check_id(&keyed_accounts[0].account.owner) {
|
let mut vote_state: VoteState = vote_account.state()?;
|
||||||
error!("account[0] is not assigned to the VOTE_PROGRAM");
|
|
||||||
Err(InstructionError::InvalidArgument)?;
|
if vote_state.authorized_voter_id == Pubkey::default() {
|
||||||
|
return Err(InstructionError::UninitializedAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vote_state = VoteState::deserialize(&keyed_accounts[0].account.data)?;
|
let authorized = Some(&vote_state.authorized_voter_id);
|
||||||
|
// find a signer that matches the authorized_voter_id
|
||||||
// If no voter was authorized, expect account[0] to be the signer, otherwise account[1].
|
if vote_account.signer_key() != authorized
|
||||||
let signer_index = if vote_state.authorized_voter_id == *keyed_accounts[0].unsigned_key() {
|
&& other_signers
|
||||||
0
|
.iter()
|
||||||
} else {
|
.all(|account| account.signer_key() != authorized)
|
||||||
1
|
{
|
||||||
};
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
|
|
||||||
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)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vote_state.process_vote(vote);
|
vote_state.process_vote(vote);
|
||||||
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
|
vote_account.set_state(&vote_state)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_credits(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> {
|
// utility function, used by Bank, tests
|
||||||
if !check_id(&keyed_accounts[0].account.owner) {
|
pub fn create_account(
|
||||||
error!("account[0] is not assigned to the VOTE_PROGRAM");
|
vote_id: &Pubkey,
|
||||||
Err(InstructionError::InvalidArgument)?;
|
node_id: &Pubkey,
|
||||||
|
commission: u32,
|
||||||
|
lamports: u64,
|
||||||
|
) -> Account {
|
||||||
|
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
|
||||||
|
|
||||||
|
initialize_account(
|
||||||
|
&mut KeyedAccount::new(vote_id, false, &mut vote_account),
|
||||||
|
node_id,
|
||||||
|
commission,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
vote_account
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vote_state = VoteState::deserialize(&keyed_accounts[0].account.data)?;
|
// utility function, used by Bank, tests
|
||||||
vote_state.clear_credits();
|
pub fn vote(
|
||||||
vote_state.serialize(&mut keyed_accounts[0].account.data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_vote_account(lamports: u64) -> Account {
|
|
||||||
let space = VoteState::max_size();
|
|
||||||
Account::new(lamports, space, &id())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initialize_and_deserialize(
|
|
||||||
vote_id: &Pubkey,
|
vote_id: &Pubkey,
|
||||||
vote_account: &mut Account,
|
vote_account: &mut Account,
|
||||||
|
vote: &Vote,
|
||||||
) -> Result<VoteState, InstructionError> {
|
) -> Result<VoteState, InstructionError> {
|
||||||
let mut keyed_accounts = [KeyedAccount::new(vote_id, false, vote_account)];
|
process_vote(
|
||||||
initialize_account(&mut keyed_accounts)?;
|
&mut KeyedAccount::new(vote_id, true, vote_account),
|
||||||
let vote_state = VoteState::deserialize(&vote_account.data).unwrap();
|
&[],
|
||||||
Ok(vote_state)
|
vote,
|
||||||
}
|
)?;
|
||||||
|
vote_account.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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::vote_state;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_initialize_vote_account() {
|
fn test_initialize_vote_account() {
|
||||||
let vote_account_id = Pubkey::new_rand();
|
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 node_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,
|
|
||||||
)];
|
|
||||||
|
|
||||||
//init should pass
|
//init should pass
|
||||||
keyed_accounts[0] = KeyedAccount::new(&vote_account_id, false, &mut vote_account);
|
let mut vote_account = KeyedAccount::new(&vote_account_id, false, &mut vote_account);
|
||||||
let res = initialize_account(&mut keyed_accounts);
|
let res = initialize_account(&mut vote_account, &node_id, 0);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
// reinit should fail
|
// reinit should fail
|
||||||
let res = initialize_account(&mut keyed_accounts);
|
let res = initialize_account(&mut vote_account, &node_id, 0);
|
||||||
assert_eq!(res, Err(InstructionError::InvalidAccountData));
|
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]
|
#[test]
|
||||||
fn test_vote_serialize() {
|
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();
|
let mut vote_state = VoteState::default();
|
||||||
vote_state
|
vote_state
|
||||||
.votes
|
.votes
|
||||||
.resize(MAX_LOCKOUT_HISTORY, Lockout::default());
|
.resize(MAX_LOCKOUT_HISTORY, Lockout::default());
|
||||||
|
assert!(vote_state.serialize(&mut buffer[0..4]).is_err());
|
||||||
vote_state.serialize(&mut buffer).unwrap();
|
vote_state.serialize(&mut buffer).unwrap();
|
||||||
assert_eq!(VoteState::deserialize(&buffer).unwrap(), vote_state);
|
assert_eq!(VoteState::deserialize(&buffer).unwrap(), vote_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_voter_registration() {
|
fn test_voter_registration() {
|
||||||
let vote_id = Pubkey::new_rand();
|
let (vote_id, vote_account) = create_test_account();
|
||||||
let mut vote_account = create_vote_account(100);
|
|
||||||
|
|
||||||
let vote_state = initialize_and_deserialize(&vote_id, &mut vote_account).unwrap();
|
let vote_state: VoteState = vote_account.state().unwrap();
|
||||||
assert_eq!(vote_state.delegate_id, vote_id);
|
assert_eq!(vote_state.authorized_voter_id, vote_id);
|
||||||
assert!(vote_state.votes.is_empty());
|
assert!(vote_state.votes.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote() {
|
fn test_vote() {
|
||||||
let vote_id = Pubkey::new_rand();
|
let (vote_id, mut vote_account) = create_test_account();
|
||||||
let mut vote_account = create_vote_account(100);
|
|
||||||
initialize_and_deserialize(&vote_id, &mut vote_account).unwrap();
|
|
||||||
|
|
||||||
let vote = Vote::new(1);
|
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.votes, vec![Lockout::new(&vote)]);
|
||||||
assert_eq!(vote_state.credits(), 0);
|
assert_eq!(vote_state.credits(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_signature() {
|
fn test_vote_signature() {
|
||||||
let vote_id = Pubkey::new_rand();
|
let (vote_id, mut vote_account) = create_test_account();
|
||||||
let mut vote_account = create_vote_account(100);
|
|
||||||
initialize_and_deserialize(&vote_id, &mut vote_account).unwrap();
|
|
||||||
|
|
||||||
let vote = Vote::new(1);
|
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);
|
// unsigned
|
||||||
assert_eq!(res, Err(InstructionError::InvalidArgument));
|
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]
|
#[test]
|
||||||
fn test_vote_without_initialization() {
|
fn test_vote_without_initialization() {
|
||||||
let vote_id = Pubkey::new_rand();
|
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_state::vote(&vote_id, &mut vote_account, &Vote::new(1));
|
||||||
let res = vote_and_deserialize(&vote_id, &mut vote_account, vote.clone());
|
assert_eq!(res, Err(InstructionError::UninitializedAccount));
|
||||||
assert_eq!(res, Err(InstructionError::InvalidArgument));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_lockout() {
|
fn test_vote_lockout() {
|
||||||
let voter_id = Pubkey::new_rand();
|
let (_vote_id, vote_account) = create_test_account();
|
||||||
let mut vote_state = VoteState::new(&voter_id);
|
|
||||||
|
let mut vote_state: VoteState = vote_account.state().unwrap();
|
||||||
|
|
||||||
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
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
|
// 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
|
// the root_slot should change to the
|
||||||
// second vote
|
// second vote
|
||||||
let top_vote = vote_state.votes.front().unwrap().slot;
|
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(),
|
vote_state.votes.back().unwrap().expiration_slot(),
|
||||||
));
|
));
|
||||||
assert_eq!(Some(top_vote), vote_state.root_slot);
|
assert_eq!(Some(top_vote), vote_state.root_slot);
|
||||||
|
|
||||||
// Expire everything except the first vote
|
// Expire everything except the first vote
|
||||||
let vote = Vote::new(vote_state.votes.front().unwrap().expiration_slot());
|
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
|
// First vote and new vote are both stored for a total of 2 votes
|
||||||
assert_eq!(vote_state.votes.len(), 2);
|
assert_eq!(vote_state.votes.len(), 2);
|
||||||
}
|
}
|
||||||
|
@ -443,11 +457,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_double_lockout_after_expiration() {
|
fn test_vote_double_lockout_after_expiration() {
|
||||||
let voter_id = Pubkey::new_rand();
|
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 {
|
for i in 0..3 {
|
||||||
let vote = Vote::new(i as u64);
|
let vote = Vote::new(i as u64);
|
||||||
vote_state.process_vote(vote);
|
vote_state.process_vote(&vote);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_lockouts(&vote_state);
|
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
|
// 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
|
// vote stack is unchanged, so none of the previous votes should have
|
||||||
// doubled in lockout
|
// 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);
|
check_lockouts(&vote_state);
|
||||||
|
|
||||||
// Vote again, this time the vote stack depth increases, so the lockouts should
|
// Vote again, this time the vote stack depth increases, so the lockouts should
|
||||||
// double for everybody
|
// 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);
|
check_lockouts(&vote_state);
|
||||||
|
|
||||||
// Vote again, this time the vote stack depth increases, so the lockouts should
|
// Vote again, this time the vote stack depth increases, so the lockouts should
|
||||||
// double for everybody
|
// 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);
|
check_lockouts(&vote_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expire_multiple_votes() {
|
fn test_expire_multiple_votes() {
|
||||||
let voter_id = Pubkey::new_rand();
|
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 {
|
for i in 0..3 {
|
||||||
let vote = Vote::new(i as u64);
|
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);
|
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
||||||
|
|
||||||
// Expire the second and third votes
|
// Expire the second and third votes
|
||||||
let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1;
|
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);
|
assert_eq!(vote_state.votes.len(), 2);
|
||||||
|
|
||||||
// Check that the old votes expired
|
// Check that the old votes expired
|
||||||
|
@ -491,7 +505,7 @@ mod tests {
|
||||||
assert_eq!(vote_state.votes[1].slot, expire_slot);
|
assert_eq!(vote_state.votes[1].slot, expire_slot);
|
||||||
|
|
||||||
// Process one more vote
|
// 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
|
// Confirmation count for the older first vote should remain unchanged
|
||||||
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
||||||
|
@ -504,31 +518,29 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_credits() {
|
fn test_vote_credits() {
|
||||||
let voter_id = Pubkey::new_rand();
|
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 {
|
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);
|
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);
|
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);
|
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);
|
assert_eq!(vote_state.credits(), 3);
|
||||||
vote_state.clear_credits();
|
|
||||||
assert_eq!(vote_state.credits(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_duplicate_vote() {
|
fn test_duplicate_vote() {
|
||||||
let voter_id = Pubkey::new_rand();
|
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);
|
||||||
vote_state.process_vote(Vote::new(0));
|
vote_state.process_vote(&Vote::new(0));
|
||||||
vote_state.process_vote(Vote::new(1));
|
vote_state.process_vote(&Vote::new(1));
|
||||||
vote_state.process_vote(Vote::new(0));
|
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(0).unwrap().slot, 1);
|
||||||
assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
|
assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
|
||||||
assert!(vote_state.nth_recent_vote(2).is_none());
|
assert!(vote_state.nth_recent_vote(2).is_none());
|
||||||
|
@ -537,9 +549,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nth_recent_vote() {
|
fn test_nth_recent_vote() {
|
||||||
let voter_id = Pubkey::new_rand();
|
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 {
|
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) {
|
for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -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);
|
solana_sdk::solana_entrypoint!(process_instruction);
|
||||||
|
|
7
run.sh
7
run.sh
|
@ -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 airdrop 42
|
||||||
solana-wallet \
|
solana-wallet \
|
||||||
--keypair "$dataDir"/config/leader-keypair.json \
|
--keypair "$dataDir"/config/leader-keypair.json \
|
||||||
create-staking-account "$leaderStakingAccountPubkey" 42
|
create-vote-account "$leaderStakingAccountPubkey" "$leaderPubkey" 42
|
||||||
solana-wallet \
|
|
||||||
--keypair "$dataDir"/config/leader-staking-account-keypair.json \
|
|
||||||
configure-staking-account \
|
|
||||||
--delegate-account "$leaderPubkey" \
|
|
||||||
--authorize-voter "$leaderStakingAccountPubkey"
|
|
||||||
solana-wallet --keypair "$dataDir"/config/leader-keypair.json balance
|
solana-wallet --keypair "$dataDir"/config/leader-keypair.json balance
|
||||||
|
|
||||||
wait "$fullnode"
|
wait "$fullnode"
|
||||||
|
|
|
@ -23,8 +23,7 @@ use solana_sdk::signature::{Keypair, Signature};
|
||||||
use solana_sdk::system_transaction;
|
use solana_sdk::system_transaction;
|
||||||
use solana_sdk::timing::{duration_as_ms, duration_as_us, MAX_RECENT_BLOCKHASHES};
|
use solana_sdk::timing::{duration_as_ms, duration_as_us, MAX_RECENT_BLOCKHASHES};
|
||||||
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
use solana_sdk::transaction::{Result, Transaction, TransactionError};
|
||||||
use solana_vote_api::vote_instruction::Vote;
|
use solana_vote_api::vote_state::{self, Vote};
|
||||||
use solana_vote_api::vote_state::{Lockout, VoteState};
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
@ -321,18 +320,18 @@ impl Bank {
|
||||||
|
|
||||||
// Construct a vote account for the bootstrap_leader such that the leader_scheduler
|
// 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
|
// will be forced to select it as the leader for height 0
|
||||||
let mut bootstrap_leader_vote_account = Account {
|
let mut bootstrap_leader_vote_account = vote_state::create_account(
|
||||||
lamports: bootstrap_leader_stake,
|
&genesis_block.bootstrap_leader_vote_account_id,
|
||||||
data: vec![0; VoteState::max_size() as usize],
|
&genesis_block.bootstrap_leader_id,
|
||||||
owner: solana_vote_api::id(),
|
0,
|
||||||
executable: false,
|
bootstrap_leader_stake,
|
||||||
};
|
);
|
||||||
|
|
||||||
let mut vote_state = VoteState::new(&genesis_block.bootstrap_leader_id);
|
vote_state::vote(
|
||||||
vote_state.votes.push_back(Lockout::new(&Vote::new(0)));
|
&genesis_block.bootstrap_leader_vote_account_id,
|
||||||
vote_state.authorized_voter_id = genesis_block.bootstrap_leader_vote_account_id;
|
&mut bootstrap_leader_vote_account,
|
||||||
vote_state
|
&Vote::new(0),
|
||||||
.serialize(&mut bootstrap_leader_vote_account.data)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.store(
|
self.store(
|
||||||
|
@ -1037,6 +1036,7 @@ mod tests {
|
||||||
use solana_sdk::system_instruction;
|
use solana_sdk::system_instruction;
|
||||||
use solana_sdk::system_transaction;
|
use solana_sdk::system_transaction;
|
||||||
use solana_vote_api::vote_instruction;
|
use solana_vote_api::vote_instruction;
|
||||||
|
use solana_vote_api::vote_state::VoteState;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bank_new() {
|
fn test_bank_new() {
|
||||||
|
@ -1654,7 +1654,7 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(pubkey, account)| {
|
.filter_map(|(pubkey, account)| {
|
||||||
if let Ok(vote_state) = VoteState::deserialize(&account.data) {
|
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))
|
Some((*pubkey, true))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -1886,8 +1886,13 @@ mod tests {
|
||||||
// to have a vote account
|
// to have a vote account
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let instructions =
|
let instructions = vote_instruction::create_account(
|
||||||
vote_instruction::create_account(&mint_keypair.pubkey(), &vote_keypair.pubkey(), 10);
|
&mint_keypair.pubkey(),
|
||||||
|
&vote_keypair.pubkey(),
|
||||||
|
&mint_keypair.pubkey(),
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
|
||||||
let transaction = Transaction::new_signed_instructions(
|
let transaction = Transaction::new_signed_instructions(
|
||||||
&[&mint_keypair],
|
&[&mint_keypair],
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use clap::{
|
use clap::{
|
||||||
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgGroup, ArgMatches,
|
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand,
|
||||||
SubCommand,
|
|
||||||
};
|
};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
|
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
|
||||||
|
@ -207,33 +206,20 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("configure-staking-account")
|
SubCommand::with_name("authorize-voter")
|
||||||
.about("Configure staking account for node")
|
.about("Authorize a different voter for this account")
|
||||||
.group(
|
|
||||||
ArgGroup::with_name("options")
|
|
||||||
.args(&["delegate", "authorize"])
|
|
||||||
.multiple(true)
|
|
||||||
.required(true),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("delegate")
|
Arg::with_name("authorized-voter-id")
|
||||||
.long("delegate-account")
|
.index(1)
|
||||||
.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")
|
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
.validator(is_pubkey)
|
.validator(is_pubkey)
|
||||||
.help("Vote signer to authorize"),
|
.help("Vote signer to authorize"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("create-staking-account")
|
SubCommand::with_name("create-vote-account")
|
||||||
.about("Create staking account for node")
|
.about("Create staking account for node")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("voting_account_id")
|
Arg::with_name("voting_account_id")
|
||||||
|
@ -245,13 +231,29 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
.help("Staking account address to fund"),
|
.help("Staking account address to fund"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("lamports")
|
Arg::with_name("node_id")
|
||||||
.index(2)
|
.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")
|
.value_name("NUM")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("The number of lamports to send to staking account"),
|
.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(
|
||||||
SubCommand::with_name("deploy")
|
SubCommand::with_name("deploy")
|
||||||
|
|
|
@ -36,8 +36,8 @@ pub enum WalletCommand {
|
||||||
Cancel(Pubkey),
|
Cancel(Pubkey),
|
||||||
Confirm(Signature),
|
Confirm(Signature),
|
||||||
// ConfigureStakingAccount(delegate_id, authorized_voter_id)
|
// ConfigureStakingAccount(delegate_id, authorized_voter_id)
|
||||||
ConfigureStakingAccount(Option<Pubkey>, Option<Pubkey>),
|
AuthorizeVoter(Pubkey),
|
||||||
CreateStakingAccount(Pubkey, u64),
|
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
|
||||||
Deploy(String),
|
Deploy(String),
|
||||||
GetTransactionCount,
|
GetTransactionCount,
|
||||||
// Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable)
|
// Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable)
|
||||||
|
@ -164,19 +164,23 @@ pub fn parse_command(
|
||||||
Err(WalletError::BadParameter("Invalid signature".to_string()))
|
Err(WalletError::BadParameter("Invalid signature".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
("configure-staking-account", Some(staking_config_matches)) => {
|
("authorize-voter", Some(matches)) => {
|
||||||
let delegate_id = pubkey_of(staking_config_matches, "delegate");
|
let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap();
|
||||||
let authorized_voter_id = pubkey_of(staking_config_matches, "authorize");
|
Ok(WalletCommand::AuthorizeVoter(authorized_voter_id))
|
||||||
Ok(WalletCommand::ConfigureStakingAccount(
|
|
||||||
delegate_id,
|
|
||||||
authorized_voter_id,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
("create-staking-account", Some(staking_matches)) => {
|
("create-vote-account", Some(matches)) => {
|
||||||
let voting_account_id = pubkey_of(staking_matches, "voting_account_id").unwrap();
|
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
|
||||||
let lamports = staking_matches.value_of("lamports").unwrap().parse()?;
|
let node_id = pubkey_of(matches, "node_id").unwrap();
|
||||||
Ok(WalletCommand::CreateStakingAccount(
|
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,
|
voting_account_id,
|
||||||
|
node_id,
|
||||||
|
commission,
|
||||||
lamports,
|
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,
|
rpc_client: &RpcClient,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
delegate_option: Option<Pubkey>,
|
authorized_voter_id: Pubkey,
|
||||||
authorized_voter_option: Option<Pubkey>,
|
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let recent_blockhash = rpc_client.get_recent_blockhash()?;
|
let recent_blockhash = rpc_client.get_recent_blockhash()?;
|
||||||
let mut ixs = vec![];
|
let ixs = vec![vote_instruction::authorize_voter(
|
||||||
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(),
|
&config.keypair.pubkey(),
|
||||||
&authorized_voter_id,
|
&authorized_voter_id,
|
||||||
));
|
)];
|
||||||
}
|
|
||||||
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
|
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)?;
|
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?;
|
||||||
Ok(signature_str.to_string())
|
Ok(signature_str.to_string())
|
||||||
|
@ -356,11 +351,18 @@ fn process_create_staking(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
voting_account_id: &Pubkey,
|
voting_account_id: &Pubkey,
|
||||||
|
node_id: &Pubkey,
|
||||||
|
commission: u32,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let recent_blockhash = rpc_client.get_recent_blockhash()?;
|
let recent_blockhash = rpc_client.get_recent_blockhash()?;
|
||||||
let ixs =
|
let ixs = vote_instruction::create_account(
|
||||||
vote_instruction::create_account(&config.keypair.pubkey(), voting_account_id, lamports);
|
&config.keypair.pubkey(),
|
||||||
|
voting_account_id,
|
||||||
|
node_id,
|
||||||
|
commission,
|
||||||
|
lamports,
|
||||||
|
);
|
||||||
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
|
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)?;
|
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?;
|
||||||
Ok(signature_str.to_string())
|
Ok(signature_str.to_string())
|
||||||
|
@ -617,18 +619,20 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
||||||
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
|
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
|
||||||
|
|
||||||
// Configure staking account already created
|
// Configure staking account already created
|
||||||
WalletCommand::ConfigureStakingAccount(delegate_option, authorized_voter_option) => {
|
WalletCommand::AuthorizeVoter(authorized_voter_id) => {
|
||||||
process_configure_staking(
|
process_authorize_voter(&rpc_client, config, authorized_voter_id)
|
||||||
&rpc_client,
|
|
||||||
config,
|
|
||||||
delegate_option,
|
|
||||||
authorized_voter_option,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create staking account
|
// Create staking account
|
||||||
WalletCommand::CreateStakingAccount(voting_account_id, lamports) => {
|
WalletCommand::CreateVoteAccount(voting_account_id, node_id, commission, lamports) => {
|
||||||
process_create_staking(&rpc_client, config, &voting_account_id, lamports)
|
process_create_staking(
|
||||||
|
&rpc_client,
|
||||||
|
config,
|
||||||
|
&voting_account_id,
|
||||||
|
&node_id,
|
||||||
|
commission,
|
||||||
|
lamports,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy a custom program to the chain
|
// Deploy a custom program to the chain
|
||||||
|
@ -723,7 +727,7 @@ pub fn request_and_confirm_airdrop(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use clap::{App, Arg, ArgGroup, SubCommand};
|
use clap::{App, Arg, SubCommand};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use solana_client::mock_rpc_client_request::SIGNATURE;
|
use solana_client::mock_rpc_client_request::SIGNATURE;
|
||||||
use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil};
|
use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil};
|
||||||
|
@ -802,31 +806,19 @@ mod tests {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("configure-staking-account")
|
SubCommand::with_name("authorize-voter")
|
||||||
.about("Configure staking account for node")
|
.about("Configure staking account for node")
|
||||||
.group(
|
|
||||||
ArgGroup::with_name("options")
|
|
||||||
.args(&["delegate", "authorize"])
|
|
||||||
.multiple(true)
|
|
||||||
.required(true),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("delegate")
|
Arg::with_name("authorized_voter_id")
|
||||||
.long("delegate-account")
|
.index(1)
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
.help("Address to delegate this vote account to"),
|
.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(
|
||||||
SubCommand::with_name("create-staking-account")
|
SubCommand::with_name("create-vote-account")
|
||||||
.about("Create staking account for node")
|
.about("Create staking account for node")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("voting_account_id")
|
Arg::with_name("voting_account_id")
|
||||||
|
@ -837,12 +829,27 @@ mod tests {
|
||||||
.help("Staking account address to fund"),
|
.help("Staking account address to fund"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("lamports")
|
Arg::with_name("node_id")
|
||||||
.index(2)
|
.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")
|
.value_name("NUM")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("The number of lamports to send to staking account"),
|
.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(
|
.subcommand(
|
||||||
|
@ -1004,42 +1011,42 @@ mod tests {
|
||||||
.get_matches_from(vec!["test", "confirm", "deadbeef"]);
|
.get_matches_from(vec!["test", "confirm", "deadbeef"]);
|
||||||
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
|
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
|
||||||
|
|
||||||
// Test ConfigureStakingAccount Subcommand
|
// Test AuthorizeVoter Subcommand
|
||||||
let second_pubkey = Pubkey::new_rand();
|
let test_authorize_voter =
|
||||||
let second_pubkey_string = format!("{}", second_pubkey);
|
test_commands
|
||||||
let test_configure_staking_account = test_commands.clone().get_matches_from(vec![
|
.clone()
|
||||||
"test",
|
.get_matches_from(vec!["test", "authorize-voter", &pubkey_string]);
|
||||||
"configure-staking-account",
|
|
||||||
"--delegate-account",
|
|
||||||
&pubkey_string,
|
|
||||||
"--authorize-voter",
|
|
||||||
&second_pubkey_string,
|
|
||||||
]);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command(&pubkey, &test_configure_staking_account).unwrap(),
|
parse_command(&pubkey, &test_authorize_voter).unwrap(),
|
||||||
WalletCommand::ConfigureStakingAccount(Some(pubkey), Some(second_pubkey))
|
WalletCommand::AuthorizeVoter(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)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test CreateStakingAccount SubCommand
|
// Test CreateVoteAccount SubCommand
|
||||||
let test_create_staking_account = test_commands.clone().get_matches_from(vec![
|
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",
|
"test",
|
||||||
"create-staking-account",
|
"create-vote-account",
|
||||||
&pubkey_string,
|
&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",
|
"50",
|
||||||
]);
|
]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command(&pubkey, &test_create_staking_account).unwrap(),
|
parse_command(&pubkey, &test_create_vote_account2).unwrap(),
|
||||||
WalletCommand::CreateStakingAccount(pubkey, 50)
|
WalletCommand::CreateVoteAccount(pubkey, node_id, 0, 50)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test Deploy Subcommand
|
// Test Deploy Subcommand
|
||||||
|
@ -1191,11 +1198,12 @@ mod tests {
|
||||||
assert_eq!(process_command(&config).unwrap(), "Confirmed");
|
assert_eq!(process_command(&config).unwrap(), "Confirmed");
|
||||||
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
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);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
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);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||||
|
|
||||||
|
@ -1300,10 +1308,10 @@ mod tests {
|
||||||
config.command = WalletCommand::Balance(config.keypair.pubkey());
|
config.command = WalletCommand::Balance(config.keypair.pubkey());
|
||||||
assert!(process_command(&config).is_err());
|
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());
|
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());
|
assert!(process_command(&config).is_err());
|
||||||
|
|
||||||
config.command = WalletCommand::GetTransactionCount;
|
config.command = WalletCommand::GetTransactionCount;
|
||||||
|
|
Loading…
Reference in New Issue