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:
parent
6ddd494826
commit
ba884b4e36
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue