Add thin client test for vote functionality, fix sizing errors in vote contract (#1643)

* Added tests to thin client to test VoteContract calls, fix VoteContract sizing errors

* Calculate upper bound on VoteProgram size at runtime, add test for serializing/deserializing a max sized VoteProgram state
This commit is contained in:
carllin 2018-10-31 17:47:50 -07:00 committed by GitHub
parent 6ddd494826
commit ba884b4e36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 31 deletions

View File

@ -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<u64>) -> R
Ok(leader.unwrap().clone())
}
pub fn retry_get_balance(
client: &mut ThinClient,
bob_pubkey: &Pubkey,
expected_balance: Option<i64>,
) -> Option<i64> {
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

View File

@ -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<T> = std::result::Result<T, Error>;
#[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<Vote>,
pub node_id: Pubkey,
@ -70,7 +68,7 @@ impl VoteProgram {
pub fn deserialize(input: &[u8]) -> Result<VoteProgram> {
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::<VoteProgram>()
+ MAX_VOTE_HISTORY * mem::size_of::<Vote>()
+ mem::size_of::<Pubkey>()
+ mem::size_of::<u16>()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serde() -> Result<()> {
let mut buffer: Vec<u8> = 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(())
}
}

View File

@ -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,
)

View File

@ -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<i64>,
) -> Option<i64> {
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,