diff --git a/src/thin_client.rs b/src/thin_client.rs index e77b6ed891..626649c046 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -161,7 +161,7 @@ impl ThinClient { self.transfer_signed(&tx) } - /// Creates, signs, and processes a Transaction. Useful for writing unit-tests. + /// Creates, signs, and processes a vote Transaction. Useful for writing unit-tests. pub fn register_vote_account( &self, node_keypair: &Keypair, @@ -470,6 +470,32 @@ pub fn poll_gossip_for_leader(leader_ncp: SocketAddr, timeout: Option) -> R Ok(leader.unwrap().clone()) } +pub fn retry_get_balance( + client: &mut ThinClient, + bob_pubkey: &Pubkey, + expected_balance: Option, +) -> Option { + const LAST: usize = 30; + for run in 0..LAST { + let balance_result = client.poll_get_balance(bob_pubkey); + if expected_balance.is_none() { + return balance_result.ok(); + } + trace!( + "retry_get_balance[{}] {:?} {:?}", + run, + balance_result, + expected_balance + ); + if let (Some(expected_balance), Ok(balance_result)) = (expected_balance, balance_result) { + if expected_balance == balance_result { + return Some(balance_result); + } + } + } + None +} + #[cfg(test)] mod tests { use super::*; @@ -483,6 +509,7 @@ mod tests { use signature::{Keypair, KeypairUtil}; use std::fs::remove_dir_all; use system_program::SystemProgram; + use vote_program::VoteProgram; #[test] #[ignore] @@ -659,6 +686,104 @@ mod tests { remove_dir_all(ledger_path).unwrap(); } + #[test] + fn test_register_vote_account() { + logger::setup(); + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let mint = Mint::new(10_000); + let mut bank = Bank::new(&mint); + let leader_data = leader.info.clone(); + let ledger_path = create_tmp_ledger_with_mint("client_check_signature", &mint); + + let genesis_entries = &mint.create_entries(); + let entry_height = genesis_entries.len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let leader_vote_account_keypair = Arc::new(Keypair::new()); + let server = Fullnode::new_with_bank( + leader_keypair, + leader_vote_account_keypair.clone(), + bank, + 0, + entry_height, + &genesis_entries, + leader, + None, + &ledger_path, + false, + Some(0), + ); + sleep(Duration::from_millis(300)); + + let requests_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + requests_socket + .set_read_timeout(Some(Duration::new(5, 0))) + .unwrap(); + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new( + leader_data.contact_info.rpu, + requests_socket, + leader_data.contact_info.tpu, + transactions_socket, + ); + + // Create the validator account, transfer some tokens to that account + let validator_keypair = Keypair::new(); + let last_id = client.get_last_id(); + let signature = client + .transfer(500, &mint.keypair(), validator_keypair.pubkey(), &last_id) + .unwrap(); + + assert!(client.poll_for_signature(&signature).is_ok()); + + // Create the vote account + let validator_vote_account_keypair = Keypair::new(); + let vote_account_id = validator_vote_account_keypair.pubkey(); + let last_id = client.get_last_id(); + let signature = client + .create_vote_account(&validator_keypair, vote_account_id, &last_id, 1) + .unwrap(); + + assert!(client.poll_for_signature(&signature).is_ok()); + let balance = retry_get_balance(&mut client, &vote_account_id, Some(1)) + .expect("Expected balance for new account to exist"); + assert_eq!(balance, 1); + + // Register the vote account to the validator + let last_id = client.get_last_id(); + let signature = client + .register_vote_account(&validator_keypair, vote_account_id, &last_id) + .unwrap(); + assert!(client.poll_for_signature(&signature).is_ok()); + + const LAST: usize = 30; + for run in 0..=LAST { + println!("Checking for account registered: {}", run); + let account_user_data = client + .get_account_userdata(&vote_account_id) + .expect("Expected valid response for account userdata") + .expect("Expected valid account userdata to exist after account creation"); + + let vote_state = VoteProgram::deserialize(&account_user_data); + + if vote_state.map(|vote_state| vote_state.node_id) == Ok(validator_keypair.pubkey()) { + break; + } + + if run == LAST { + panic!("Expected successful vote account registration"); + } + sleep(Duration::from_millis(900)); + } + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); + } + #[test] fn test_transaction_count() { // set a bogus address, see that we don't hang diff --git a/src/vote_program.rs b/src/vote_program.rs index 819f2e7343..477f8e9b1c 100644 --- a/src/vote_program.rs +++ b/src/vote_program.rs @@ -7,11 +7,9 @@ use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; use std; use std::collections::VecDeque; +use std::mem; use transaction::Transaction; -// Upper limit on the size of the Vote State -pub const MAX_STATE_SIZE: usize = 1024; - // Maximum number of votes to keep around const MAX_VOTE_HISTORY: usize = 32; @@ -29,7 +27,7 @@ impl std::fmt::Display for Error { } pub type Result = std::result::Result; -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Vote { // TODO: add signature of the state here as well /// A vote for height tick_height @@ -47,7 +45,7 @@ pub enum VoteInstruction { NewVote(Vote), } -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct VoteProgram { pub votes: VecDeque, pub node_id: Pubkey, @@ -70,7 +68,7 @@ impl VoteProgram { pub fn deserialize(input: &[u8]) -> Result { let len = LittleEndian::read_u16(&input[0..2]) as usize; - if len == 0 || input.len() < len + 1 { + if len == 0 || input.len() < len + 2 { Err(Error::InvalidUserdata) } else { deserialize(&input[2..=len + 1]).map_err(|err| { @@ -149,4 +147,29 @@ impl VoteProgram { } } } + + pub fn get_max_size() -> usize { + // Upper limit on the size of the Vote State. Equal to + // sizeof(VoteProgram) + MAX_VOTE_HISTORY * sizeof(Vote) + + // 32 (the size of the Pubkey) + 2 (2 bytes for the size) + mem::size_of::() + + MAX_VOTE_HISTORY * mem::size_of::() + + mem::size_of::() + + mem::size_of::() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serde() -> Result<()> { + let mut buffer: Vec = vec![0; VoteProgram::get_max_size()]; + let mut vote_program = VoteProgram::default(); + vote_program.votes = (0..MAX_VOTE_HISTORY).map(|_| Vote::default()).collect(); + vote_program.serialize(&mut buffer).unwrap(); + assert_eq!(VoteProgram::deserialize(&buffer).unwrap(), vote_program); + Ok(()) + } } diff --git a/src/vote_transaction.rs b/src/vote_transaction.rs index fe5edb835a..e13cf74df8 100644 --- a/src/vote_transaction.rs +++ b/src/vote_transaction.rs @@ -6,7 +6,7 @@ use signature::Keypair; use solana_sdk::pubkey::Pubkey; use system_transaction::SystemTransaction; use transaction::Transaction; -use vote_program::{Vote, VoteInstruction, VoteProgram, MAX_STATE_SIZE}; +use vote_program::{Vote, VoteInstruction, VoteProgram}; pub trait VoteTransaction { fn vote_new(vote_account: &Keypair, vote: Vote, last_id: Hash, fee: i64) -> Self; @@ -43,7 +43,7 @@ impl VoteTransaction for Transaction { new_vote_account_id, last_id, num_tokens, - MAX_STATE_SIZE as u64, + VoteProgram::get_max_size() as u64, VoteProgram::id(), 0, ) diff --git a/tests/multinode.rs b/tests/multinode.rs index dacb2b0787..516f21ea90 100644 --- a/tests/multinode.rs +++ b/tests/multinode.rs @@ -21,7 +21,7 @@ use solana::result; use solana::service::Service; use solana::signature::{Keypair, KeypairUtil}; use solana::system_transaction::SystemTransaction; -use solana::thin_client::ThinClient; +use solana::thin_client::{retry_get_balance, ThinClient}; use solana::timing::{duration_as_ms, duration_as_s}; use solana::transaction::Transaction; use solana::window::default_window; @@ -1470,27 +1470,6 @@ fn mk_client(leader: &NodeInfo) -> ThinClient { ) } -fn retry_get_balance( - client: &mut ThinClient, - bob_pubkey: &Pubkey, - expected: Option, -) -> Option { - const LAST: usize = 30; - for run in 0..(LAST + 1) { - let out = client.poll_get_balance(bob_pubkey); - if expected.is_none() || run == LAST { - return out.ok().clone(); - } - trace!("retry_get_balance[{}] {:?} {:?}", run, out, expected); - if let (Some(e), Ok(o)) = (expected, out) { - if o == e { - return Some(o); - } - } - } - None -} - fn send_tx_and_retry_get_balance( leader: &NodeInfo, alice: &Mint,