support issuing vote instructions from system account (#4338)

* issue vote instructions from system account

* fixup

* bring back KeypairUtil
This commit is contained in:
Rob Walker 2019-05-20 13:32:32 -07:00 committed by GitHub
parent 114e2989fa
commit 86e03a6d1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 156 additions and 67 deletions

View File

@ -438,8 +438,6 @@ mod tests {
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::Vote;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction {
@ -460,12 +458,6 @@ mod tests {
Transaction::new_signed_instructions(&[keypair], vec![ix], hash)
}
fn create_sample_vote(keypair: &Keypair, hash: Hash) -> Transaction {
let pubkey = keypair.pubkey();
let ix = vote_instruction::vote(&pubkey, vec![Vote::new(1)]);
Transaction::new_signed_instructions(&[keypair], vec![ix], hash)
}
#[test]
fn test_entry_verify() {
let zero = Hash::default();
@ -647,8 +639,7 @@ mod tests {
let hash = Hash::default();
let next_hash = solana_sdk::hash::hash(&hash.as_ref());
let keypair = Keypair::new();
let vote_account = Keypair::new();
let tx_small = create_sample_vote(&vote_account, next_hash);
let tx_small = create_sample_timestamp(&keypair, next_hash);
let tx_large = create_sample_payment(&keypair, next_hash);
let tx_small_size = serialized_size(&tx_small).unwrap() as usize;

View File

@ -71,19 +71,16 @@ pub struct Fullnode {
}
impl Fullnode {
pub fn new<T>(
pub fn new(
mut node: Node,
keypair: &Arc<Keypair>,
ledger_path: &str,
vote_account: &Pubkey,
voting_keypair: &Arc<T>,
voting_keypair: &Arc<Keypair>,
storage_keypair: &Arc<Keypair>,
entrypoint_info_option: Option<&ContactInfo>,
config: &FullnodeConfig,
) -> Self
where
T: 'static + KeypairUtil + Sync + Send,
{
) -> Self {
info!("creating bank...");
let id = keypair.pubkey();

View File

@ -95,7 +95,6 @@ impl ReplayStage {
let bank_forks = bank_forks.clone();
let poh_recorder = poh_recorder.clone();
let my_id = *my_id;
let vote_account = *vote_account;
let mut ticks_per_slot = 0;
let mut locktower = Locktower::new_from_forks(&bank_forks.read().unwrap(), &my_id);
if let Some(root) = locktower.root() {
@ -105,6 +104,7 @@ impl ReplayStage {
}
// Start the replay stage loop
let leader_schedule_cache = leader_schedule_cache.clone();
let vote_account = *vote_account;
let voting_keypair = voting_keypair.cloned();
let t_replay = Builder::new()
.name("solana-replay-stage".to_string())
@ -152,8 +152,8 @@ impl ReplayStage {
&bank_forks,
&mut locktower,
&mut progress,
&voting_keypair,
&vote_account,
&voting_keypair,
&cluster_info,
&blocktree,
&leader_schedule_cache,
@ -297,8 +297,8 @@ impl ReplayStage {
bank_forks: &Arc<RwLock<BankForks>>,
locktower: &mut Locktower,
progress: &mut HashMap<u64, ForkProgress>,
vote_account: &Pubkey,
voting_keypair: &Option<Arc<T>>,
vote_account_pubkey: &Pubkey,
cluster_info: &Arc<RwLock<ClusterInfo>>,
blocktree: &Arc<Blocktree>,
leader_schedule_cache: &Arc<LeaderScheduleCache>,
@ -322,13 +322,20 @@ impl ReplayStage {
}
locktower.update_epoch(&bank);
if let Some(ref voting_keypair) = voting_keypair {
let node_keypair = cluster_info.read().unwrap().keypair.clone();
// Send our last few votes along with the new one
let vote_ix = vote_instruction::vote(vote_account_pubkey, locktower.recent_votes());
let vote_tx = Transaction::new_signed_instructions(
&[voting_keypair.as_ref()],
vec![vote_ix],
bank.last_blockhash(),
let vote_ix = vote_instruction::vote(
&node_keypair.pubkey(),
&vote_account,
&voting_keypair.pubkey(),
locktower.recent_votes(),
);
let mut vote_tx = Transaction::new_unsigned_instructions(vec![vote_ix]);
let blockhash = bank.last_blockhash();
vote_tx.partial_sign(&[node_keypair.as_ref()], blockhash);
vote_tx.partial_sign(&[voting_keypair.as_ref()], blockhash);
cluster_info.write().unwrap().push_vote(vote_tx);
}
Ok(())

View File

@ -25,8 +25,16 @@ pub enum VoteInstruction {
Vote(Vec<Vote>),
}
fn initialize_account(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, false)];
fn initialize_account(
from_id: &Pubkey,
vote_id: &Pubkey,
node_id: &Pubkey,
commission: u32,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*from_id, true),
AccountMeta::new(*vote_id, false),
];
Instruction::new(
id(),
&VoteInstruction::InitializeAccount(*node_id, commission),
@ -42,22 +50,51 @@ pub fn create_account(
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);
let create_ix = system_instruction::create_account(from_id, vote_id, lamports, space, &id());
let init_ix = initialize_account(from_id, vote_id, node_id, commission);
vec![create_ix, init_ix]
}
pub fn authorize_voter(vote_id: &Pubkey, authorized_voter_id: &Pubkey) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, true)];
fn metas_for_authorized_signer(
from_id: &Pubkey,
vote_id: &Pubkey,
authorized_voter_id: &Pubkey, // currently authorized
) -> Vec<AccountMeta> {
let mut account_metas = vec![AccountMeta::new(*from_id, true)]; // sender
let is_own_signer = authorized_voter_id == vote_id;
account_metas.push(AccountMeta::new(*vote_id, is_own_signer)); // vote account
if !is_own_signer {
account_metas.push(AccountMeta::new(*authorized_voter_id, true)) // signer
}
account_metas
}
pub fn authorize_voter(
from_id: &Pubkey,
vote_id: &Pubkey,
authorized_voter_id: &Pubkey, // currently authorized
new_authorized_voter_id: &Pubkey,
) -> Instruction {
let account_metas = metas_for_authorized_signer(from_id, vote_id, authorized_voter_id);
Instruction::new(
id(),
&VoteInstruction::AuthorizeVoter(*authorized_voter_id),
&VoteInstruction::AuthorizeVoter(*new_authorized_voter_id),
account_metas,
)
}
pub fn vote(vote_id: &Pubkey, recent_votes: Vec<Vote>) -> Instruction {
let account_metas = vec![AccountMeta::new(*vote_id, true)];
pub fn vote(
from_id: &Pubkey,
vote_id: &Pubkey,
authorized_voter_id: &Pubkey,
recent_votes: Vec<Vote>,
) -> Instruction {
let account_metas = metas_for_authorized_signer(from_id, vote_id, authorized_voter_id);
Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas)
}
@ -72,27 +109,25 @@ pub fn process_instruction(
trace!("process_instruction: {:?}", data);
trace!("keyed_accounts: {:?}", keyed_accounts);
if keyed_accounts.is_empty() {
if keyed_accounts.len() < 2 {
Err(InstructionError::InvalidInstructionData)?;
}
// 0th index is the guy who paid for the transaction
let (me, other_signers) = &mut keyed_accounts.split_at_mut(2);
let me = &mut me[1];
// TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
VoteInstruction::InitializeAccount(node_id, commission) => {
let vote_account = &mut keyed_accounts[0];
vote_state::initialize_account(vote_account, &node_id, commission)
vote_state::initialize_account(me, &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)
vote_state::authorize_voter(me, other_signers, &voter_id)
}
VoteInstruction::Vote(votes) => {
datapoint_warn!("vote-native", ("count", 1, i64));
let (vote_account, other_signers) = keyed_accounts.split_at_mut(1);
let vote_account = &mut vote_account[0];
vote_state::process_votes(vote_account, other_signers, &votes)
vote_state::process_votes(me, other_signers, &votes)
}
}
}
@ -102,7 +137,7 @@ mod tests {
use super::*;
use solana_sdk::account::Account;
// these are for 100% coverage
// these are for 100% coverage in this file
#[test]
fn test_vote_process_instruction_decode_bail() {
assert_eq!(
@ -146,11 +181,21 @@ mod tests {
Err(InstructionError::InvalidAccountData),
);
assert_eq!(
process_instruction(&vote(&Pubkey::default(), vec![Vote::default()])),
process_instruction(&vote(
&Pubkey::default(),
&Pubkey::default(),
&Pubkey::default(),
vec![Vote::default()]
)),
Err(InstructionError::InvalidAccountData),
);
assert_eq!(
process_instruction(&authorize_voter(&Pubkey::default(), &Pubkey::default())),
process_instruction(&authorize_voter(
&Pubkey::default(),
&Pubkey::default(),
&Pubkey::default(),
&Pubkey::default(),
)),
Err(InstructionError::InvalidAccountData),
);
}

View File

@ -44,7 +44,7 @@ pub enum WalletCommand {
Balance(Pubkey),
Cancel(Pubkey),
Confirm(Signature),
AuthorizeVoter(Pubkey),
AuthorizeVoter(Pubkey, Keypair, Pubkey),
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
ShowVoteAccount(Pubkey),
CreateStakeAccount(Pubkey, u64),
@ -193,8 +193,16 @@ pub fn parse_command(
))
}
("authorize-voter", Some(matches)) => {
let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap();
Ok(WalletCommand::AuthorizeVoter(authorized_voter_id))
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
let authorized_voter_keypair =
keypair_of(matches, "authorized_voter_keypair_file").unwrap();
let new_authorized_voter_id = pubkey_of(matches, "new_authorized_voter_id").unwrap();
Ok(WalletCommand::AuthorizeVoter(
voting_account_id,
authorized_voter_keypair,
new_authorized_voter_id,
))
}
("show-vote-account", Some(matches)) => {
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
@ -392,15 +400,23 @@ fn process_create_vote_account(
fn process_authorize_voter(
rpc_client: &RpcClient,
config: &WalletConfig,
authorized_voter_id: &Pubkey,
voting_account_id: &Pubkey,
authorized_voter_keypair: &Keypair,
new_authorized_voter_id: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![vote_instruction::authorize_voter(
&config.keypair.pubkey(),
authorized_voter_id,
&config.keypair.pubkey(), // from
voting_account_id, // vote account to update
&authorized_voter_keypair.pubkey(), // current authorized voter (often the vote account itself)
new_authorized_voter_id, // new vote signer
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let mut tx = Transaction::new_signed_instructions(
&[&config.keypair, &authorized_voter_keypair],
ixs,
recent_blockhash,
);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
Ok(signature_str.to_string())
}
@ -776,9 +792,17 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
)
}
// Configure staking account already created
WalletCommand::AuthorizeVoter(authorized_voter_id) => {
process_authorize_voter(&rpc_client, config, &authorized_voter_id)
}
WalletCommand::AuthorizeVoter(
voting_account_id,
authorized_voter_keypair,
new_authorized_voter_id,
) => process_authorize_voter(
&rpc_client,
config,
&voting_account_id,
&authorized_voter_keypair,
&new_authorized_voter_id,
),
// Show a vote account
WalletCommand::ShowVoteAccount(voting_account_id) => {
process_show_vote_account(&rpc_client, config, &voting_account_id)
@ -996,15 +1020,32 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
)
.subcommand(
SubCommand::with_name("authorize-voter")
.about("Authorize a different voter for this vote account")
.about("Authorize a new vote signing keypair for the given vote account")
.arg(
Arg::with_name("authorized_voter_id")
Arg::with_name("voting_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote signer to authorize"),
.help("Vote account in which to set the authorized voter"),
)
.arg(
Arg::with_name("authorized_voter_keypair_file")
.index(2)
.value_name("KEYPAIR_FILE")
.takes_value(true)
.required(true)
.help("Keypair file for the currently authorized vote signer"),
)
.arg(
Arg::with_name("new_authorized_voter_id")
.index(3)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("New vote signer to authorize"),
),
)
.subcommand(
@ -1313,13 +1354,20 @@ mod tests {
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
// Test AuthorizeVoter Subcommand
let test_authorize_voter =
test_commands
.clone()
.get_matches_from(vec!["test", "authorize-voter", &pubkey_string]);
let keypair_file = make_tmp_path("keypair_file");
gen_keypair_file(&keypair_file).unwrap();
let keypair = read_keypair(&keypair_file).unwrap();
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
"test",
"authorize-voter",
&pubkey_string,
&keypair_file,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_authorize_voter).unwrap(),
WalletCommand::AuthorizeVoter(pubkey)
WalletCommand::AuthorizeVoter(pubkey, keypair, pubkey)
);
// Test CreateVoteAccount SubCommand
@ -1545,7 +1593,8 @@ mod tests {
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
let bob_keypair = Keypair::new();
config.command = WalletCommand::AuthorizeVoter(bob_pubkey, bob_keypair, bob_pubkey);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -1663,7 +1712,7 @@ mod tests {
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
assert!(process_command(&config).is_err());
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
config.command = WalletCommand::AuthorizeVoter(bob_pubkey, Keypair::new(), bob_pubkey);
assert!(process_command(&config).is_err());
config.command = WalletCommand::GetTransactionCount;