add authorized parameters to vote api (#6072)
* add authorized parameters to vote api * code review
This commit is contained in:
parent
62429585ba
commit
43795193c4
141
cli/src/vote.rs
141
cli/src/vote.rs
|
@ -15,13 +15,16 @@ use solana_sdk::{
|
|||
};
|
||||
use solana_vote_api::{
|
||||
vote_instruction::{self, VoteError},
|
||||
vote_state::VoteState,
|
||||
vote_state::{VoteAuthorize, VoteInit, VoteState},
|
||||
};
|
||||
|
||||
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
|
||||
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
||||
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
|
||||
let commission = value_of(&matches, "commission").unwrap_or(0);
|
||||
let authorized_voter = pubkey_of(matches, "authorized_voter").unwrap_or(vote_account_pubkey);
|
||||
let authorized_withdrawer =
|
||||
pubkey_of(matches, "authorized_withdrawer").unwrap_or(vote_account_pubkey);
|
||||
let lamports = matches
|
||||
.value_of("lamports")
|
||||
.unwrap()
|
||||
|
@ -29,21 +32,29 @@ pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<WalletComma
|
|||
.map_err(|err| WalletError::BadParameter(format!("Invalid lamports: {:?}", err)))?;
|
||||
Ok(WalletCommand::CreateVoteAccount(
|
||||
vote_account_pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter,
|
||||
authorized_withdrawer,
|
||||
commission,
|
||||
},
|
||||
lamports,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn parse_vote_authorize_voter(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
|
||||
pub fn parse_vote_authorize(
|
||||
matches: &ArgMatches<'_>,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Result<WalletCommand, WalletError> {
|
||||
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
||||
let authorized_voter_keypair = keypair_of(matches, "authorized_voter_keypair_file").unwrap();
|
||||
let new_authorized_voter_pubkey = pubkey_of(matches, "new_authorized_voter_pubkey").unwrap();
|
||||
let authorized_keypair = keypair_of(matches, "authorized_keypair_file").unwrap();
|
||||
let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap();
|
||||
|
||||
Ok(WalletCommand::AuthorizeVoter(
|
||||
Ok(WalletCommand::VoteAuthorize(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_keypair,
|
||||
new_authorized_voter_pubkey,
|
||||
authorized_keypair,
|
||||
new_authorized_pubkey,
|
||||
vote_authorize,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -58,13 +69,12 @@ pub fn process_create_vote_account(
|
|||
rpc_client: &RpcClient,
|
||||
config: &WalletConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
node_pubkey: &Pubkey,
|
||||
commission: u8,
|
||||
vote_init: &VoteInit,
|
||||
lamports: u64,
|
||||
) -> ProcessResult {
|
||||
check_unique_pubkeys(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(node_pubkey, "node_pubkey".to_string()),
|
||||
(&vote_init.node_pubkey, "node_pubkey".to_string()),
|
||||
)?;
|
||||
check_unique_pubkeys(
|
||||
(&config.keypair.pubkey(), "wallet keypair".to_string()),
|
||||
|
@ -73,8 +83,7 @@ pub fn process_create_vote_account(
|
|||
let ixs = vote_instruction::create_account(
|
||||
&config.keypair.pubkey(),
|
||||
vote_account_pubkey,
|
||||
node_pubkey,
|
||||
commission,
|
||||
vote_init,
|
||||
lamports,
|
||||
);
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
@ -84,36 +93,35 @@ pub fn process_create_vote_account(
|
|||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
pub fn process_authorize_voter(
|
||||
pub fn process_vote_authorize(
|
||||
rpc_client: &RpcClient,
|
||||
config: &WalletConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
authorized_voter_keypair: &Keypair,
|
||||
new_authorized_voter_pubkey: &Pubkey,
|
||||
authorized_keypair: &Keypair,
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> ProcessResult {
|
||||
check_unique_pubkeys(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(
|
||||
new_authorized_voter_pubkey,
|
||||
"new_authorized_voter_pubkey".to_string(),
|
||||
),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::authorize_voter(
|
||||
let ixs = vec![vote_instruction::authorize(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized_voter_keypair.pubkey(), // current authorized voter (often the vote account itself)
|
||||
new_authorized_voter_pubkey, // new vote signer
|
||||
&authorized_keypair.pubkey(), // current authorized voter (often the vote account itself)
|
||||
new_authorized_pubkey, // new vote signer
|
||||
vote_authorize, // vote or withdraw
|
||||
)];
|
||||
|
||||
let mut tx = Transaction::new_signed_with_payer(
|
||||
ixs,
|
||||
Some(&config.keypair.pubkey()),
|
||||
&[&config.keypair, &authorized_voter_keypair],
|
||||
&[&config.keypair, &authorized_keypair],
|
||||
recent_blockhash,
|
||||
);
|
||||
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
||||
let result = rpc_client
|
||||
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_voter_keypair]);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_keypair]);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
|
@ -153,9 +161,10 @@ pub fn process_show_vote_account(
|
|||
|
||||
println!("account lamports: {}", vote_account.lamports);
|
||||
println!("node id: {}", vote_state.node_pubkey);
|
||||
println!("authorized voter: {}", vote_state.authorized_voter);
|
||||
println!(
|
||||
"authorized voter pubkey: {}",
|
||||
vote_state.authorized_voter_pubkey
|
||||
"authorized withdrawer: {}",
|
||||
vote_state.authorized_withdrawer
|
||||
);
|
||||
println!("credits: {}", vote_state.credits());
|
||||
println!(
|
||||
|
@ -221,10 +230,7 @@ pub fn process_uptime(
|
|||
})?;
|
||||
|
||||
println!("Node id: {}", vote_state.node_pubkey);
|
||||
println!(
|
||||
"Authorized voter pubkey: {}",
|
||||
vote_state.authorized_voter_pubkey
|
||||
);
|
||||
println!("Authorized voter: {}", vote_state.authorized_voter);
|
||||
if !vote_state.votes.is_empty() {
|
||||
println!("Uptime:");
|
||||
|
||||
|
@ -291,14 +297,14 @@ mod tests {
|
|||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"authorize-voter",
|
||||
"vote-authorize-voter",
|
||||
&pubkey_string,
|
||||
&keypair_file,
|
||||
&pubkey_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_authorize_voter).unwrap(),
|
||||
WalletCommand::AuthorizeVoter(pubkey, keypair, pubkey)
|
||||
WalletCommand::VoteAuthorize(pubkey, keypair, pubkey, VoteAuthorize::Voter)
|
||||
);
|
||||
fs::remove_file(&keypair_file).unwrap();
|
||||
|
||||
|
@ -316,7 +322,16 @@ mod tests {
|
|||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_create_vote_account).unwrap(),
|
||||
WalletCommand::CreateVoteAccount(pubkey, node_pubkey, 10, 50)
|
||||
WalletCommand::CreateVoteAccount(
|
||||
pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: pubkey,
|
||||
authorized_withdrawer: pubkey,
|
||||
commission: 10
|
||||
},
|
||||
50
|
||||
)
|
||||
);
|
||||
let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
|
@ -327,7 +342,63 @@ mod tests {
|
|||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_create_vote_account2).unwrap(),
|
||||
WalletCommand::CreateVoteAccount(pubkey, node_pubkey, 0, 50)
|
||||
WalletCommand::CreateVoteAccount(
|
||||
pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: pubkey,
|
||||
authorized_withdrawer: pubkey,
|
||||
commission: 0
|
||||
},
|
||||
50
|
||||
)
|
||||
);
|
||||
// test init with an authed voter
|
||||
let authed = Pubkey::new_rand();
|
||||
let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&pubkey_string,
|
||||
&node_pubkey_string,
|
||||
"50",
|
||||
"--authorized-voter",
|
||||
&authed.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_create_vote_account3).unwrap(),
|
||||
WalletCommand::CreateVoteAccount(
|
||||
pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: authed,
|
||||
authorized_withdrawer: pubkey,
|
||||
commission: 0
|
||||
},
|
||||
50
|
||||
)
|
||||
);
|
||||
// test init with an authed withdrawer
|
||||
let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&pubkey_string,
|
||||
&node_pubkey_string,
|
||||
"50",
|
||||
"--authorized-withdrawer",
|
||||
&authed.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_create_vote_account4).unwrap(),
|
||||
WalletCommand::CreateVoteAccount(
|
||||
pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: pubkey,
|
||||
authorized_withdrawer: authed,
|
||||
commission: 0
|
||||
},
|
||||
50
|
||||
)
|
||||
);
|
||||
|
||||
// Test Uptime Subcommand
|
||||
|
|
|
@ -7,11 +7,9 @@ use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
|||
use console::{style, Emoji};
|
||||
use log::*;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde_json;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{self, json, Value};
|
||||
use solana_budget_api::budget_instruction::{self, BudgetError};
|
||||
use solana_client::client_error::ClientError;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
||||
#[cfg(not(test))]
|
||||
use solana_drone::drone::request_airdrop_transaction;
|
||||
#[cfg(test)]
|
||||
|
@ -33,14 +31,16 @@ use solana_sdk::{
|
|||
};
|
||||
use solana_stake_api::stake_instruction::{self, StakeError};
|
||||
use solana_storage_api::storage_instruction;
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{error, fmt};
|
||||
use solana_vote_api::vote_state::{VoteAuthorize, VoteInit, VoteState};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
net::{IpAddr, SocketAddr},
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
{error, fmt},
|
||||
};
|
||||
|
||||
const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE
|
||||
|
||||
|
@ -64,8 +64,8 @@ pub enum WalletCommand {
|
|||
},
|
||||
Cancel(Pubkey),
|
||||
Confirm(Signature),
|
||||
AuthorizeVoter(Pubkey, Keypair, Pubkey),
|
||||
CreateVoteAccount(Pubkey, Pubkey, u8, u64),
|
||||
VoteAuthorize(Pubkey, Keypair, Pubkey, VoteAuthorize),
|
||||
CreateVoteAccount(Pubkey, VoteInit, u64),
|
||||
ShowAccount {
|
||||
pubkey: Pubkey,
|
||||
output_file: Option<String>,
|
||||
|
@ -236,7 +236,12 @@ pub fn parse_command(
|
|||
})
|
||||
}
|
||||
("create-vote-account", Some(matches)) => parse_vote_create_account(matches),
|
||||
("authorize-voter", Some(matches)) => parse_vote_authorize_voter(matches),
|
||||
("vote-authorize-voter", Some(matches)) => {
|
||||
parse_vote_authorize(matches, VoteAuthorize::Voter)
|
||||
}
|
||||
("vote-authorize-withdrawer", Some(matches)) => {
|
||||
parse_vote_authorize(matches, VoteAuthorize::Withdrawer)
|
||||
}
|
||||
("show-vote-account", Some(matches)) => parse_vote_get_account_command(matches),
|
||||
("uptime", Some(matches)) => parse_vote_uptime_command(matches),
|
||||
("delegate-stake", Some(matches)) => {
|
||||
|
@ -1269,30 +1274,28 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
|||
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
|
||||
|
||||
// Create vote account
|
||||
WalletCommand::CreateVoteAccount(
|
||||
vote_account_pubkey,
|
||||
node_pubkey,
|
||||
commission,
|
||||
lamports,
|
||||
) => process_create_vote_account(
|
||||
WalletCommand::CreateVoteAccount(vote_account_pubkey, vote_init, lamports) => {
|
||||
process_create_vote_account(
|
||||
&rpc_client,
|
||||
config,
|
||||
&vote_account_pubkey,
|
||||
&node_pubkey,
|
||||
*commission,
|
||||
&vote_init,
|
||||
*lamports,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
WalletCommand::AuthorizeVoter(
|
||||
WalletCommand::VoteAuthorize(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_keypair,
|
||||
new_authorized_voter_pubkey,
|
||||
) => process_authorize_voter(
|
||||
authorized_keypair,
|
||||
new_authorized_pubkey,
|
||||
vote_authorize,
|
||||
) => process_vote_authorize(
|
||||
&rpc_client,
|
||||
config,
|
||||
&vote_account_pubkey,
|
||||
&authorized_voter_keypair,
|
||||
&new_authorized_voter_pubkey,
|
||||
&authorized_keypair,
|
||||
&new_authorized_pubkey,
|
||||
*vote_authorize,
|
||||
),
|
||||
|
||||
WalletCommand::ShowAccount {
|
||||
|
@ -1646,7 +1649,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("authorize-voter")
|
||||
SubCommand::with_name("vote-authorize-voter")
|
||||
.about("Authorize a new vote signing keypair for the given vote account")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
|
@ -1658,7 +1661,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
.help("Vote account in which to set the authorized voter"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_voter_keypair_file")
|
||||
Arg::with_name("authorized_keypair_file")
|
||||
.index(2)
|
||||
.value_name("CURRENT VOTER KEYPAIR FILE")
|
||||
.takes_value(true)
|
||||
|
@ -1667,7 +1670,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
.help("Keypair file for the currently authorized vote signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_voter_pubkey")
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(3)
|
||||
.value_name("NEW VOTER PUBKEY")
|
||||
.takes_value(true)
|
||||
|
@ -1676,6 +1679,37 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
.help("New vote signer to authorize"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-authorize-withdrawer")
|
||||
.about("Authorize a new withdraw signing keypair for the given vote account")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Vote account in which to set the authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_keypair_file")
|
||||
.index(2)
|
||||
.value_name("CURRENT WITHDRAWER KEYPAIR FILE")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_keypair)
|
||||
.help("Keypair file for the currently authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(3)
|
||||
.value_name("NEW WITHDRAWER PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("New withdrawer to authorize"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("create-vote-account")
|
||||
.about("Create a vote account")
|
||||
|
@ -1711,7 +1745,25 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("The commission taken on reward redemption (0-255), default: 0"),
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_voter")
|
||||
.long("authorized-voter")
|
||||
.value_name("PUBKEY")
|
||||
.takes_value(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Public key of the authorized voter (defaults to vote account pubkey)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_withdrawer")
|
||||
.long("authorized-withdrawer")
|
||||
.value_name("PUBKEY")
|
||||
.takes_value(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Public key of the authorized withdrawer (defaults to vote account pubkey)"),
|
||||
)
|
||||
|
||||
,
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("show-account")
|
||||
|
@ -2621,14 +2673,27 @@ mod tests {
|
|||
|
||||
let bob_pubkey = Pubkey::new_rand();
|
||||
let node_pubkey = Pubkey::new_rand();
|
||||
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_pubkey, 0, 10);
|
||||
config.command = WalletCommand::CreateVoteAccount(
|
||||
bob_pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: bob_pubkey,
|
||||
authorized_withdrawer: bob_pubkey,
|
||||
commission: 0,
|
||||
},
|
||||
10,
|
||||
);
|
||||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
|
||||
let bob_keypair = Keypair::new();
|
||||
let new_authorized_voter_pubkey = Pubkey::new_rand();
|
||||
config.command =
|
||||
WalletCommand::AuthorizeVoter(bob_pubkey, bob_keypair, new_authorized_voter_pubkey);
|
||||
let new_authorized_pubkey = Pubkey::new_rand();
|
||||
config.command = WalletCommand::VoteAuthorize(
|
||||
bob_pubkey,
|
||||
bob_keypair,
|
||||
new_authorized_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
|
||||
|
@ -2778,10 +2843,24 @@ mod tests {
|
|||
};
|
||||
assert!(process_command(&config).is_err());
|
||||
|
||||
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_pubkey, 0, 10);
|
||||
config.command = WalletCommand::CreateVoteAccount(
|
||||
bob_pubkey,
|
||||
VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: bob_pubkey,
|
||||
authorized_withdrawer: bob_pubkey,
|
||||
commission: 0,
|
||||
},
|
||||
10,
|
||||
);
|
||||
assert!(process_command(&config).is_err());
|
||||
|
||||
config.command = WalletCommand::AuthorizeVoter(bob_pubkey, Keypair::new(), bob_pubkey);
|
||||
config.command = WalletCommand::VoteAuthorize(
|
||||
bob_pubkey,
|
||||
Keypair::new(),
|
||||
bob_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
assert!(process_command(&config).is_err());
|
||||
|
||||
config.command = WalletCommand::GetSlot;
|
||||
|
|
|
@ -228,7 +228,7 @@ mod tests {
|
|||
let ancestors = vec![3, 4, 5, 7, 9, 11];
|
||||
let mut confidence = HashMap::new();
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let root = ancestors.last().unwrap();
|
||||
vote_state.root_slot = Some(*root);
|
||||
|
@ -251,7 +251,7 @@ mod tests {
|
|||
let ancestors = vec![3, 4, 5, 7, 9, 11];
|
||||
let mut confidence = HashMap::new();
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let root = ancestors[2];
|
||||
vote_state.root_slot = Some(root);
|
||||
|
@ -281,7 +281,7 @@ mod tests {
|
|||
let ancestors = vec![3, 4, 5, 7, 9, 10, 11];
|
||||
let mut confidence = HashMap::new();
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let root = ancestors[2];
|
||||
vote_state.root_slot = Some(root);
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::{account::Account, pubkey::Pubkey};
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::{borrow::Borrow, collections::HashMap};
|
||||
|
||||
/// Looks through vote accounts, and finds the latest slot that has achieved
|
||||
/// supermajority lockout
|
||||
|
@ -99,14 +97,15 @@ where
|
|||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo, BOOTSTRAP_LEADER_LAMPORTS};
|
||||
use solana_sdk::instruction::Instruction;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::sysvar::stake_history::{self, StakeHistory};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_stake_api::stake_instruction;
|
||||
use solana_stake_api::stake_state::Stake;
|
||||
use solana_vote_api::vote_instruction;
|
||||
use solana_sdk::{
|
||||
instruction::Instruction,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, KeypairUtil},
|
||||
sysvar::stake_history::{self, StakeHistory},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_stake_api::{stake_instruction, stake_state::Stake};
|
||||
use solana_vote_api::{vote_instruction, vote_state::VoteInit};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn new_from_parent(parent: &Arc<Bank>, slot: u64) -> Bank {
|
||||
|
@ -140,8 +139,12 @@ pub(crate) mod tests {
|
|||
vote_instruction::create_account(
|
||||
&from_account.pubkey(),
|
||||
vote_pubkey,
|
||||
node_pubkey,
|
||||
0,
|
||||
&VoteInit {
|
||||
node_pubkey: *node_pubkey,
|
||||
authorized_voter: *vote_pubkey,
|
||||
authorized_withdrawer: *vote_pubkey,
|
||||
commission: 0,
|
||||
},
|
||||
amount,
|
||||
),
|
||||
);
|
||||
|
@ -288,15 +291,28 @@ pub(crate) mod tests {
|
|||
fn test_to_staked_nodes() {
|
||||
let mut stakes = Vec::new();
|
||||
let node1 = Pubkey::new_rand();
|
||||
let node2 = Pubkey::new_rand();
|
||||
|
||||
// Node 1 has stake of 3
|
||||
for i in 0..3 {
|
||||
stakes.push((i, VoteState::new(&Pubkey::new_rand(), &node1, 0)));
|
||||
stakes.push((
|
||||
i,
|
||||
VoteState::new(&VoteInit {
|
||||
node_pubkey: node1,
|
||||
..VoteInit::default()
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// Node 1 has stake of 5
|
||||
stakes.push((5, VoteState::new(&Pubkey::new_rand(), &node2, 0)));
|
||||
let node2 = Pubkey::new_rand();
|
||||
|
||||
stakes.push((
|
||||
5,
|
||||
VoteState::new(&VoteInit {
|
||||
node_pubkey: node2,
|
||||
..VoteInit::default()
|
||||
}),
|
||||
));
|
||||
|
||||
let result = to_staked_nodes(stakes.into_iter());
|
||||
assert_eq!(result.len(), 2);
|
||||
|
|
|
@ -12,8 +12,7 @@ use solana_core::{
|
|||
};
|
||||
use solana_sdk::{
|
||||
client::SyncClient,
|
||||
clock::DEFAULT_TICKS_PER_SLOT,
|
||||
clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT},
|
||||
clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT},
|
||||
genesis_block::GenesisBlock,
|
||||
message::Message,
|
||||
poh_config::PohConfig,
|
||||
|
@ -24,7 +23,10 @@ use solana_sdk::{
|
|||
};
|
||||
use solana_stake_api::{config as stake_config, stake_instruction, stake_state::StakeState};
|
||||
use solana_storage_api::{storage_contract, storage_instruction};
|
||||
use solana_vote_api::{vote_instruction, vote_state::VoteState};
|
||||
use solana_vote_api::{
|
||||
vote_instruction,
|
||||
vote_state::{VoteInit, VoteState},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::remove_dir_all,
|
||||
|
@ -436,8 +438,12 @@ impl LocalCluster {
|
|||
vote_instruction::create_account(
|
||||
&from_account.pubkey(),
|
||||
&vote_account_pubkey,
|
||||
&node_pubkey,
|
||||
0,
|
||||
&VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: vote_account_pubkey,
|
||||
authorized_withdrawer: vote_account_pubkey,
|
||||
commission: 0,
|
||||
},
|
||||
amount,
|
||||
),
|
||||
client.get_recent_blockhash().unwrap().0,
|
||||
|
|
|
@ -14,7 +14,7 @@ use solana_stake_api::stake_instruction;
|
|||
use solana_stake_api::stake_instruction::process_instruction;
|
||||
use solana_stake_api::stake_state::StakeState;
|
||||
use solana_vote_api::vote_instruction;
|
||||
use solana_vote_api::vote_state::{Vote, VoteState};
|
||||
use solana_vote_api::vote_state::{Vote, VoteInit, VoteState};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn fill_epoch_with_votes(
|
||||
|
@ -76,8 +76,12 @@ fn test_stake_account_delegate() {
|
|||
let message = Message::new(vote_instruction::create_account(
|
||||
&mint_pubkey,
|
||||
&vote_pubkey,
|
||||
&node_pubkey,
|
||||
std::u8::MAX / 2,
|
||||
&VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: vote_pubkey,
|
||||
authorized_withdrawer: vote_pubkey,
|
||||
commission: std::u8::MAX / 2,
|
||||
},
|
||||
10,
|
||||
));
|
||||
bank_client
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use crate::{
|
||||
id,
|
||||
vote_state::{self, Vote, VoteState},
|
||||
vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState},
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use log::*;
|
||||
|
@ -51,11 +51,11 @@ impl std::error::Error for VoteError {}
|
|||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum VoteInstruction {
|
||||
/// Initialize the VoteState for this `vote account`
|
||||
/// takes a node_pubkey and commission
|
||||
InitializeAccount(Pubkey, u8),
|
||||
InitializeAccount(VoteInit),
|
||||
|
||||
/// Authorize a voter to send signed votes.
|
||||
AuthorizeVoter(Pubkey),
|
||||
/// Authorize a voter to send signed votes or a withdrawer
|
||||
/// to withdraw
|
||||
Authorize(Pubkey, VoteAuthorize),
|
||||
|
||||
/// A Vote instruction with recent votes
|
||||
Vote(Vote),
|
||||
|
@ -64,44 +64,38 @@ pub enum VoteInstruction {
|
|||
Withdraw(u64),
|
||||
}
|
||||
|
||||
fn initialize_account(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u8) -> Instruction {
|
||||
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
|
||||
let account_metas = vec![AccountMeta::new(*vote_pubkey, false)];
|
||||
Instruction::new(
|
||||
id(),
|
||||
&VoteInstruction::InitializeAccount(*node_pubkey, commission),
|
||||
&VoteInstruction::InitializeAccount(*vote_init),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn minimum_balance() -> u64 {
|
||||
let rent_calculator = solana_sdk::rent_calculator::RentCalculator::default();
|
||||
|
||||
rent_calculator.minimum_balance(VoteState::size_of())
|
||||
}
|
||||
|
||||
pub fn create_account(
|
||||
from_pubkey: &Pubkey,
|
||||
vote_pubkey: &Pubkey,
|
||||
node_pubkey: &Pubkey,
|
||||
commission: u8,
|
||||
vote_init: &VoteInit,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
let space = VoteState::size_of() as u64;
|
||||
let create_ix =
|
||||
system_instruction::create_account(from_pubkey, vote_pubkey, lamports, space, &id());
|
||||
let init_ix = initialize_account(vote_pubkey, node_pubkey, commission);
|
||||
let init_ix = initialize_account(vote_pubkey, vote_init);
|
||||
vec![create_ix, init_ix]
|
||||
}
|
||||
|
||||
// for instructions that whose authorized signer may differ from the account's pubkey
|
||||
fn metas_for_authorized_signer(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_voter_pubkey: &Pubkey, // currently authorized
|
||||
account_pubkey: &Pubkey,
|
||||
authorized_signer: &Pubkey, // currently authorized
|
||||
other_params: &[AccountMeta],
|
||||
) -> Vec<AccountMeta> {
|
||||
let is_own_signer = authorized_voter_pubkey == vote_pubkey;
|
||||
let is_own_signer = authorized_signer == account_pubkey;
|
||||
|
||||
// vote account
|
||||
let mut account_metas = vec![AccountMeta::new(*vote_pubkey, is_own_signer)];
|
||||
let mut account_metas = vec![AccountMeta::new(*account_pubkey, is_own_signer)];
|
||||
|
||||
for meta in other_params {
|
||||
account_metas.push(meta.clone());
|
||||
|
@ -109,22 +103,23 @@ fn metas_for_authorized_signer(
|
|||
|
||||
// append signer at the end
|
||||
if !is_own_signer {
|
||||
account_metas.push(AccountMeta::new_credit_only(*authorized_voter_pubkey, true)) // signer
|
||||
account_metas.push(AccountMeta::new_credit_only(*authorized_signer, true)) // signer
|
||||
}
|
||||
|
||||
account_metas
|
||||
}
|
||||
|
||||
pub fn authorize_voter(
|
||||
pub fn authorize(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_voter_pubkey: &Pubkey, // currently authorized
|
||||
new_authorized_voter_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey, // currently authorized
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Instruction {
|
||||
let account_metas = metas_for_authorized_signer(vote_pubkey, authorized_voter_pubkey, &[]);
|
||||
let account_metas = metas_for_authorized_signer(vote_pubkey, authorized_pubkey, &[]);
|
||||
|
||||
Instruction::new(
|
||||
id(),
|
||||
&VoteInstruction::AuthorizeVoter(*new_authorized_voter_pubkey),
|
||||
&VoteInstruction::Authorize(*new_authorized_pubkey, vote_authorize),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
@ -144,11 +139,17 @@ pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote)
|
|||
Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas)
|
||||
}
|
||||
|
||||
pub fn withdraw(vote_pubkey: &Pubkey, lamports: u64, to_pubkey: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*vote_pubkey, true),
|
||||
AccountMeta::new_credit_only(*to_pubkey, false),
|
||||
];
|
||||
pub fn withdraw(
|
||||
vote_pubkey: &Pubkey,
|
||||
withdrawer_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
to_pubkey: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = metas_for_authorized_signer(
|
||||
vote_pubkey,
|
||||
withdrawer_pubkey,
|
||||
&[AccountMeta::new_credit_only(*to_pubkey, false)],
|
||||
);
|
||||
|
||||
Instruction::new(id(), &VoteInstruction::Withdraw(lamports), account_metas)
|
||||
}
|
||||
|
@ -173,11 +174,11 @@ pub fn process_instruction(
|
|||
|
||||
// TODO: data-driven unpack and dispatch of KeyedAccounts
|
||||
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
|
||||
VoteInstruction::InitializeAccount(node_pubkey, commission) => {
|
||||
vote_state::initialize_account(me, &node_pubkey, commission)
|
||||
VoteInstruction::InitializeAccount(vote_init) => {
|
||||
vote_state::initialize_account(me, &vote_init)
|
||||
}
|
||||
VoteInstruction::AuthorizeVoter(voter_pubkey) => {
|
||||
vote_state::authorize_voter(me, rest, &voter_pubkey)
|
||||
VoteInstruction::Authorize(voter_pubkey, vote_authorize) => {
|
||||
vote_state::authorize(me, rest, &voter_pubkey, vote_authorize)
|
||||
}
|
||||
VoteInstruction::Vote(vote) => {
|
||||
datapoint_info!("vote-native", ("count", 1, i64));
|
||||
|
@ -198,7 +199,10 @@ pub fn process_instruction(
|
|||
if rest.is_empty() {
|
||||
Err(InstructionError::InvalidInstructionData)?;
|
||||
}
|
||||
vote_state::withdraw(me, lamports, &mut rest[0])
|
||||
let (to, rest) = rest.split_at_mut(1);
|
||||
let to = &mut to[0];
|
||||
|
||||
vote_state::withdraw(me, rest, lamports, to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,8 +255,7 @@ mod tests {
|
|||
let instructions = create_account(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
0,
|
||||
&VoteInit::default(),
|
||||
100,
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -268,10 +271,20 @@ mod tests {
|
|||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction(&authorize_voter(
|
||||
process_instruction(&authorize(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
VoteAuthorize::Voter,
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction(&withdraw(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
0,
|
||||
&Pubkey::default()
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
|
@ -285,4 +298,44 @@ mod tests {
|
|||
assert!(minimum_balance as f64 / 2f64.powf(34.0) < 0.02)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_metas_for_authorized_signer() {
|
||||
let account_pubkey = Pubkey::new_rand();
|
||||
let authorized_signer = Pubkey::new_rand();
|
||||
|
||||
assert_eq!(
|
||||
metas_for_authorized_signer(&account_pubkey, &authorized_signer, &[]).len(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
metas_for_authorized_signer(&account_pubkey, &account_pubkey, &[]).len(),
|
||||
1
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_custom_error_decode() {
|
||||
use num_traits::FromPrimitive;
|
||||
fn pretty_err<T>(err: InstructionError) -> String
|
||||
where
|
||||
T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
|
||||
{
|
||||
if let InstructionError::CustomError(code) = err {
|
||||
let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
|
||||
format!(
|
||||
"{:?}: {}::{:?} - {}",
|
||||
err,
|
||||
T::type_of(),
|
||||
specific_error,
|
||||
specific_error,
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
"CustomError(0): VoteError::VoteTooOld - vote already recorded or not in slot hashes history",
|
||||
pretty_err::<VoteError>(VoteError::VoteTooOld.into())
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,14 +67,33 @@ impl Lockout {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct VoteInit {
|
||||
pub node_pubkey: Pubkey,
|
||||
pub authorized_voter: Pubkey,
|
||||
pub authorized_withdrawer: Pubkey,
|
||||
pub commission: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum VoteAuthorize {
|
||||
Voter,
|
||||
Withdrawer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct VoteState {
|
||||
pub votes: VecDeque<Lockout>,
|
||||
/// the node that votes in this account
|
||||
pub node_pubkey: Pubkey,
|
||||
pub authorized_voter_pubkey: Pubkey,
|
||||
/// the signer for vote transactions
|
||||
pub authorized_voter: Pubkey,
|
||||
/// the signer for withdrawals
|
||||
pub authorized_withdrawer: Pubkey,
|
||||
/// fraction of std::u8::MAX that represents what part of a rewards
|
||||
/// payout should be given to this VoteAccount
|
||||
pub commission: u8,
|
||||
|
||||
pub votes: VecDeque<Lockout>,
|
||||
pub root_slot: Option<u64>,
|
||||
|
||||
/// clock epoch
|
||||
|
@ -91,11 +110,12 @@ pub struct VoteState {
|
|||
}
|
||||
|
||||
impl VoteState {
|
||||
pub fn new(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u8) -> Self {
|
||||
pub fn new(vote_init: &VoteInit) -> Self {
|
||||
Self {
|
||||
node_pubkey: *node_pubkey,
|
||||
authorized_voter_pubkey: *vote_pubkey,
|
||||
commission,
|
||||
node_pubkey: vote_init.node_pubkey,
|
||||
authorized_voter: vote_init.authorized_voter,
|
||||
authorized_withdrawer: vote_init.authorized_withdrawer,
|
||||
commission: vote_init.commission,
|
||||
..VoteState::default()
|
||||
}
|
||||
}
|
||||
|
@ -313,39 +333,69 @@ impl VoteState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Authorize the given pubkey to sign votes. This may be called multiple times,
|
||||
/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times,
|
||||
/// but will implicitly withdraw authorization from the previously authorized
|
||||
/// voter. The default voter is the owner of the vote account's pubkey.
|
||||
pub fn authorize_voter(
|
||||
/// key
|
||||
pub fn authorize(
|
||||
vote_account: &mut KeyedAccount,
|
||||
other_signers: &[KeyedAccount],
|
||||
authorized_voter_pubkey: &Pubkey,
|
||||
authorized: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut vote_state: VoteState = vote_account.state()?;
|
||||
|
||||
// clock authorized signer must say "yay"
|
||||
let authorized = Some(&vote_state.authorized_voter_pubkey);
|
||||
if vote_account.signer_key() != authorized
|
||||
// current authorized signer must say "yay"
|
||||
match vote_authorize {
|
||||
VoteAuthorize::Voter => {
|
||||
verify_authorized_signer(&vote_state.authorized_voter, vote_account, other_signers)?;
|
||||
vote_state.authorized_voter = *authorized;
|
||||
}
|
||||
VoteAuthorize::Withdrawer => {
|
||||
verify_authorized_signer(
|
||||
&vote_state.authorized_withdrawer,
|
||||
vote_account,
|
||||
other_signers,
|
||||
)?;
|
||||
vote_state.authorized_withdrawer = *authorized;
|
||||
}
|
||||
}
|
||||
|
||||
vote_account.set_state(&vote_state)
|
||||
}
|
||||
|
||||
fn verify_authorized_signer(
|
||||
authorized: &Pubkey,
|
||||
account: &KeyedAccount,
|
||||
other_signers: &[KeyedAccount],
|
||||
) -> Result<(), InstructionError> {
|
||||
let authorized = Some(authorized);
|
||||
|
||||
// find a signer that matches authorized
|
||||
if account.signer_key() != authorized
|
||||
&& other_signers
|
||||
.iter()
|
||||
.all(|account| account.signer_key() != authorized)
|
||||
{
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
vote_state.authorized_voter_pubkey = *authorized_voter_pubkey;
|
||||
vote_account.set_state(&vote_state)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Withdraw funds from the vote account
|
||||
pub fn withdraw(
|
||||
vote_account: &mut KeyedAccount,
|
||||
other_signers: &[KeyedAccount],
|
||||
lamports: u64,
|
||||
to_account: &mut KeyedAccount,
|
||||
) -> Result<(), InstructionError> {
|
||||
if vote_account.signer_key().is_none() {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
let vote_state: VoteState = vote_account.state()?;
|
||||
|
||||
verify_authorized_signer(
|
||||
&vote_state.authorized_withdrawer,
|
||||
vote_account,
|
||||
other_signers,
|
||||
)?;
|
||||
|
||||
if vote_account.account.lamports < lamports {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
@ -359,19 +409,14 @@ pub fn withdraw(
|
|||
/// that the transaction must be signed by the staker's keys
|
||||
pub fn initialize_account(
|
||||
vote_account: &mut KeyedAccount,
|
||||
node_pubkey: &Pubkey,
|
||||
commission: u8,
|
||||
vote_init: &VoteInit,
|
||||
) -> Result<(), InstructionError> {
|
||||
let vote_state: VoteState = vote_account.state()?;
|
||||
|
||||
if vote_state.authorized_voter_pubkey != Pubkey::default() {
|
||||
if vote_state.authorized_voter != Pubkey::default() {
|
||||
return Err(InstructionError::AccountAlreadyInitialized);
|
||||
}
|
||||
vote_account.set_state(&VoteState::new(
|
||||
vote_account.unsigned_key(),
|
||||
node_pubkey,
|
||||
commission,
|
||||
))
|
||||
vote_account.set_state(&VoteState::new(vote_init))
|
||||
}
|
||||
|
||||
pub fn process_vote(
|
||||
|
@ -383,19 +428,11 @@ pub fn process_vote(
|
|||
) -> Result<(), InstructionError> {
|
||||
let mut vote_state: VoteState = vote_account.state()?;
|
||||
|
||||
if vote_state.authorized_voter_pubkey == Pubkey::default() {
|
||||
if vote_state.authorized_voter == Pubkey::default() {
|
||||
return Err(InstructionError::UninitializedAccount);
|
||||
}
|
||||
|
||||
let authorized = Some(&vote_state.authorized_voter_pubkey);
|
||||
// find a signer that matches the authorized_voter_pubkey
|
||||
if vote_account.signer_key() != authorized
|
||||
&& other_signers
|
||||
.iter()
|
||||
.all(|account| account.signer_key() != authorized)
|
||||
{
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
verify_authorized_signer(&vote_state.authorized_voter, vote_account, other_signers)?;
|
||||
|
||||
vote_state.process_vote(vote, slot_hashes, clock.epoch)?;
|
||||
vote_account.set_state(&vote_state)
|
||||
|
@ -410,7 +447,12 @@ pub fn create_account(
|
|||
) -> Account {
|
||||
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id());
|
||||
|
||||
VoteState::new(vote_pubkey, node_pubkey, commission)
|
||||
VoteState::new(&VoteInit {
|
||||
node_pubkey: *node_pubkey,
|
||||
authorized_voter: *vote_pubkey,
|
||||
authorized_withdrawer: *vote_pubkey,
|
||||
commission,
|
||||
})
|
||||
.to(&mut vote_account)
|
||||
.unwrap();
|
||||
|
||||
|
@ -427,6 +469,17 @@ mod tests {
|
|||
|
||||
const MAX_RECENT_VOTES: usize = 16;
|
||||
|
||||
impl VoteState {
|
||||
pub fn new_for_test(auth_pubkey: &Pubkey) -> Self {
|
||||
Self::new(&VoteInit {
|
||||
node_pubkey: Pubkey::new_rand(),
|
||||
authorized_voter: *auth_pubkey,
|
||||
authorized_withdrawer: *auth_pubkey,
|
||||
commission: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initialize_vote_account() {
|
||||
let vote_account_pubkey = Pubkey::new_rand();
|
||||
|
@ -436,11 +489,27 @@ mod tests {
|
|||
|
||||
//init should pass
|
||||
let mut vote_account = KeyedAccount::new(&vote_account_pubkey, false, &mut vote_account);
|
||||
let res = initialize_account(&mut vote_account, &node_pubkey, 0);
|
||||
let res = initialize_account(
|
||||
&mut vote_account,
|
||||
&VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: vote_account_pubkey,
|
||||
authorized_withdrawer: vote_account_pubkey,
|
||||
commission: 0,
|
||||
},
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
|
||||
// reinit should fail
|
||||
let res = initialize_account(&mut vote_account, &node_pubkey, 0);
|
||||
let res = initialize_account(
|
||||
&mut vote_account,
|
||||
&VoteInit {
|
||||
node_pubkey,
|
||||
authorized_voter: vote_account_pubkey,
|
||||
authorized_withdrawer: vote_account_pubkey,
|
||||
commission: 0,
|
||||
},
|
||||
);
|
||||
assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized));
|
||||
}
|
||||
|
||||
|
@ -504,7 +573,7 @@ mod tests {
|
|||
let (vote_pubkey, vote_account) = create_test_account();
|
||||
|
||||
let vote_state: VoteState = vote_account.state().unwrap();
|
||||
assert_eq!(vote_state.authorized_voter_pubkey, vote_pubkey);
|
||||
assert_eq!(vote_state.authorized_voter, vote_pubkey);
|
||||
assert!(vote_state.votes.is_empty());
|
||||
}
|
||||
|
||||
|
@ -582,21 +651,23 @@ mod tests {
|
|||
|
||||
// another voter
|
||||
let authorized_voter_pubkey = Pubkey::new_rand();
|
||||
let res = authorize_voter(
|
||||
let res = authorize(
|
||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||
&[],
|
||||
&authorized_voter_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||
|
||||
let res = authorize_voter(
|
||||
let res = authorize(
|
||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||
&[],
|
||||
&authorized_voter_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
// verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;)
|
||||
let res = authorize_voter(
|
||||
let res = authorize(
|
||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||
&[KeyedAccount::new(
|
||||
&authorized_voter_pubkey,
|
||||
|
@ -604,6 +675,31 @@ mod tests {
|
|||
&mut Account::default(),
|
||||
)],
|
||||
&authorized_voter_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
|
||||
// authorize another withdrawer
|
||||
// another voter
|
||||
let authorized_withdrawer_pubkey = Pubkey::new_rand();
|
||||
let res = authorize(
|
||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||
&[],
|
||||
&authorized_withdrawer_pubkey,
|
||||
VoteAuthorize::Withdrawer,
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
|
||||
// verify authorized_withdrawer can authorize authorized_withdrawer ;)
|
||||
let res = authorize(
|
||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||
&[KeyedAccount::new(
|
||||
&authorized_withdrawer_pubkey,
|
||||
true,
|
||||
&mut Account::default(),
|
||||
)],
|
||||
&authorized_withdrawer_pubkey,
|
||||
VoteAuthorize::Withdrawer,
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
|
||||
|
@ -678,7 +774,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_vote_double_lockout_after_expiration() {
|
||||
let voter_pubkey = Pubkey::new_rand();
|
||||
let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state = VoteState::new_for_test(&voter_pubkey);
|
||||
|
||||
for i in 0..3 {
|
||||
vote_state.process_slot_vote_unchecked(i as u64);
|
||||
|
@ -706,7 +802,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_expire_multiple_votes() {
|
||||
let voter_pubkey = Pubkey::new_rand();
|
||||
let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state = VoteState::new_for_test(&voter_pubkey);
|
||||
|
||||
for i in 0..3 {
|
||||
vote_state.process_slot_vote_unchecked(i as u64);
|
||||
|
@ -737,7 +833,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_vote_credits() {
|
||||
let voter_pubkey = Pubkey::new_rand();
|
||||
let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state = VoteState::new_for_test(&voter_pubkey);
|
||||
|
||||
for i in 0..MAX_LOCKOUT_HISTORY {
|
||||
vote_state.process_slot_vote_unchecked(i as u64);
|
||||
|
@ -756,7 +852,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_duplicate_vote() {
|
||||
let voter_pubkey = Pubkey::new_rand();
|
||||
let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state = VoteState::new_for_test(&voter_pubkey);
|
||||
vote_state.process_slot_vote_unchecked(0);
|
||||
vote_state.process_slot_vote_unchecked(1);
|
||||
vote_state.process_slot_vote_unchecked(0);
|
||||
|
@ -768,7 +864,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_nth_recent_vote() {
|
||||
let voter_pubkey = Pubkey::new_rand();
|
||||
let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state = VoteState::new_for_test(&voter_pubkey);
|
||||
for i in 0..MAX_LOCKOUT_HISTORY {
|
||||
vote_state.process_slot_vote_unchecked(i as u64);
|
||||
}
|
||||
|
@ -799,9 +895,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_process_missed_votes() {
|
||||
let account_a = Pubkey::new_rand();
|
||||
let mut vote_state_a = VoteState::new(&account_a, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state_a = VoteState::new_for_test(&account_a);
|
||||
let account_b = Pubkey::new_rand();
|
||||
let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0);
|
||||
let mut vote_state_b = VoteState::new_for_test(&account_b);
|
||||
|
||||
// process some votes on account a
|
||||
(0..5)
|
||||
|
@ -821,7 +917,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_process_vote_skips_old_vote() {
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
|
||||
|
@ -836,7 +932,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_vote_empty_slot_hashes() {
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
assert_eq!(
|
||||
|
@ -847,7 +943,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_new_vote() {
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
|
@ -859,7 +955,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_bad_hash() {
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
|
||||
|
@ -871,7 +967,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_bad_slot() {
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![1], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
|
||||
|
@ -883,7 +979,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_duplicate_vote() {
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
|
@ -896,7 +992,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_next_vote() {
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
|
@ -912,7 +1008,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_check_slots_are_valid_next_vote_only() {
|
||||
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
|
@ -925,17 +1021,28 @@ mod tests {
|
|||
Ok(())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_process_vote_empty_slots() {
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let vote = Vote::new(vec![], Hash::default());
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &[], 0),
|
||||
Err(VoteError::EmptySlots)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_state_commission_split() {
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||
let vote_state = VoteState::default();
|
||||
|
||||
assert_eq!(vote_state.commission_split(1.0), (0.0, 1.0, false));
|
||||
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), std::u8::MAX);
|
||||
let mut vote_state = VoteState::default();
|
||||
vote_state.commission = std::u8::MAX;
|
||||
assert_eq!(vote_state.commission_split(1.0), (1.0, 0.0, false));
|
||||
|
||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), std::u8::MAX / 2);
|
||||
vote_state.commission = std::u8::MAX / 2;
|
||||
let (voter_portion, staker_portion, was_split) = vote_state.commission_split(10.0);
|
||||
|
||||
assert_eq!(
|
||||
|
@ -948,9 +1055,10 @@ mod tests {
|
|||
fn test_vote_state_withdraw() {
|
||||
let (vote_pubkey, mut vote_account) = create_test_account();
|
||||
|
||||
// unsigned
|
||||
// unsigned request
|
||||
let res = withdraw(
|
||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||
&[],
|
||||
0,
|
||||
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()),
|
||||
);
|
||||
|
@ -959,6 +1067,7 @@ mod tests {
|
|||
// insufficient funds
|
||||
let res = withdraw(
|
||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||
&[],
|
||||
101,
|
||||
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()),
|
||||
);
|
||||
|
@ -969,6 +1078,25 @@ mod tests {
|
|||
let lamports = vote_account.lamports;
|
||||
let res = withdraw(
|
||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||
&[],
|
||||
lamports,
|
||||
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account),
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
assert_eq!(vote_account.lamports, 0);
|
||||
assert_eq!(to_account.lamports, lamports);
|
||||
|
||||
// reset balance, verify that authorized_withdrawer works
|
||||
vote_account.lamports = lamports;
|
||||
to_account.lamports = 0;
|
||||
let mut authorized_withdrawer_account = Account::new(0, 0, &vote_pubkey);
|
||||
let res = withdraw(
|
||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||
&[KeyedAccount::new(
|
||||
&vote_pubkey,
|
||||
true,
|
||||
&mut authorized_withdrawer_account,
|
||||
)],
|
||||
lamports,
|
||||
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account),
|
||||
);
|
||||
|
|
|
@ -1584,7 +1584,7 @@ mod tests {
|
|||
use solana_sdk::sysvar::{fees::Fees, rewards::Rewards};
|
||||
use solana_stake_api::stake_state::Stake;
|
||||
use solana_vote_api::vote_instruction;
|
||||
use solana_vote_api::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
||||
use solana_vote_api::vote_state::{VoteInit, VoteState, MAX_LOCKOUT_HISTORY};
|
||||
use std::io::Cursor;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
|
@ -2861,8 +2861,12 @@ mod tests {
|
|||
let instructions = vote_instruction::create_account(
|
||||
&mint_keypair.pubkey(),
|
||||
&vote_keypair.pubkey(),
|
||||
&mint_keypair.pubkey(),
|
||||
0,
|
||||
&VoteInit {
|
||||
node_pubkey: mint_keypair.pubkey(),
|
||||
authorized_voter: vote_keypair.pubkey(),
|
||||
authorized_withdrawer: vote_keypair.pubkey(),
|
||||
commission: 0,
|
||||
},
|
||||
10,
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue