Cherry-pick vote and stake authority changes (#6127)

* add authorized parameters to vote api (#6072)

* add authorized parameters to vote api

* code review

* add authorities to stake init (#6104)

* add authorities to stake init

* fixups

* code review
This commit is contained in:
Tyera Eulberg
2019-09-26 17:18:31 -06:00
committed by GitHub
parent 232d2b3899
commit 61930c0dd3
14 changed files with 947 additions and 428 deletions

View File

@@ -15,13 +15,16 @@ use solana_sdk::{
}; };
use solana_vote_api::{ use solana_vote_api::{
vote_instruction::{self, VoteError}, vote_instruction::{self, VoteError},
vote_state::VoteState, vote_state::{VoteAuthorize, VoteInit, VoteState},
}; };
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> { pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap(); let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
let commission = value_of(&matches, "commission").unwrap_or(0); 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 = crate::wallet::parse_amount_lamports( let lamports = crate::wallet::parse_amount_lamports(
matches.value_of("amount").unwrap(), matches.value_of("amount").unwrap(),
matches.value_of("unit"), matches.value_of("unit"),
@@ -30,21 +33,29 @@ pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<WalletComma
Ok(WalletCommand::CreateVoteAccount( Ok(WalletCommand::CreateVoteAccount(
vote_account_pubkey, vote_account_pubkey,
VoteInit {
node_pubkey, node_pubkey,
authorized_voter,
authorized_withdrawer,
commission, commission,
},
lamports, 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 vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let authorized_voter_keypair = keypair_of(matches, "authorized_voter_keypair_file").unwrap(); let authorized_keypair = keypair_of(matches, "authorized_keypair_file").unwrap();
let new_authorized_voter_pubkey = pubkey_of(matches, "new_authorized_voter_pubkey").unwrap(); let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap();
Ok(WalletCommand::AuthorizeVoter( Ok(WalletCommand::VoteAuthorize(
vote_account_pubkey, vote_account_pubkey,
authorized_voter_keypair, authorized_keypair,
new_authorized_voter_pubkey, new_authorized_pubkey,
vote_authorize,
)) ))
} }
@@ -63,13 +74,12 @@ pub fn process_create_vote_account(
rpc_client: &RpcClient, rpc_client: &RpcClient,
config: &WalletConfig, config: &WalletConfig,
vote_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey,
node_pubkey: &Pubkey, vote_init: &VoteInit,
commission: u8,
lamports: u64, lamports: u64,
) -> ProcessResult { ) -> ProcessResult {
check_unique_pubkeys( check_unique_pubkeys(
(vote_account_pubkey, "vote_account_pubkey".to_string()), (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( check_unique_pubkeys(
(&config.keypair.pubkey(), "wallet keypair".to_string()), (&config.keypair.pubkey(), "wallet keypair".to_string()),
@@ -78,8 +88,7 @@ pub fn process_create_vote_account(
let ixs = vote_instruction::create_account( let ixs = vote_instruction::create_account(
&config.keypair.pubkey(), &config.keypair.pubkey(),
vote_account_pubkey, vote_account_pubkey,
node_pubkey, vote_init,
commission,
lamports, lamports,
); );
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
@@ -89,36 +98,35 @@ pub fn process_create_vote_account(
log_instruction_custom_error::<SystemError>(result) log_instruction_custom_error::<SystemError>(result)
} }
pub fn process_authorize_voter( pub fn process_vote_authorize(
rpc_client: &RpcClient, rpc_client: &RpcClient,
config: &WalletConfig, config: &WalletConfig,
vote_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey,
authorized_voter_keypair: &Keypair, authorized_keypair: &Keypair,
new_authorized_voter_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
vote_authorize: VoteAuthorize,
) -> ProcessResult { ) -> ProcessResult {
check_unique_pubkeys( check_unique_pubkeys(
(vote_account_pubkey, "vote_account_pubkey".to_string()), (vote_account_pubkey, "vote_account_pubkey".to_string()),
( (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
new_authorized_voter_pubkey,
"new_authorized_voter_pubkey".to_string(),
),
)?; )?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; 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 vote_account_pubkey, // vote account to update
&authorized_voter_keypair.pubkey(), // current authorized voter (often the vote account itself) &authorized_keypair.pubkey(), // current authorized voter (often the vote account itself)
new_authorized_voter_pubkey, // new vote signer new_authorized_pubkey, // new vote signer
vote_authorize, // vote or withdraw
)]; )];
let mut tx = Transaction::new_signed_with_payer( let mut tx = Transaction::new_signed_with_payer(
ixs, ixs,
Some(&config.keypair.pubkey()), Some(&config.keypair.pubkey()),
&[&config.keypair, &authorized_voter_keypair], &[&config.keypair, &authorized_keypair],
recent_blockhash, recent_blockhash,
); );
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client let result =
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_voter_keypair]); rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_keypair]);
log_instruction_custom_error::<VoteError>(result) log_instruction_custom_error::<VoteError>(result)
} }
@@ -162,9 +170,10 @@ pub fn process_show_vote_account(
crate::wallet::build_balance_message(vote_account.lamports, use_lamports_unit) crate::wallet::build_balance_message(vote_account.lamports, use_lamports_unit)
); );
println!("node id: {}", vote_state.node_pubkey); println!("node id: {}", vote_state.node_pubkey);
println!("authorized voter: {}", vote_state.authorized_voter);
println!( println!(
"authorized voter pubkey: {}", "authorized withdrawer: {}",
vote_state.authorized_voter_pubkey vote_state.authorized_withdrawer
); );
println!("credits: {}", vote_state.credits()); println!("credits: {}", vote_state.credits());
println!( println!(
@@ -230,10 +239,7 @@ pub fn process_uptime(
})?; })?;
println!("Node id: {}", vote_state.node_pubkey); println!("Node id: {}", vote_state.node_pubkey);
println!( println!("Authorized voter: {}", vote_state.authorized_voter);
"Authorized voter pubkey: {}",
vote_state.authorized_voter_pubkey
);
if !vote_state.votes.is_empty() { if !vote_state.votes.is_empty() {
println!("Uptime:"); println!("Uptime:");
@@ -300,14 +306,14 @@ mod tests {
let test_authorize_voter = test_commands.clone().get_matches_from(vec![ let test_authorize_voter = test_commands.clone().get_matches_from(vec![
"test", "test",
"authorize-voter", "vote-authorize-voter",
&pubkey_string, &pubkey_string,
&keypair_file, &keypair_file,
&pubkey_string, &pubkey_string,
]); ]);
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_authorize_voter).unwrap(), 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(); fs::remove_file(&keypair_file).unwrap();
@@ -326,7 +332,16 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_create_vote_account).unwrap(), 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![ let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
"test", "test",
@@ -337,7 +352,63 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_create_vote_account2).unwrap(), parse_command(&pubkey, &test_create_vote_account2).unwrap(),
WalletCommand::CreateVoteAccount(pubkey, node_pubkey, 0, 858993459200) WalletCommand::CreateVoteAccount(
pubkey,
VoteInit {
node_pubkey,
authorized_voter: pubkey,
authorized_withdrawer: pubkey,
commission: 0
},
858993459200
)
);
// 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
},
858993459200
)
);
// 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
},
858993459200
)
); );
// Test Uptime Subcommand // Test Uptime Subcommand

View File

@@ -7,11 +7,9 @@ use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
use console::{style, Emoji}; use console::{style, Emoji};
use log::*; use log::*;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use serde_json; use serde_json::{self, json, Value};
use serde_json::{json, Value};
use solana_budget_api::budget_instruction::{self, BudgetError}; use solana_budget_api::budget_instruction::{self, BudgetError};
use solana_client::client_error::ClientError; use solana_client::{client_error::ClientError, rpc_client::RpcClient};
use solana_client::rpc_client::RpcClient;
#[cfg(not(test))] #[cfg(not(test))]
use solana_drone::drone::request_airdrop_transaction; use solana_drone::drone::request_airdrop_transaction;
#[cfg(test)] #[cfg(test)]
@@ -31,16 +29,21 @@ use solana_sdk::{
system_transaction, system_transaction,
transaction::{Transaction, TransactionError}, transaction::{Transaction, TransactionError},
}; };
use solana_stake_api::stake_instruction::{self, StakeError}; use solana_stake_api::{
stake_instruction::{self, StakeError},
stake_state::{Authorized, Lockup},
};
use solana_storage_api::storage_instruction; use solana_storage_api::storage_instruction;
use solana_vote_api::vote_state::VoteState; use solana_vote_api::vote_state::{VoteAuthorize, VoteInit, VoteState};
use std::collections::VecDeque; use std::{
use std::fs::File; collections::VecDeque,
use std::io::{Read, Write}; fs::File,
use std::net::{IpAddr, SocketAddr}; io::{Read, Write},
use std::thread::sleep; net::{IpAddr, SocketAddr},
use std::time::{Duration, Instant}; thread::sleep,
use std::{error, fmt}; time::{Duration, Instant},
{error, fmt},
};
const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE
@@ -64,8 +67,8 @@ pub enum WalletCommand {
}, },
Cancel(Pubkey), Cancel(Pubkey),
Confirm(Signature), Confirm(Signature),
AuthorizeVoter(Pubkey, Keypair, Pubkey), VoteAuthorize(Pubkey, Keypair, Pubkey, VoteAuthorize),
CreateVoteAccount(Pubkey, Pubkey, u8, u64), CreateVoteAccount(Pubkey, VoteInit, u64),
ShowAccount { ShowAccount {
pubkey: Pubkey, pubkey: Pubkey,
output_file: Option<String>, output_file: Option<String>,
@@ -80,7 +83,7 @@ pub enum WalletCommand {
aggregate: bool, aggregate: bool,
span: Option<u64>, span: Option<u64>,
}, },
DelegateStake(Keypair, Pubkey, u64, bool), DelegateStake(Keypair, Pubkey, u64, Authorized, bool),
WithdrawStake(Keypair, Pubkey, u64), WithdrawStake(Keypair, Pubkey, u64),
DeactivateStake(Keypair, Pubkey), DeactivateStake(Keypair, Pubkey),
RedeemVoteCredits(Pubkey, Pubkey), RedeemVoteCredits(Pubkey, Pubkey),
@@ -242,7 +245,12 @@ pub fn parse_command(
}) })
} }
("create-vote-account", Some(matches)) => parse_vote_create_account(matches), ("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), ("show-vote-account", Some(matches)) => parse_vote_get_account_command(matches),
("uptime", Some(matches)) => parse_vote_uptime_command(matches), ("uptime", Some(matches)) => parse_vote_uptime_command(matches),
("delegate-stake", Some(matches)) => { ("delegate-stake", Some(matches)) => {
@@ -252,11 +260,13 @@ pub fn parse_command(
matches.value_of("amount").unwrap(), matches.value_of("amount").unwrap(),
matches.value_of("unit"), matches.value_of("unit"),
)?; )?;
let authorized = Authorized::auto(&stake_account_keypair.pubkey());
let force = matches.is_present("force"); let force = matches.is_present("force");
Ok(WalletCommand::DelegateStake( Ok(WalletCommand::DelegateStake(
stake_account_keypair, stake_account_keypair,
vote_account_pubkey, vote_account_pubkey,
lamports, lamports,
authorized,
force, force,
)) ))
} }
@@ -602,6 +612,7 @@ fn process_delegate_stake(
stake_account_keypair: &Keypair, stake_account_keypair: &Keypair,
vote_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey,
lamports: u64, lamports: u64,
authorized: &Authorized,
force: bool, force: bool,
) -> ProcessResult { ) -> ProcessResult {
check_unique_pubkeys( check_unique_pubkeys(
@@ -618,6 +629,7 @@ fn process_delegate_stake(
&stake_account_keypair.pubkey(), &stake_account_keypair.pubkey(),
vote_account_pubkey, vote_account_pubkey,
lamports, lamports,
authorized,
); );
// Sanity check the vote account to ensure it is attached to a validator that has recently // Sanity check the vote account to ensure it is attached to a validator that has recently
@@ -735,8 +747,16 @@ fn process_show_stake_account(
format!("{:?} is not a stake account", stake_account_pubkey).to_string(), format!("{:?} is not a stake account", stake_account_pubkey).to_string(),
))?; ))?;
} }
fn show_authorized(authorized: &Authorized) {
println!("authorized staker: {}", authorized.staker);
println!("authorized withdrawer: {}", authorized.staker);
}
fn show_lockup(lockup: &Lockup) {
println!("lockup slot: {}", lockup.slot);
println!("lockup custodian: {}", lockup.custodian);
}
match stake_account.state() { match stake_account.state() {
Ok(StakeState::Stake(stake)) => { Ok(StakeState::Stake(authorized, lockup, stake)) => {
println!( println!(
"total stake: {}", "total stake: {}",
build_balance_message(stake_account.lamports, use_lamports_unit) build_balance_message(stake_account.lamports, use_lamports_unit)
@@ -759,11 +779,17 @@ fn process_show_stake_account(
stake.deactivation_epoch stake.deactivation_epoch
); );
} }
show_authorized(&authorized);
show_lockup(&lockup);
Ok("".to_string()) Ok("".to_string())
} }
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()), Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
Ok(StakeState::Uninitialized) | Ok(StakeState::Lockup(_)) => { Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
Ok("Stake account is uninitialized".to_string()) Ok(StakeState::Initialized(authorized, lockup)) => {
println!("Stake account is undelegated");
show_authorized(&authorized);
show_lockup(&lockup);
Ok("".to_string())
} }
Err(err) => Err(WalletError::RpcRequestError(format!( Err(err) => Err(WalletError::RpcRequestError(format!(
"Account data could not be deserialized to stake state: {:?}", "Account data could not be deserialized to stake state: {:?}",
@@ -1286,30 +1312,28 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature), WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
// Create vote account // Create vote account
WalletCommand::CreateVoteAccount( WalletCommand::CreateVoteAccount(vote_account_pubkey, vote_init, lamports) => {
vote_account_pubkey, process_create_vote_account(
node_pubkey,
commission,
lamports,
) => process_create_vote_account(
&rpc_client, &rpc_client,
config, config,
&vote_account_pubkey, &vote_account_pubkey,
&node_pubkey, &vote_init,
*commission,
*lamports, *lamports,
), )
}
WalletCommand::AuthorizeVoter( WalletCommand::VoteAuthorize(
vote_account_pubkey, vote_account_pubkey,
authorized_voter_keypair, authorized_keypair,
new_authorized_voter_pubkey, new_authorized_pubkey,
) => process_authorize_voter( vote_authorize,
) => process_vote_authorize(
&rpc_client, &rpc_client,
config, config,
&vote_account_pubkey, &vote_account_pubkey,
&authorized_voter_keypair, &authorized_keypair,
&new_authorized_voter_pubkey, &new_authorized_pubkey,
*vote_authorize,
), ),
WalletCommand::ShowAccount { WalletCommand::ShowAccount {
@@ -1344,6 +1368,7 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
stake_account_keypair, stake_account_keypair,
vote_account_pubkey, vote_account_pubkey,
lamports, lamports,
authorized,
force, force,
) => process_delegate_stake( ) => process_delegate_stake(
&rpc_client, &rpc_client,
@@ -1351,6 +1376,7 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
&stake_account_keypair, &stake_account_keypair,
&vote_account_pubkey, &vote_account_pubkey,
*lamports, *lamports,
&authorized,
*force, *force,
), ),
@@ -1675,7 +1701,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
), ),
) )
.subcommand( .subcommand(
SubCommand::with_name("authorize-voter") SubCommand::with_name("vote-authorize-voter")
.about("Authorize a new vote signing keypair for the given vote account") .about("Authorize a new vote signing keypair for the given vote account")
.arg( .arg(
Arg::with_name("vote_account_pubkey") Arg::with_name("vote_account_pubkey")
@@ -1687,7 +1713,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"), .help("Vote account in which to set the authorized voter"),
) )
.arg( .arg(
Arg::with_name("authorized_voter_keypair_file") Arg::with_name("authorized_keypair_file")
.index(2) .index(2)
.value_name("CURRENT VOTER KEYPAIR FILE") .value_name("CURRENT VOTER KEYPAIR FILE")
.takes_value(true) .takes_value(true)
@@ -1696,7 +1722,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"), .help("Keypair file for the currently authorized vote signer"),
) )
.arg( .arg(
Arg::with_name("new_authorized_voter_pubkey") Arg::with_name("new_authorized_pubkey")
.index(3) .index(3)
.value_name("NEW VOTER PUBKEY") .value_name("NEW VOTER PUBKEY")
.takes_value(true) .takes_value(true)
@@ -1705,6 +1731,37 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.help("New vote signer to authorize"), .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(
SubCommand::with_name("create-vote-account") SubCommand::with_name("create-vote-account")
.about("Create a vote account") .about("Create a vote account")
@@ -1747,7 +1804,25 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.value_name("NUM") .value_name("NUM")
.takes_value(true) .takes_value(true)
.help("The commission taken on reward redemption (0-255), default: 0"), .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(
SubCommand::with_name("show-account") SubCommand::with_name("show-account")
@@ -2425,9 +2500,16 @@ mod tests {
"42", "42",
"lamports", "lamports",
]); ]);
let stake_pubkey = keypair.pubkey();
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(), parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(keypair, pubkey, 42, false) WalletCommand::DelegateStake(
keypair,
pubkey,
42,
Authorized::auto(&stake_pubkey),
false,
)
); );
let keypair = read_keypair(&keypair_file).unwrap(); let keypair = read_keypair(&keypair_file).unwrap();
@@ -2440,9 +2522,16 @@ mod tests {
"42", "42",
"lamports", "lamports",
]); ]);
let stake_pubkey = keypair.pubkey();
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(), parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(keypair, pubkey, 42, true) WalletCommand::DelegateStake(
keypair,
pubkey,
42,
Authorized::auto(&stake_pubkey),
true
)
); );
// Test WithdrawStake Subcommand // Test WithdrawStake Subcommand
@@ -2669,14 +2758,27 @@ mod tests {
let bob_pubkey = Pubkey::new_rand(); let bob_pubkey = Pubkey::new_rand();
let node_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); let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string()); assert_eq!(signature.unwrap(), SIGNATURE.to_string());
let bob_keypair = Keypair::new(); let bob_keypair = Keypair::new();
let new_authorized_voter_pubkey = Pubkey::new_rand(); let new_authorized_pubkey = Pubkey::new_rand();
config.command = config.command = WalletCommand::VoteAuthorize(
WalletCommand::AuthorizeVoter(bob_pubkey, bob_keypair, new_authorized_voter_pubkey); bob_pubkey,
bob_keypair,
new_authorized_pubkey,
VoteAuthorize::Voter,
);
let signature = process_command(&config); let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string()); assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@@ -2826,10 +2928,24 @@ mod tests {
}; };
assert!(process_command(&config).is_err()); 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()); 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()); assert!(process_command(&config).is_err());
config.command = WalletCommand::GetSlot; config.command = WalletCommand::GetSlot;

View File

@@ -228,7 +228,7 @@ mod tests {
let ancestors = vec![3, 4, 5, 7, 9, 11]; let ancestors = vec![3, 4, 5, 7, 9, 11];
let mut confidence = HashMap::new(); let mut confidence = HashMap::new();
let lamports = 5; 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(); let root = ancestors.last().unwrap();
vote_state.root_slot = Some(*root); vote_state.root_slot = Some(*root);
@@ -251,7 +251,7 @@ mod tests {
let ancestors = vec![3, 4, 5, 7, 9, 11]; let ancestors = vec![3, 4, 5, 7, 9, 11];
let mut confidence = HashMap::new(); let mut confidence = HashMap::new();
let lamports = 5; let lamports = 5;
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); let mut vote_state = VoteState::default();
let root = ancestors[2]; let root = ancestors[2];
vote_state.root_slot = Some(root); vote_state.root_slot = Some(root);
@@ -281,7 +281,7 @@ mod tests {
let ancestors = vec![3, 4, 5, 7, 9, 10, 11]; let ancestors = vec![3, 4, 5, 7, 9, 10, 11];
let mut confidence = HashMap::new(); let mut confidence = HashMap::new();
let lamports = 5; let lamports = 5;
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); let mut vote_state = VoteState::default();
let root = ancestors[2]; let root = ancestors[2];
vote_state.root_slot = Some(root); vote_state.root_slot = Some(root);
@@ -319,18 +319,20 @@ mod tests {
mut genesis_block, .. mut genesis_block, ..
} = create_genesis_block(10_000); } = create_genesis_block(10_000);
let sk1 = Pubkey::new_rand();
let pk1 = Pubkey::new_rand(); let pk1 = Pubkey::new_rand();
let mut vote_account1 = vote_state::create_account(&pk1, &Pubkey::new_rand(), 0, 100); let mut vote_account1 = vote_state::create_account(&pk1, &Pubkey::new_rand(), 0, 100);
let stake_account1 = stake_state::create_account(&pk1, &vote_account1, 100); let stake_account1 = stake_state::create_account(&sk1, &pk1, &vote_account1, 100);
let sk2 = Pubkey::new_rand();
let pk2 = Pubkey::new_rand(); let pk2 = Pubkey::new_rand();
let mut vote_account2 = vote_state::create_account(&pk2, &Pubkey::new_rand(), 0, 50); let mut vote_account2 = vote_state::create_account(&pk2, &Pubkey::new_rand(), 0, 50);
let stake_account2 = stake_state::create_account(&pk2, &vote_account2, 50); let stake_account2 = stake_state::create_account(&sk2, &pk2, &vote_account2, 50);
genesis_block.accounts.extend(vec![ genesis_block.accounts.extend(vec![
(pk1, vote_account1.clone()), (pk1, vote_account1.clone()),
(Pubkey::new_rand(), stake_account1), (sk1, stake_account1),
(pk2, vote_account2.clone()), (pk2, vote_account2.clone()),
(Pubkey::new_rand(), stake_account2), (sk2, stake_account2),
]); ]);
// Create bank // Create bank

View File

@@ -1,9 +1,7 @@
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::account::Account; use solana_sdk::{account::Account, pubkey::Pubkey};
use solana_sdk::pubkey::Pubkey;
use solana_vote_api::vote_state::VoteState; use solana_vote_api::vote_state::VoteState;
use std::borrow::Borrow; use std::{borrow::Borrow, collections::HashMap};
use std::collections::HashMap;
/// Looks through vote accounts, and finds the latest slot that has achieved /// Looks through vote accounts, and finds the latest slot that has achieved
/// supermajority lockout /// supermajority lockout
@@ -99,14 +97,18 @@ where
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo, BOOTSTRAP_LEADER_LAMPORTS}; use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo, BOOTSTRAP_LEADER_LAMPORTS};
use solana_sdk::instruction::Instruction; use solana_sdk::{
use solana_sdk::pubkey::Pubkey; instruction::Instruction,
use solana_sdk::signature::{Keypair, KeypairUtil}; pubkey::Pubkey,
use solana_sdk::sysvar::stake_history::{self, StakeHistory}; signature::{Keypair, KeypairUtil},
use solana_sdk::transaction::Transaction; sysvar::stake_history::{self, StakeHistory},
use solana_stake_api::stake_instruction; transaction::Transaction,
use solana_stake_api::stake_state::Stake; };
use solana_vote_api::vote_instruction; use solana_stake_api::{
stake_instruction,
stake_state::{Authorized, Stake},
};
use solana_vote_api::{vote_instruction, vote_state::VoteInit};
use std::sync::Arc; use std::sync::Arc;
fn new_from_parent(parent: &Arc<Bank>, slot: u64) -> Bank { fn new_from_parent(parent: &Arc<Bank>, slot: u64) -> Bank {
@@ -140,8 +142,12 @@ pub(crate) mod tests {
vote_instruction::create_account( vote_instruction::create_account(
&from_account.pubkey(), &from_account.pubkey(),
vote_pubkey, vote_pubkey,
node_pubkey, &VoteInit {
0, node_pubkey: *node_pubkey,
authorized_voter: *vote_pubkey,
authorized_withdrawer: *vote_pubkey,
commission: 0,
},
amount, amount,
), ),
); );
@@ -157,6 +163,7 @@ pub(crate) mod tests {
&stake_account_pubkey, &stake_account_pubkey,
vote_pubkey, vote_pubkey,
amount, amount,
&Authorized::auto(&stake_account_pubkey),
), ),
); );
} }
@@ -288,15 +295,28 @@ pub(crate) mod tests {
fn test_to_staked_nodes() { fn test_to_staked_nodes() {
let mut stakes = Vec::new(); let mut stakes = Vec::new();
let node1 = Pubkey::new_rand(); let node1 = Pubkey::new_rand();
let node2 = Pubkey::new_rand();
// Node 1 has stake of 3 // Node 1 has stake of 3
for i in 0..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 // 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()); let result = to_staked_nodes(stakes.into_iter());
assert_eq!(result.len(), 2); assert_eq!(result.len(), 2);

View File

@@ -315,6 +315,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
1, 1,
); );
let stake_account = stake_state::create_account( let stake_account = stake_state::create_account(
&bootstrap_stake_keypair.pubkey(),
&bootstrap_vote_keypair.pubkey(), &bootstrap_vote_keypair.pubkey(),
&vote_account, &vote_account,
bootstrap_leader_stake_lamports, bootstrap_leader_stake_lamports,

View File

@@ -12,8 +12,7 @@ use solana_core::{
}; };
use solana_sdk::{ use solana_sdk::{
client::SyncClient, client::SyncClient,
clock::DEFAULT_TICKS_PER_SLOT, clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT},
clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT},
genesis_block::GenesisBlock, genesis_block::GenesisBlock,
message::Message, message::Message,
poh_config::PohConfig, poh_config::PohConfig,
@@ -22,9 +21,15 @@ use solana_sdk::{
system_transaction, system_transaction,
transaction::Transaction, transaction::Transaction,
}; };
use solana_stake_api::{config as stake_config, stake_instruction, stake_state::StakeState}; use solana_stake_api::{
config as stake_config, stake_instruction,
stake_state::{Authorized as StakeAuthorized, StakeState},
};
use solana_storage_api::{storage_contract, storage_instruction}; 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::{ use std::{
collections::HashMap, collections::HashMap,
fs::remove_dir_all, fs::remove_dir_all,
@@ -436,8 +441,12 @@ impl LocalCluster {
vote_instruction::create_account( vote_instruction::create_account(
&from_account.pubkey(), &from_account.pubkey(),
&vote_account_pubkey, &vote_account_pubkey,
&node_pubkey, &VoteInit {
0, node_pubkey,
authorized_voter: vote_account_pubkey,
authorized_withdrawer: vote_account_pubkey,
commission: 0,
},
amount, amount,
), ),
client.get_recent_blockhash().unwrap().0, client.get_recent_blockhash().unwrap().0,
@@ -456,6 +465,7 @@ impl LocalCluster {
&stake_account_pubkey, &stake_account_pubkey,
&vote_account_pubkey, &vote_account_pubkey,
amount, amount,
&StakeAuthorized::auto(&stake_account_pubkey),
), ),
client.get_recent_blockhash().unwrap().0, client.get_recent_blockhash().unwrap().0,
); );

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
config, id, config, id,
stake_state::{StakeAccount, StakeState}, stake_state::{Authorized, Lockup, StakeAccount, StakeAuthorize, StakeState},
}; };
use bincode::deserialize; use bincode::deserialize;
use log::*; use log::*;
@@ -8,7 +8,6 @@ use num_derive::{FromPrimitive, ToPrimitive};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::KeyedAccount, account::KeyedAccount,
clock::Slot,
instruction::{AccountMeta, Instruction, InstructionError}, instruction::{AccountMeta, Instruction, InstructionError},
instruction_processor_utils::DecodeError, instruction_processor_utils::DecodeError,
pubkey::Pubkey, pubkey::Pubkey,
@@ -36,30 +35,33 @@ impl std::fmt::Display for StakeError {
} }
impl std::error::Error for StakeError {} impl std::error::Error for StakeError {}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum StakeInstruction { pub enum StakeInstruction {
/// `Lockup` a stake until the specified slot /// `Initialize` a stake with Lockup and Authorized information
/// ///
/// Expects 1 Account: /// Expects 1 Account:
/// 0 - Uninitialized StakeAccount to be lockup'd /// 0 - Uninitialized StakeAccount
/// ///
/// The Slot parameter denotes slot height at which this stake /// Authorized carries pubkeys that must sign staker transactions
/// will allow withdrawal from the stake account. /// and withdrawer transactions.
/// The Pubkey parameter denotes a "custodian" account, the only /// Lockup carries information about withdrawal restrictions
/// account to which this stake will honor a withdrawal *before*
// lockup expires.
/// ///
Lockup((Slot, Pubkey)), Initialize(Authorized, Lockup),
/// Authorize a system account to manage stake /// Authorize a key to manage stake or withdrawal
/// requires Authorized::staker or Authorized::withdrawer
/// signature, depending on which key's being updated
/// ///
/// Expects 1 Account: /// Expects 1 Account:
/// 0 - Locked-up or delegated StakeAccount to be updated with authorized staker /// 0 - StakeAccount to be updated with the Pubkey for
Authorize(Pubkey), /// authorization
Authorize(Pubkey, StakeAuthorize),
/// `Delegate` a stake to a particular vote account /// `Delegate` a stake to a particular vote account
/// requires Authorized::staker signature
/// ///
/// Expects 4 Accounts: /// Expects 4 Accounts:
/// 0 - Lockup'd StakeAccount to be delegated <= transaction must have this signature /// 0 - Initialized StakeAccount to be delegated
/// 1 - VoteAccount to which this Stake will be delegated /// 1 - VoteAccount to which this Stake will be delegated
/// 2 - Clock sysvar Account that carries clock bank epoch /// 2 - Clock sysvar Account that carries clock bank epoch
/// 3 - Config Account that carries stake config /// 3 - Config Account that carries stake config
@@ -71,9 +73,10 @@ pub enum StakeInstruction {
DelegateStake, DelegateStake,
/// Redeem credits in the stake account /// Redeem credits in the stake account
/// requires Authorized::staker signature
/// ///
/// Expects 5 Accounts: /// Expects 5 Accounts:
/// 0 - Delegate StakeAccount to be updated with rewards /// 0 - StakeAccount to be updated with rewards
/// 1 - VoteAccount to which the Stake is delegated, /// 1 - VoteAccount to which the Stake is delegated,
/// 2 - RewardsPool Stake Account from which to redeem credits /// 2 - RewardsPool Stake Account from which to redeem credits
/// 3 - Rewards sysvar Account that carries points values /// 3 - Rewards sysvar Account that carries points values
@@ -81,21 +84,23 @@ pub enum StakeInstruction {
RedeemVoteCredits, RedeemVoteCredits,
/// Withdraw unstaked lamports from the stake account /// Withdraw unstaked lamports from the stake account
/// requires Authorized::withdrawer signature
/// ///
/// Expects 4 Accounts: /// Expects 4 Accounts:
/// 0 - Delegate StakeAccount <= transaction must have this signature /// 0 - StakeAccount from which to withdraw
/// 1 - System account to which the lamports will be transferred, /// 1 - System account to which the lamports will be transferred,
/// 2 - Syscall Account that carries epoch /// 2 - Syscall Account that carries epoch
/// 3 - StakeHistory sysvar that carries stake warmup/cooldown history /// 3 - StakeHistory sysvar that carries stake warmup/cooldown history
/// ///
/// The u64 is the portion of the Stake account balance to be withdrawn, /// The u64 is the portion of the Stake account balance to be withdrawn,
/// must be <= StakeAccount.lamports - staked lamports /// must be <= StakeAccount.lamports - staked lamports.
Withdraw(u64), Withdraw(u64),
/// Deactivates the stake in the account /// Deactivates the stake in the account
/// requires Authorized::staker signature
/// ///
/// Expects 3 Accounts: /// Expects 3 Accounts:
/// 0 - Delegate StakeAccount <= transaction must have this signature /// 0 - Delegate StakeAccount
/// 1 - VoteAccount to which the Stake is delegated /// 1 - VoteAccount to which the Stake is delegated
/// 2 - Syscall Account that carries epoch /// 2 - Syscall Account that carries epoch
/// ///
@@ -106,8 +111,8 @@ pub fn create_stake_account_with_lockup(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
stake_pubkey: &Pubkey, stake_pubkey: &Pubkey,
lamports: u64, lamports: u64,
lockup: Slot, authorized: &Authorized,
custodian: &Pubkey, lockup: &Lockup,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
vec![ vec![
system_instruction::create_account( system_instruction::create_account(
@@ -119,7 +124,7 @@ pub fn create_stake_account_with_lockup(
), ),
Instruction::new( Instruction::new(
id(), id(),
&StakeInstruction::Lockup((lockup, *custodian)), &StakeInstruction::Initialize(*authorized, *lockup),
vec![AccountMeta::new(*stake_pubkey, false)], vec![AccountMeta::new(*stake_pubkey, false)],
), ),
] ]
@@ -129,8 +134,15 @@ pub fn create_stake_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
stake_pubkey: &Pubkey, stake_pubkey: &Pubkey,
lamports: u64, lamports: u64,
authorized: &Authorized,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
create_stake_account_with_lockup(from_pubkey, stake_pubkey, lamports, 0, &Pubkey::default()) create_stake_account_with_lockup(
from_pubkey,
stake_pubkey,
lamports,
authorized,
&Lockup::default(),
)
} }
pub fn create_stake_account_and_delegate_stake( pub fn create_stake_account_and_delegate_stake(
@@ -138,21 +150,23 @@ pub fn create_stake_account_and_delegate_stake(
stake_pubkey: &Pubkey, stake_pubkey: &Pubkey,
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
lamports: u64, lamports: u64,
authorized: &Authorized,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports); let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports, authorized);
instructions.push(delegate_stake(stake_pubkey, vote_pubkey)); instructions.push(delegate_stake(stake_pubkey, vote_pubkey));
instructions instructions
} }
fn metas_for_authorized_staker( // for instructions that whose authorized signer may differ from the account's pubkey
stake_pubkey: &Pubkey, fn metas_for_authorized_signer(
authorized_pubkey: &Pubkey, // currently authorized account_pubkey: &Pubkey,
authorized_signer: &Pubkey, // currently authorized
other_params: &[AccountMeta], other_params: &[AccountMeta],
) -> Vec<AccountMeta> { ) -> Vec<AccountMeta> {
let is_own_signer = authorized_pubkey == stake_pubkey; let is_own_signer = authorized_signer == account_pubkey;
// stake account // vote account
let mut account_metas = vec![AccountMeta::new(*stake_pubkey, is_own_signer)]; let mut account_metas = vec![AccountMeta::new(*account_pubkey, is_own_signer)];
for meta in other_params { for meta in other_params {
account_metas.push(meta.clone()); account_metas.push(meta.clone());
@@ -160,7 +174,7 @@ fn metas_for_authorized_staker(
// append signer at the end // append signer at the end
if !is_own_signer { if !is_own_signer {
account_metas.push(AccountMeta::new_credit_only(*authorized_pubkey, true)) // signer account_metas.push(AccountMeta::new_credit_only(*authorized_signer, true)) // signer
} }
account_metas account_metas
@@ -170,12 +184,13 @@ pub fn authorize(
stake_pubkey: &Pubkey, stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey, authorized_pubkey: &Pubkey,
new_authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
stake_authorize: StakeAuthorize,
) -> Instruction { ) -> Instruction {
let account_metas = metas_for_authorized_staker(stake_pubkey, authorized_pubkey, &[]); let account_metas = metas_for_authorized_signer(stake_pubkey, authorized_pubkey, &[]);
Instruction::new( Instruction::new(
id(), id(),
&StakeInstruction::Authorize(*new_authorized_pubkey), &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
account_metas, account_metas,
) )
} }
@@ -239,8 +254,10 @@ pub fn process_instruction(
// TODO: data-driven unpack and dispatch of KeyedAccounts // TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
StakeInstruction::Lockup((lockup, custodian)) => me.lockup(lockup, &custodian), StakeInstruction::Initialize(authorized, lockup) => me.initialize(&authorized, &lockup),
StakeInstruction::Authorize(authorized_pubkey) => me.authorize(&authorized_pubkey, &rest), StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
me.authorize(&authorized_pubkey, stake_authorize, &rest)
}
StakeInstruction::DelegateStake => { StakeInstruction::DelegateStake => {
if rest.len() < 3 { if rest.len() < 3 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
@@ -366,7 +383,11 @@ mod tests {
super::process_instruction( super::process_instruction(
&Pubkey::default(), &Pubkey::default(),
&mut [], &mut [],
&serialize(&StakeInstruction::Lockup((0, Pubkey::default()))).unwrap(), &serialize(&StakeInstruction::Initialize(
Authorized::default(),
Lockup::default()
))
.unwrap(),
), ),
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
); );

View File

@@ -18,12 +18,12 @@ use solana_sdk::{
}; };
use solana_vote_api::vote_state::VoteState; use solana_vote_api::vote_state::VoteState;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum StakeState { pub enum StakeState {
Uninitialized, Uninitialized,
Lockup(Lockup), Initialized(Authorized, Lockup),
Stake(Stake), Stake(Authorized, Lockup, Stake),
RewardsPool, RewardsPool,
} }
@@ -43,26 +43,48 @@ impl StakeState {
Self::from(account).and_then(|state: Self| state.stake()) Self::from(account).and_then(|state: Self| state.stake())
} }
pub fn authorized_from(account: &Account) -> Option<Authorized> {
Self::from(account).and_then(|state: Self| state.authorized())
}
pub fn stake(&self) -> Option<Stake> { pub fn stake(&self) -> Option<Stake> {
match self { match self {
StakeState::Stake(stake) => Some(stake.clone()), StakeState::Stake(_authorized, _lockup, stake) => Some(*stake),
_ => None,
}
}
pub fn authorized(&self) -> Option<Authorized> {
match self {
StakeState::Stake(authorized, _lockup, _stake) => Some(*authorized),
_ => None, _ => None,
} }
} }
} }
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Lockup { pub enum StakeAuthorize {
/// slot height at which this stake will allow withdrawal, unless to the custodian Staker,
pub slot: Slot, Withdrawer,
/// custodian account, the only account to which this stake will honor a
/// withdrawal *before* lockup expires
pub custodian: Pubkey,
/// alternate signer that is enabled to act on the Stake account
pub authority: Pubkey,
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Lockup {
/// slot height at which this stake will allow withdrawal, unless
/// to the custodian
pub slot: Slot,
/// custodian account, the only account to which this stake will honor a
/// withdrawal before lockup expires. After lockup expires, custodian
/// is irrelevant
pub custodian: Pubkey,
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Authorized {
pub staker: Pubkey,
pub withdrawer: Pubkey,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Stake { pub struct Stake {
/// most recently delegated vote account pubkey /// most recently delegated vote account pubkey
pub voter_pubkey: Pubkey, pub voter_pubkey: Pubkey,
@@ -78,8 +100,6 @@ pub struct Stake {
pub deactivation_epoch: Epoch, pub deactivation_epoch: Epoch,
/// stake config (warmup, etc.) /// stake config (warmup, etc.)
pub config: Config, pub config: Config,
/// the Lockup information, see above
pub lockup: Lockup,
/// history of prior delegates and the epoch ranges for which /// history of prior delegates and the epoch ranges for which
/// they were set, circular buffer /// they were set, circular buffer
pub prior_delegates: [(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES], pub prior_delegates: [(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES],
@@ -92,7 +112,6 @@ const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exp
impl Default for Stake { impl Default for Stake {
fn default() -> Self { fn default() -> Self {
Self { Self {
lockup: Lockup::default(),
voter_pubkey: Pubkey::default(), voter_pubkey: Pubkey::default(),
voter_pubkey_epoch: 0, voter_pubkey_epoch: 0,
credits_observed: 0, credits_observed: 0,
@@ -106,20 +125,54 @@ impl Default for Stake {
} }
} }
impl Authorized {
pub fn auto(authorized: &Pubkey) -> Self {
Self {
staker: *authorized,
withdrawer: *authorized,
}
}
pub fn check(
&self,
stake_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
let authorized = match stake_authorize {
StakeAuthorize::Staker => Some(&self.staker),
StakeAuthorize::Withdrawer => Some(&self.withdrawer),
};
if stake_signer != authorized
&& other_signers
.iter()
.all(|account| account.signer_key() != authorized)
{
Err(InstructionError::MissingRequiredSignature)
} else {
Ok(())
}
}
pub fn authorize(
&mut self,
stake_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
new_authorized: &Pubkey,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
self.check(stake_signer, other_signers, stake_authorize)?;
match stake_authorize {
StakeAuthorize::Staker => self.staker = *new_authorized,
StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized,
}
Ok(())
}
}
impl Stake { impl Stake {
fn is_bootstrap(&self) -> bool { fn is_bootstrap(&self) -> bool {
self.activation_epoch == std::u64::MAX self.activation_epoch == std::u64::MAX
} }
fn check_authorized(
&self,
stake_pubkey_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> {
self.lockup
.check_authorized(stake_pubkey_signer, other_signers)
}
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
self.stake_activating_and_deactivating(epoch, history).0 self.stake_activating_and_deactivating(epoch, history).0
} }
@@ -310,7 +363,6 @@ impl Stake {
vote_state, vote_state,
std::u64::MAX, std::u64::MAX,
&Config::default(), &Config::default(),
&Lockup::default(),
) )
} }
@@ -340,7 +392,6 @@ impl Stake {
vote_state: &VoteState, vote_state: &VoteState,
activation_epoch: Epoch, activation_epoch: Epoch,
config: &Config, config: &Config,
lockup: &Lockup,
) -> Self { ) -> Self {
Self { Self {
stake, stake,
@@ -349,7 +400,6 @@ impl Stake {
voter_pubkey_epoch: activation_epoch, voter_pubkey_epoch: activation_epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
config: *config, config: *config,
lockup: *lockup,
..Stake::default() ..Stake::default()
} }
} }
@@ -359,29 +409,16 @@ impl Stake {
} }
} }
impl Lockup {
fn check_authorized(
&self,
stake_pubkey_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> {
let authorized = Some(&self.authority);
if stake_pubkey_signer != authorized
&& other_signers
.iter()
.all(|account| account.signer_key() != authorized)
{
return Err(InstructionError::MissingRequiredSignature);
}
Ok(())
}
}
pub trait StakeAccount { pub trait StakeAccount {
fn lockup(&mut self, slot: Slot, custodian: &Pubkey) -> Result<(), InstructionError>; fn initialize(
&mut self,
authorized: &Authorized,
lockup: &Lockup,
) -> Result<(), InstructionError>;
fn authorize( fn authorize(
&mut self, &mut self,
authorized_pubkey: &Pubkey, authority: &Pubkey,
stake_authorize: StakeAuthorize,
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn delegate_stake( fn delegate_stake(
@@ -415,13 +452,13 @@ pub trait StakeAccount {
} }
impl<'a> StakeAccount for KeyedAccount<'a> { impl<'a> StakeAccount for KeyedAccount<'a> {
fn lockup(&mut self, slot: Slot, custodian: &Pubkey) -> Result<(), InstructionError> { fn initialize(
&mut self,
authorized: &Authorized,
lockup: &Lockup,
) -> Result<(), InstructionError> {
if let StakeState::Uninitialized = self.state()? { if let StakeState::Uninitialized = self.state()? {
self.set_state(&StakeState::Lockup(Lockup { self.set_state(&StakeState::Initialized(*authorized, *lockup))
slot,
custodian: *custodian,
authority: *self.unsigned_key(),
}))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
@@ -432,17 +469,17 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
fn authorize( fn authorize(
&mut self, &mut self,
authority: &Pubkey, authority: &Pubkey,
stake_authorize: StakeAuthorize,
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let stake_state = self.state()?; let stake_state = self.state()?;
if let StakeState::Stake(mut stake) = stake_state {
stake.check_authorized(self.signer_key(), other_signers)?; if let StakeState::Stake(mut authorized, lockup, stake) = stake_state {
stake.lockup.authority = *authority; authorized.authorize(self.signer_key(), other_signers, authority, stake_authorize)?;
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else if let StakeState::Lockup(mut lockup) = stake_state { } else if let StakeState::Initialized(mut authorized, lockup) = stake_state {
lockup.check_authorized(self.signer_key(), other_signers)?; authorized.authorize(self.signer_key(), other_signers, authority, stake_authorize)?;
lockup.authority = *authority; self.set_state(&StakeState::Initialized(authorized, lockup))
self.set_state(&StakeState::Lockup(lockup))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
@@ -454,26 +491,25 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
config: &Config, config: &Config,
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let StakeState::Lockup(lockup) = self.state()? { if let StakeState::Initialized(authorized, lockup) = self.state()? {
lockup.check_authorized(self.signer_key(), other_signers)?; authorized.check(self.signer_key(), other_signers, StakeAuthorize::Staker)?;
let stake = Stake::new( let stake = Stake::new(
self.account.lamports, self.account.lamports,
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
clock.epoch, clock.epoch,
config, config,
&lockup,
); );
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else if let StakeState::Stake(mut stake) = self.state()? { } else if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
stake.check_authorized(self.signer_key(), other_signers)?; authorized.check(self.signer_key(), other_signers, StakeAuthorize::Staker)?;
stake.redelegate( stake.redelegate(
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
clock.epoch, clock.epoch,
)?; )?;
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
@@ -484,11 +520,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let StakeState::Stake(mut stake) = self.state()? { if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
stake.check_authorized(self.signer_key(), other_signers)?; authorized.check(self.signer_key(), other_signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch); stake.deactivate(clock.epoch);
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
@@ -500,7 +536,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
rewards: &sysvar::rewards::Rewards, rewards: &sysvar::rewards::Rewards,
stake_history: &sysvar::stake_history::StakeHistory, stake_history: &sysvar::stake_history::StakeHistory,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let (StakeState::Stake(mut stake), StakeState::RewardsPool) = if let (StakeState::Stake(authorized, lockup, mut stake), StakeState::RewardsPool) =
(self.state()?, rewards_account.state()?) (self.state()?, rewards_account.state()?)
{ {
let vote_state: VoteState = vote_account.state()?; let vote_state: VoteState = vote_account.state()?;
@@ -528,7 +564,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake.credits_observed = credits_observed; stake.credits_observed = credits_observed;
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else { } else {
// not worth collecting // not worth collecting
Err(StakeError::NoCreditsToRedeem.into()) Err(StakeError::NoCreditsToRedeem.into())
@@ -546,8 +582,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let lockup = match self.state()? { let lockup = match self.state()? {
StakeState::Stake(stake) => { StakeState::Stake(authorized, lockup, stake) => {
stake.check_authorized(self.signer_key(), other_signers)?; authorized.check(self.signer_key(), other_signers, StakeAuthorize::Withdrawer)?;
// if we have a deactivation epoch and we're in cooldown // if we have a deactivation epoch and we're in cooldown
let staked = if clock.epoch >= stake.deactivation_epoch { let staked = if clock.epoch >= stake.deactivation_epoch {
stake.stake(clock.epoch, Some(stake_history)) stake.stake(clock.epoch, Some(stake_history))
@@ -561,10 +597,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
if lamports > self.account.lamports.saturating_sub(staked) { if lamports > self.account.lamports.saturating_sub(staked) {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
stake.lockup lockup
} }
StakeState::Lockup(lockup) => { StakeState::Initialized(authorized, lockup) => {
lockup.check_authorized(self.signer_key(), other_signers)?; authorized.check(self.signer_key(), other_signers, StakeAuthorize::Withdrawer)?;
lockup lockup
} }
StakeState::Uninitialized => { StakeState::Uninitialized => {
@@ -615,17 +651,25 @@ where
} }
// utility function, used by Bank, tests, genesis // utility function, used by Bank, tests, genesis
pub fn create_account(voter_pubkey: &Pubkey, vote_account: &Account, lamports: u64) -> Account { pub fn create_account(
authorized: &Pubkey,
voter_pubkey: &Pubkey,
vote_account: &Account,
lamports: u64,
) -> Account {
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id()); let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
let vote_state = VoteState::from(vote_account).expect("vote_state"); let vote_state = VoteState::from(vote_account).expect("vote_state");
stake_account stake_account
.set_state(&StakeState::Stake(Stake::new_bootstrap( .set_state(&StakeState::Stake(
lamports, Authorized {
voter_pubkey, staker: *authorized,
&vote_state, withdrawer: *authorized,
))) },
Lockup::default(),
Stake::new_bootstrap(lamports, voter_pubkey, &vote_state),
))
.expect("set_state"); .expect("set_state");
stake_account stake_account
@@ -691,10 +735,13 @@ mod tests {
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(
authority: stake_pubkey, Authorized {
..Lockup::default() staker: stake_pubkey,
}), withdrawer: stake_pubkey,
},
Lockup::default(),
),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -707,10 +754,13 @@ mod tests {
let stake_state: StakeState = stake_keyed_account.state().unwrap(); let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!( assert_eq!(
stake_state, stake_state,
StakeState::Lockup(Lockup { StakeState::Initialized(
authority: stake_pubkey, Authorized {
..Lockup::default() staker: stake_pubkey,
}) withdrawer: stake_pubkey,
},
Lockup::default(),
)
); );
} }
@@ -741,10 +791,6 @@ mod tests {
stake: stake_lamports, stake: stake_lamports,
activation_epoch: clock.epoch, activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
lockup: Lockup {
authority: stake_pubkey,
..Lockup::default()
},
..Stake::default() ..Stake::default()
} }
); );
@@ -1062,21 +1108,32 @@ mod tests {
// unsigned keyed account // unsigned keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
let custodian = Pubkey::new_rand(); let custodian = Pubkey::new_rand();
assert_eq!(stake_keyed_account.lockup(1, &custodian), Ok(())); assert_eq!(
stake_keyed_account.initialize(
&Authorized {
staker: stake_pubkey,
withdrawer: stake_pubkey
},
&Lockup { slot: 1, custodian }
),
Ok(())
);
// first time works, as is uninit // first time works, as is uninit
assert_eq!( assert_eq!(
StakeState::from(&stake_keyed_account.account).unwrap(), StakeState::from(&stake_keyed_account.account).unwrap(),
StakeState::Lockup(Lockup { StakeState::Initialized(
slot: 1, Authorized {
authority: stake_pubkey, staker: stake_pubkey,
custodian withdrawer: stake_pubkey
}) },
Lockup { slot: 1, custodian }
)
); );
// 2nd time fails, can't move it from anything other than uninit->lockup // 2nd time fails, can't move it from anything other than uninit->lockup
assert_eq!( assert_eq!(
stake_keyed_account.lockup(1, &Pubkey::default()), stake_keyed_account.initialize(&Authorized::default(), &Lockup::default()),
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
); );
} }
@@ -1087,10 +1144,7 @@ mod tests {
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
authority: stake_pubkey,
..Lockup::default()
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -1195,7 +1249,12 @@ mod tests {
// lockup // lockup
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let custodian = Pubkey::new_rand(); let custodian = Pubkey::new_rand();
stake_keyed_account.lockup(0, &custodian).unwrap(); stake_keyed_account
.initialize(
&Authorized::auto(&stake_pubkey),
&Lockup { slot: 0, custodian },
)
.unwrap();
// signed keyed account and locked up, more than available should fail // signed keyed account and locked up, more than available should fail
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
@@ -1297,10 +1356,7 @@ mod tests {
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
total_lamports, total_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
authority: stake_pubkey,
..Lockup::default()
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -1381,17 +1437,16 @@ mod tests {
} }
#[test] #[test]
fn test_withdraw_lockout() { fn test_withdraw_lockup() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let custodian = Pubkey::new_rand(); let custodian = Pubkey::new_rand();
let total_lamports = 100; let total_lamports = 100;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
total_lamports, total_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(
slot: 1, Authorized::auto(&stake_pubkey),
authority: stake_pubkey, Lockup { slot: 1, custodian },
custodian, ),
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -1542,10 +1597,7 @@ mod tests {
let stake_lamports = 100; let stake_lamports = 100;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
authority: stake_pubkey,
..Lockup::default()
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -1673,10 +1725,7 @@ mod tests {
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
authority: stake_pubkey,
..Lockup::default()
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -1690,16 +1739,27 @@ mod tests {
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let stake_pubkey0 = Pubkey::new_rand(); let stake_pubkey0 = Pubkey::new_rand();
assert_eq!(stake_keyed_account.authorize(&stake_pubkey0, &[]), Ok(())); assert_eq!(
if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap() stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Staker, &[]),
Ok(())
);
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Withdrawer, &[]),
Ok(())
);
if let StakeState::Initialized(authorized, _lockup) =
StakeState::from(&stake_keyed_account.account).unwrap()
{ {
assert_eq!(lockup.authority, stake_pubkey0); assert_eq!(authorized.staker, stake_pubkey0);
assert_eq!(authorized.withdrawer, stake_pubkey0);
} else {
assert!(false);
} }
// A second authorization signed by the stake_keyed_account should fail // A second authorization signed by the stake_keyed_account should fail
let stake_pubkey1 = Pubkey::new_rand(); let stake_pubkey1 = Pubkey::new_rand();
assert_eq!( assert_eq!(
stake_keyed_account.authorize(&stake_pubkey1, &[]), stake_keyed_account.authorize(&stake_pubkey1, StakeAuthorize::Staker, &[]),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
@@ -1709,18 +1769,38 @@ mod tests {
// Test a second authorization by the newly authorized pubkey // Test a second authorization by the newly authorized pubkey
let stake_pubkey2 = Pubkey::new_rand(); let stake_pubkey2 = Pubkey::new_rand();
assert_eq!( assert_eq!(
stake_keyed_account.authorize(&stake_pubkey2, &[staker_keyed_account0]), stake_keyed_account.authorize(
&stake_pubkey2,
StakeAuthorize::Staker,
&[staker_keyed_account0]
),
Ok(()) Ok(())
); );
if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap() if let StakeState::Initialized(authorized, _lockup) =
StakeState::from(&stake_keyed_account.account).unwrap()
{ {
assert_eq!(lockup.authority, stake_pubkey2); assert_eq!(authorized.staker, stake_pubkey2);
}
let staker_keyed_account0 = KeyedAccount::new(&stake_pubkey0, true, &mut staker_account0);
assert_eq!(
stake_keyed_account.authorize(
&stake_pubkey2,
StakeAuthorize::Withdrawer,
&[staker_keyed_account0]
),
Ok(())
);
if let StakeState::Initialized(authorized, _lockup) =
StakeState::from(&stake_keyed_account.account).unwrap()
{
assert_eq!(authorized.staker, stake_pubkey2);
} }
let mut staker_account2 = Account::new(1, 0, &system_program::id()); let mut staker_account2 = Account::new(1, 0, &system_program::id());
let staker_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &mut staker_account2); let staker_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &mut staker_account2);
// Test an action by the currently authorized pubkey // Test an action by the currently authorized withdrawer
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
stake_lamports, stake_lamports,
@@ -1739,10 +1819,7 @@ mod tests {
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Lockup(Lockup { &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
authority: stake_pubkey,
..Lockup::default()
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@@ -1762,11 +1839,11 @@ mod tests {
let new_staker_pubkey = Pubkey::new_rand(); let new_staker_pubkey = Pubkey::new_rand();
assert_eq!( assert_eq!(
stake_keyed_account.authorize(&new_staker_pubkey, &[]), stake_keyed_account.authorize(&new_staker_pubkey, StakeAuthorize::Staker, &[]),
Ok(()) Ok(())
); );
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap(); let authorized = StakeState::authorized_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.lockup.authority, new_staker_pubkey); assert_eq!(authorized.staker, new_staker_pubkey);
let other_pubkey = Pubkey::new_rand(); let other_pubkey = Pubkey::new_rand();
let mut other_account = Account::new(1, 0, &system_program::id()); let mut other_account = Account::new(1, 0, &system_program::id());

View File

@@ -1,20 +1,27 @@
use assert_matches::assert_matches; use assert_matches::assert_matches;
use solana_runtime::bank::Bank; use solana_runtime::{
use solana_runtime::bank_client::BankClient; bank::Bank,
use solana_runtime::genesis_utils::{create_genesis_block_with_leader, GenesisBlockInfo}; bank_client::BankClient,
use solana_sdk::account_utils::State; genesis_utils::{create_genesis_block_with_leader, GenesisBlockInfo},
use solana_sdk::client::SyncClient; };
use solana_sdk::message::Message; use solana_sdk::{
use solana_sdk::pubkey::Pubkey; account_utils::State,
use solana_sdk::signature::{Keypair, KeypairUtil}; client::SyncClient,
use solana_sdk::sysvar; message::Message,
use solana_sdk::sysvar::rewards::Rewards; pubkey::Pubkey,
use solana_stake_api::id; signature::{Keypair, KeypairUtil},
use solana_stake_api::stake_instruction; sysvar,
use solana_stake_api::stake_instruction::process_instruction; sysvar::rewards::Rewards,
use solana_stake_api::stake_state::StakeState; };
use solana_vote_api::vote_instruction; use solana_stake_api::{
use solana_vote_api::vote_state::{Vote, VoteState}; id,
stake_instruction::{self, process_instruction},
stake_state::{self, StakeState},
};
use solana_vote_api::{
vote_instruction,
vote_state::{Vote, VoteInit, VoteState},
};
use std::sync::Arc; use std::sync::Arc;
fn fill_epoch_with_votes( fn fill_epoch_with_votes(
@@ -76,20 +83,26 @@ fn test_stake_account_delegate() {
let message = Message::new(vote_instruction::create_account( let message = Message::new(vote_instruction::create_account(
&mint_pubkey, &mint_pubkey,
&vote_pubkey, &vote_pubkey,
&node_pubkey, &VoteInit {
std::u8::MAX / 2, node_pubkey,
authorized_voter: vote_pubkey,
authorized_withdrawer: vote_pubkey,
commission: std::u8::MAX / 2,
},
10, 10,
)); ));
bank_client bank_client
.send_message(&[&mint_keypair], message) .send_message(&[&mint_keypair], message)
.expect("failed to create vote account"); .expect("failed to create vote account");
let authorized = stake_state::Authorized::auto(&staker_pubkey);
// Create stake account and delegate to vote account // Create stake account and delegate to vote account
let message = Message::new(stake_instruction::create_stake_account_and_delegate_stake( let message = Message::new(stake_instruction::create_stake_account_and_delegate_stake(
&mint_pubkey, &mint_pubkey,
&staker_pubkey, &staker_pubkey,
&vote_pubkey, &vote_pubkey,
20000, 20000,
&authorized,
)); ));
bank_client bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &staker_keypair], message)
@@ -98,7 +111,7 @@ fn test_stake_account_delegate() {
// Test that correct lamports are staked // Test that correct lamports are staked
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&staker_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(stake) = stake_state { if let StakeState::Stake(_authorized, _lockup, stake) = stake_state {
assert_eq!(stake.stake, 20000); assert_eq!(stake.stake, 20000);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")
@@ -120,7 +133,7 @@ fn test_stake_account_delegate() {
// Test that lamports are still staked // Test that lamports are still staked
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&staker_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(stake) = stake_state { if let StakeState::Stake(_authorized, _lockup, stake) = stake_state {
assert_eq!(stake.stake, 20000); assert_eq!(stake.stake, 20000);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")
@@ -164,7 +177,7 @@ fn test_stake_account_delegate() {
let rewards; let rewards;
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&staker_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(stake) = stake_state { if let StakeState::Stake(_authorized, _lockup, stake) = stake_state {
assert!(account.lamports > 20000); assert!(account.lamports > 20000);
assert_eq!(stake.stake, 20000); assert_eq!(stake.stake, 20000);
rewards = account.lamports - 20000; rewards = account.lamports - 20000;
@@ -247,7 +260,7 @@ fn test_stake_account_delegate() {
// Test that balance and stake is updated correctly (we have withdrawn all lamports except rewards) // Test that balance and stake is updated correctly (we have withdrawn all lamports except rewards)
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&staker_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(_stake) = stake_state { if let StakeState::Stake(_, _, _stake) = stake_state {
assert_eq!(account.lamports, rewards); assert_eq!(account.lamports, rewards);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")

View File

@@ -3,7 +3,7 @@
use crate::{ use crate::{
id, id,
vote_state::{self, Vote, VoteState}, vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState},
}; };
use bincode::deserialize; use bincode::deserialize;
use log::*; use log::*;
@@ -51,11 +51,11 @@ impl std::error::Error for VoteError {}
#[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`
/// takes a node_pubkey and commission InitializeAccount(VoteInit),
InitializeAccount(Pubkey, u8),
/// Authorize a voter to send signed votes. /// Authorize a voter to send signed votes or a withdrawer
AuthorizeVoter(Pubkey), /// to withdraw
Authorize(Pubkey, VoteAuthorize),
/// A Vote instruction with recent votes /// A Vote instruction with recent votes
Vote(Vote), Vote(Vote),
@@ -64,44 +64,38 @@ pub enum VoteInstruction {
Withdraw(u64), 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)]; let account_metas = vec![AccountMeta::new(*vote_pubkey, false)];
Instruction::new( Instruction::new(
id(), id(),
&VoteInstruction::InitializeAccount(*node_pubkey, commission), &VoteInstruction::InitializeAccount(*vote_init),
account_metas, 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( pub fn create_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
node_pubkey: &Pubkey, vote_init: &VoteInit,
commission: u8,
lamports: u64, lamports: u64,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
let space = VoteState::size_of() as u64; let space = VoteState::size_of() as u64;
let create_ix = let create_ix =
system_instruction::create_account(from_pubkey, vote_pubkey, lamports, space, &id()); 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] vec![create_ix, init_ix]
} }
// for instructions that whose authorized signer may differ from the account's pubkey
fn metas_for_authorized_signer( fn metas_for_authorized_signer(
vote_pubkey: &Pubkey, account_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey, // currently authorized authorized_signer: &Pubkey, // currently authorized
other_params: &[AccountMeta], other_params: &[AccountMeta],
) -> Vec<AccountMeta> { ) -> Vec<AccountMeta> {
let is_own_signer = authorized_voter_pubkey == vote_pubkey; let is_own_signer = authorized_signer == account_pubkey;
// vote account // 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 { for meta in other_params {
account_metas.push(meta.clone()); account_metas.push(meta.clone());
@@ -109,22 +103,23 @@ fn metas_for_authorized_signer(
// append signer at the end // append signer at the end
if !is_own_signer { 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 account_metas
} }
pub fn authorize_voter( pub fn authorize(
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey, // currently authorized authorized_pubkey: &Pubkey, // currently authorized
new_authorized_voter_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
vote_authorize: VoteAuthorize,
) -> Instruction { ) -> 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( Instruction::new(
id(), id(),
&VoteInstruction::AuthorizeVoter(*new_authorized_voter_pubkey), &VoteInstruction::Authorize(*new_authorized_pubkey, vote_authorize),
account_metas, 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) Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas)
} }
pub fn withdraw(vote_pubkey: &Pubkey, lamports: u64, to_pubkey: &Pubkey) -> Instruction { pub fn withdraw(
let account_metas = vec![ vote_pubkey: &Pubkey,
AccountMeta::new(*vote_pubkey, true), withdrawer_pubkey: &Pubkey,
AccountMeta::new_credit_only(*to_pubkey, false), 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) Instruction::new(id(), &VoteInstruction::Withdraw(lamports), account_metas)
} }
@@ -173,11 +174,11 @@ pub fn process_instruction(
// TODO: data-driven unpack and dispatch of KeyedAccounts // TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
VoteInstruction::InitializeAccount(node_pubkey, commission) => { VoteInstruction::InitializeAccount(vote_init) => {
vote_state::initialize_account(me, &node_pubkey, commission) vote_state::initialize_account(me, &vote_init)
} }
VoteInstruction::AuthorizeVoter(voter_pubkey) => { VoteInstruction::Authorize(voter_pubkey, vote_authorize) => {
vote_state::authorize_voter(me, rest, &voter_pubkey) vote_state::authorize(me, rest, &voter_pubkey, vote_authorize)
} }
VoteInstruction::Vote(vote) => { VoteInstruction::Vote(vote) => {
datapoint_info!("vote-native", ("count", 1, i64)); datapoint_info!("vote-native", ("count", 1, i64));
@@ -198,7 +199,10 @@ pub fn process_instruction(
if rest.is_empty() { if rest.is_empty() {
Err(InstructionError::InvalidInstructionData)?; 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( let instructions = create_account(
&Pubkey::default(), &Pubkey::default(),
&Pubkey::default(), &Pubkey::default(),
&Pubkey::default(), &VoteInit::default(),
0,
100, 100,
); );
assert_eq!( assert_eq!(
@@ -268,10 +271,20 @@ mod tests {
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
assert_eq!( assert_eq!(
process_instruction(&authorize_voter( process_instruction(&authorize(
&Pubkey::default(), &Pubkey::default(),
&Pubkey::default(), &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), Err(InstructionError::InvalidAccountData),
); );
@@ -285,4 +298,44 @@ mod tests {
assert!(minimum_balance as f64 / 2f64.powf(34.0) < 0.02) 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())
)
}
} }

View File

@@ -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)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct VoteState { pub struct VoteState {
pub votes: VecDeque<Lockout>, /// the node that votes in this account
pub node_pubkey: Pubkey, 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 /// fraction of std::u8::MAX that represents what part of a rewards
/// payout should be given to this VoteAccount /// payout should be given to this VoteAccount
pub commission: u8, pub commission: u8,
pub votes: VecDeque<Lockout>,
pub root_slot: Option<u64>, pub root_slot: Option<u64>,
/// clock epoch /// clock epoch
@@ -91,11 +110,12 @@ pub struct VoteState {
} }
impl VoteState { impl VoteState {
pub fn new(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u8) -> Self { pub fn new(vote_init: &VoteInit) -> Self {
Self { Self {
node_pubkey: *node_pubkey, node_pubkey: vote_init.node_pubkey,
authorized_voter_pubkey: *vote_pubkey, authorized_voter: vote_init.authorized_voter,
commission, authorized_withdrawer: vote_init.authorized_withdrawer,
commission: vote_init.commission,
..VoteState::default() ..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 /// but will implicitly withdraw authorization from the previously authorized
/// voter. The default voter is the owner of the vote account's pubkey. /// key
pub fn authorize_voter( pub fn authorize(
vote_account: &mut KeyedAccount, vote_account: &mut KeyedAccount,
other_signers: &[KeyedAccount], other_signers: &[KeyedAccount],
authorized_voter_pubkey: &Pubkey, authorized: &Pubkey,
vote_authorize: VoteAuthorize,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mut vote_state: VoteState = vote_account.state()?; let mut vote_state: VoteState = vote_account.state()?;
// clock authorized signer must say "yay" // current authorized signer must say "yay"
let authorized = Some(&vote_state.authorized_voter_pubkey); match vote_authorize {
if vote_account.signer_key() != authorized 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 && other_signers
.iter() .iter()
.all(|account| account.signer_key() != authorized) .all(|account| account.signer_key() != authorized)
{ {
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
Ok(())
vote_state.authorized_voter_pubkey = *authorized_voter_pubkey;
vote_account.set_state(&vote_state)
} }
/// Withdraw funds from the vote account /// Withdraw funds from the vote account
pub fn withdraw( pub fn withdraw(
vote_account: &mut KeyedAccount, vote_account: &mut KeyedAccount,
other_signers: &[KeyedAccount],
lamports: u64, lamports: u64,
to_account: &mut KeyedAccount, to_account: &mut KeyedAccount,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if vote_account.signer_key().is_none() { let vote_state: VoteState = vote_account.state()?;
return Err(InstructionError::MissingRequiredSignature);
} verify_authorized_signer(
&vote_state.authorized_withdrawer,
vote_account,
other_signers,
)?;
if vote_account.account.lamports < lamports { if vote_account.account.lamports < lamports {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
@@ -359,19 +409,14 @@ pub fn withdraw(
/// 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( pub fn initialize_account(
vote_account: &mut KeyedAccount, vote_account: &mut KeyedAccount,
node_pubkey: &Pubkey, vote_init: &VoteInit,
commission: u8,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let vote_state: VoteState = vote_account.state()?; 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); return Err(InstructionError::AccountAlreadyInitialized);
} }
vote_account.set_state(&VoteState::new( vote_account.set_state(&VoteState::new(vote_init))
vote_account.unsigned_key(),
node_pubkey,
commission,
))
} }
pub fn process_vote( pub fn process_vote(
@@ -383,19 +428,11 @@ pub fn process_vote(
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mut vote_state: VoteState = vote_account.state()?; 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); return Err(InstructionError::UninitializedAccount);
} }
let authorized = Some(&vote_state.authorized_voter_pubkey); verify_authorized_signer(&vote_state.authorized_voter, vote_account, other_signers)?;
// 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);
}
vote_state.process_vote(vote, slot_hashes, clock.epoch)?; vote_state.process_vote(vote, slot_hashes, clock.epoch)?;
vote_account.set_state(&vote_state) vote_account.set_state(&vote_state)
@@ -410,7 +447,12 @@ pub fn create_account(
) -> Account { ) -> Account {
let mut vote_account = Account::new(lamports, VoteState::size_of(), &id()); 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) .to(&mut vote_account)
.unwrap(); .unwrap();
@@ -427,6 +469,17 @@ mod tests {
const MAX_RECENT_VOTES: usize = 16; 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] #[test]
fn test_initialize_vote_account() { fn test_initialize_vote_account() {
let vote_account_pubkey = Pubkey::new_rand(); let vote_account_pubkey = Pubkey::new_rand();
@@ -436,11 +489,27 @@ mod tests {
//init should pass //init should pass
let mut vote_account = KeyedAccount::new(&vote_account_pubkey, false, &mut vote_account); 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(())); assert_eq!(res, Ok(()));
// reinit should fail // 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)); assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized));
} }
@@ -504,7 +573,7 @@ mod tests {
let (vote_pubkey, vote_account) = create_test_account(); let (vote_pubkey, vote_account) = create_test_account();
let vote_state: VoteState = vote_account.state().unwrap(); 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()); assert!(vote_state.votes.is_empty());
} }
@@ -582,21 +651,23 @@ mod tests {
// another voter // another voter
let authorized_voter_pubkey = Pubkey::new_rand(); let authorized_voter_pubkey = Pubkey::new_rand();
let res = authorize_voter( let res = authorize(
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
&[], &[],
&authorized_voter_pubkey, &authorized_voter_pubkey,
VoteAuthorize::Voter,
); );
assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
let res = authorize_voter( let res = authorize(
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
&[], &[],
&authorized_voter_pubkey, &authorized_voter_pubkey,
VoteAuthorize::Voter,
); );
assert_eq!(res, Ok(())); assert_eq!(res, Ok(()));
// verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;) // 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), &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
&[KeyedAccount::new( &[KeyedAccount::new(
&authorized_voter_pubkey, &authorized_voter_pubkey,
@@ -604,6 +675,31 @@ mod tests {
&mut Account::default(), &mut Account::default(),
)], )],
&authorized_voter_pubkey, &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(())); assert_eq!(res, Ok(()));
@@ -678,7 +774,7 @@ mod tests {
#[test] #[test]
fn test_vote_double_lockout_after_expiration() { fn test_vote_double_lockout_after_expiration() {
let voter_pubkey = Pubkey::new_rand(); 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 { for i in 0..3 {
vote_state.process_slot_vote_unchecked(i as u64); vote_state.process_slot_vote_unchecked(i as u64);
@@ -706,7 +802,7 @@ mod tests {
#[test] #[test]
fn test_expire_multiple_votes() { fn test_expire_multiple_votes() {
let voter_pubkey = Pubkey::new_rand(); 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 { for i in 0..3 {
vote_state.process_slot_vote_unchecked(i as u64); vote_state.process_slot_vote_unchecked(i as u64);
@@ -737,7 +833,7 @@ mod tests {
#[test] #[test]
fn test_vote_credits() { fn test_vote_credits() {
let voter_pubkey = Pubkey::new_rand(); 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 { for i in 0..MAX_LOCKOUT_HISTORY {
vote_state.process_slot_vote_unchecked(i as u64); vote_state.process_slot_vote_unchecked(i as u64);
@@ -756,7 +852,7 @@ mod tests {
#[test] #[test]
fn test_duplicate_vote() { fn test_duplicate_vote() {
let voter_pubkey = Pubkey::new_rand(); 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(0);
vote_state.process_slot_vote_unchecked(1); vote_state.process_slot_vote_unchecked(1);
vote_state.process_slot_vote_unchecked(0); vote_state.process_slot_vote_unchecked(0);
@@ -768,7 +864,7 @@ mod tests {
#[test] #[test]
fn test_nth_recent_vote() { fn test_nth_recent_vote() {
let voter_pubkey = Pubkey::new_rand(); 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 { for i in 0..MAX_LOCKOUT_HISTORY {
vote_state.process_slot_vote_unchecked(i as u64); vote_state.process_slot_vote_unchecked(i as u64);
} }
@@ -799,9 +895,9 @@ mod tests {
#[test] #[test]
fn test_process_missed_votes() { fn test_process_missed_votes() {
let account_a = Pubkey::new_rand(); 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 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 // process some votes on account a
(0..5) (0..5)
@@ -821,7 +917,7 @@ mod tests {
#[test] #[test]
fn test_process_vote_skips_old_vote() { 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 vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(0, vote.hash)]; let slot_hashes: Vec<_> = vec![(0, vote.hash)];
@@ -836,7 +932,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_vote_empty_slot_hashes() { 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()); let vote = Vote::new(vec![0], Hash::default());
assert_eq!( assert_eq!(
@@ -847,7 +943,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_new_vote() { 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 vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
@@ -859,7 +955,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_bad_hash() { 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 vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))]; let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
@@ -871,7 +967,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_bad_slot() { 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 vote = Vote::new(vec![1], Hash::default());
let slot_hashes: Vec<_> = vec![(0, vote.hash)]; let slot_hashes: Vec<_> = vec![(0, vote.hash)];
@@ -883,7 +979,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_duplicate_vote() { 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 vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
@@ -896,7 +992,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_next_vote() { 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 vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
@@ -912,7 +1008,7 @@ mod tests {
#[test] #[test]
fn test_check_slots_are_valid_next_vote_only() { 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 vote = Vote::new(vec![0], Hash::default());
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
@@ -925,17 +1021,28 @@ mod tests {
Ok(()) 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] #[test]
fn test_vote_state_commission_split() { 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)); 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)); 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); let (voter_portion, staker_portion, was_split) = vote_state.commission_split(10.0);
assert_eq!( assert_eq!(
@@ -948,9 +1055,10 @@ mod tests {
fn test_vote_state_withdraw() { fn test_vote_state_withdraw() {
let (vote_pubkey, mut vote_account) = create_test_account(); let (vote_pubkey, mut vote_account) = create_test_account();
// unsigned // unsigned request
let res = withdraw( let res = withdraw(
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
&[],
0, 0,
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()), &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()),
); );
@@ -959,6 +1067,7 @@ mod tests {
// insufficient funds // insufficient funds
let res = withdraw( let res = withdraw(
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
&[],
101, 101,
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()), &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()),
); );
@@ -969,6 +1078,25 @@ mod tests {
let lamports = vote_account.lamports; let lamports = vote_account.lamports;
let res = withdraw( let res = withdraw(
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), &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, lamports,
&mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account), &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account),
); );

View File

@@ -1584,7 +1584,7 @@ mod tests {
use solana_sdk::sysvar::{fees::Fees, rewards::Rewards}; use solana_sdk::sysvar::{fees::Fees, rewards::Rewards};
use solana_stake_api::stake_state::Stake; use solana_stake_api::stake_state::Stake;
use solana_vote_api::vote_instruction; 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::io::Cursor;
use std::time::Duration; use std::time::Duration;
use tempfile::TempDir; use tempfile::TempDir;
@@ -2861,8 +2861,12 @@ mod tests {
let instructions = vote_instruction::create_account( let instructions = vote_instruction::create_account(
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
&vote_keypair.pubkey(), &vote_keypair.pubkey(),
&mint_keypair.pubkey(), &VoteInit {
0, node_pubkey: mint_keypair.pubkey(),
authorized_voter: vote_keypair.pubkey(),
authorized_withdrawer: vote_keypair.pubkey(),
commission: 0,
},
10, 10,
); );

View File

@@ -41,6 +41,7 @@ pub fn create_genesis_block_with_leader(
); );
let stake_account = stake_state::create_account( let stake_account = stake_state::create_account(
&staking_keypair.pubkey(),
&voting_keypair.pubkey(), &voting_keypair.pubkey(),
&vote_account, &vote_account,
bootstrap_leader_stake_lamports, bootstrap_leader_stake_lamports,

View File

@@ -220,9 +220,11 @@ pub mod tests {
// add stake to a vote_pubkey ( stake ) // add stake to a vote_pubkey ( stake )
pub fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, Account) { pub fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, Account) {
let stake_pubkey = Pubkey::new_rand();
( (
Pubkey::new_rand(), stake_pubkey,
stake_state::create_account( stake_state::create_account(
&stake_pubkey,
&vote_pubkey, &vote_pubkey,
&vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1), &vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1),
stake, stake,