By passing a config and not a Arc'ed LeaderScheduler, callers need to use `Bank::leader_scheduler` to access the scheduler. By using the new constructor, there should be no incentive to reach into the bank for that object.
2095 lines
68 KiB
Rust
2095 lines
68 KiB
Rust
use log::*;
|
|
use solana::blob_fetch_stage::BlobFetchStage;
|
|
use solana::blocktree::{
|
|
create_tmp_sample_ledger, tmp_copy_ledger, Blocktree, BlocktreeConfig, DEFAULT_SLOT_HEIGHT,
|
|
};
|
|
use solana::client::mk_client;
|
|
use solana::cluster_info::{Node, NodeInfo};
|
|
use solana::entry::{reconstruct_entries_from_blobs, Entry};
|
|
use solana::fullnode::{new_bank_from_ledger, Fullnode, FullnodeConfig, FullnodeReturnType};
|
|
use solana::gossip_service::{converge, make_listening_node};
|
|
use solana::leader_scheduler::{make_active_set_entries, LeaderScheduler, LeaderSchedulerConfig};
|
|
use solana::result;
|
|
use solana::service::Service;
|
|
use solana::thin_client::{poll_gossip_for_leader, retry_get_balance};
|
|
use solana::voting_keypair::VotingKeypair;
|
|
use solana_sdk::pubkey::Pubkey;
|
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
|
use solana_sdk::system_transaction::SystemTransaction;
|
|
use solana_sdk::timing::duration_as_s;
|
|
use std::collections::{HashSet, VecDeque};
|
|
use std::env;
|
|
use std::fs::remove_dir_all;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::mpsc::{channel, TryRecvError};
|
|
use std::sync::{Arc, RwLock};
|
|
use std::thread::{sleep, Builder};
|
|
use std::time::{Duration, Instant};
|
|
|
|
fn read_ledger(ledger_path: &str, blocktree_config: &BlocktreeConfig) -> Vec<Entry> {
|
|
let ledger =
|
|
Blocktree::open_config(&ledger_path, blocktree_config).expect("Unable to open ledger");
|
|
ledger
|
|
.read_ledger()
|
|
.expect("Unable to read ledger")
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn test_multi_node_ledger_window() -> result::Result<()> {
|
|
solana_logger::setup();
|
|
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_data = leader.info.clone();
|
|
let bob_pubkey = Keypair::new().pubkey();
|
|
let mut ledger_paths = Vec::new();
|
|
|
|
let fullnode_config = FullnodeConfig::default();
|
|
info!(
|
|
"ticks_per_slot: {}",
|
|
fullnode_config.leader_scheduler_config.ticks_per_slot
|
|
);
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
|
|
let (
|
|
alice,
|
|
leader_ledger_path,
|
|
tick_height,
|
|
mut last_entry_height,
|
|
_last_id,
|
|
mut last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"multi_node_ledger_window",
|
|
10_000,
|
|
0,
|
|
leader_data.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
|
|
// make a copy at zero
|
|
let zero_ledger_path = tmp_copy_ledger(
|
|
&leader_ledger_path,
|
|
"multi_node_ledger_window",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(zero_ledger_path.clone());
|
|
|
|
// Write some into leader's ledger, this should populate the leader's window
|
|
// and force it to respond to repair from the ledger window
|
|
// TODO: write out more than slot 0
|
|
{
|
|
let blocktree = Blocktree::open_config(&leader_ledger_path, &blocktree_config).unwrap();
|
|
|
|
let entries = solana::entry::create_ticks(
|
|
fullnode_config.leader_scheduler_config.ticks_per_slot - last_entry_height - 1,
|
|
last_entry_id,
|
|
);
|
|
blocktree
|
|
.write_entries(
|
|
DEFAULT_SLOT_HEIGHT,
|
|
tick_height,
|
|
last_entry_height,
|
|
&entries,
|
|
)
|
|
.unwrap();
|
|
|
|
last_entry_height += entries.len() as u64;
|
|
last_entry_id = entries.last().unwrap().id;
|
|
|
|
info!(
|
|
"Final last_entry_height: {}, last_entry_id: {:?}",
|
|
last_entry_height, last_entry_id
|
|
);
|
|
}
|
|
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let leader = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
None,
|
|
&fullnode_config,
|
|
);
|
|
let leader_exit = leader.run(None);
|
|
|
|
// Give validator some tokens for voting
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator_pubkey = keypair.pubkey().clone();
|
|
info!("validator id: {:?}", validator_pubkey);
|
|
let validator_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &validator_pubkey, 500, None).unwrap();
|
|
info!("validator balance {}", validator_balance);
|
|
|
|
// Start up another validator from zero, converge and then check
|
|
// balances
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let validator_data = validator.info.clone();
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let validator = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&zero_ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&FullnodeConfig::default(),
|
|
);
|
|
let validator_exit = validator.run(None);
|
|
|
|
converge(&leader_data, 2);
|
|
|
|
// Another transaction with leader
|
|
let bob_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 1, None).unwrap();
|
|
info!("bob balance on leader {}", bob_balance);
|
|
let mut checks = 1;
|
|
loop {
|
|
let mut leader_client = mk_client(&leader_data);
|
|
let bal = leader_client.poll_get_balance(&bob_pubkey);
|
|
info!(
|
|
"Bob balance on leader is {:?} after {} checks...",
|
|
bal, checks
|
|
);
|
|
|
|
let mut validator_client = mk_client(&validator_data);
|
|
let bal = validator_client.poll_get_balance(&bob_pubkey);
|
|
info!(
|
|
"Bob balance on validator is {:?} after {} checks...",
|
|
bal, checks
|
|
);
|
|
if bal.unwrap_or(0) == bob_balance {
|
|
break;
|
|
}
|
|
checks += 1;
|
|
}
|
|
|
|
info!("Done!");
|
|
validator_exit();
|
|
leader_exit();
|
|
|
|
for path in ledger_paths {
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_multi_node_validator_catchup_from_zero() -> result::Result<()> {
|
|
solana_logger::setup();
|
|
const N: usize = 2;
|
|
trace!("test_multi_node_validator_catchup_from_zero");
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_data = leader.info.clone();
|
|
let bob_pubkey = Keypair::new().pubkey();
|
|
let mut ledger_paths = Vec::new();
|
|
|
|
let fullnode_config = FullnodeConfig::default();
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
let (alice, genesis_ledger_path, _tick_height, _last_entry_height, _last_id, _last_entry_id) =
|
|
create_tmp_sample_ledger(
|
|
"multi_node_validator_catchup_from_zero",
|
|
10_000,
|
|
0,
|
|
leader_data.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(genesis_ledger_path.clone());
|
|
|
|
let zero_ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"multi_node_validator_catchup_from_zero",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(zero_ledger_path.clone());
|
|
|
|
let leader_ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"multi_node_validator_catchup_from_zero",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let server = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
None,
|
|
&fullnode_config,
|
|
);
|
|
|
|
let mut node_exits = vec![server.run(None)];
|
|
for _ in 0..N {
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator_pubkey = keypair.pubkey().clone();
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"multi_node_validator_catchup_from_zero_validator",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(ledger_path.clone());
|
|
|
|
// Send each validator some tokens to vote
|
|
let validator_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &validator_pubkey, 500, None)
|
|
.unwrap();
|
|
info!(
|
|
"validator {} balance {}",
|
|
validator_pubkey, validator_balance
|
|
);
|
|
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let validator = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&FullnodeConfig::default(),
|
|
);
|
|
node_exits.push(validator.run(None));
|
|
}
|
|
let nodes = converge(&leader_data, N + 1); // contains the leader addr as well
|
|
|
|
// Verify leader can transfer from alice to bob
|
|
let leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 123, None).unwrap();
|
|
assert_eq!(leader_balance, 123);
|
|
|
|
// Verify validators all have the same balance for bob
|
|
let mut success = 0usize;
|
|
for server in nodes.iter() {
|
|
let id = server.id;
|
|
info!("0server: {}", id);
|
|
let mut client = mk_client(server);
|
|
|
|
let mut found = false;
|
|
for i in 0..20 {
|
|
let result = client.poll_get_balance(&bob_pubkey);
|
|
if let Ok(bal) = client.poll_get_balance(&bob_pubkey) {
|
|
if bal == leader_balance {
|
|
info!("validator {} bob balance {}", id, bal);
|
|
success += 1;
|
|
found = true;
|
|
break;
|
|
} else {
|
|
info!("validator {} bob balance {} incorrect: {}", id, i, bal);
|
|
}
|
|
} else {
|
|
info!(
|
|
"validator {} bob poll_get_balance {} failed: {:?}",
|
|
id, i, result
|
|
);
|
|
}
|
|
sleep(Duration::new(1, 0));
|
|
}
|
|
assert!(found);
|
|
}
|
|
assert_eq!(success, nodes.len());
|
|
|
|
success = 0;
|
|
|
|
// Start up another validator from zero, converge and then check everyone's
|
|
// balances
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator_pubkey = keypair.pubkey().clone();
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
info!("created start from zero validator {:?}", validator_pubkey);
|
|
|
|
let validator = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&zero_ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&FullnodeConfig::default(),
|
|
);
|
|
|
|
node_exits.push(validator.run(None));
|
|
let nodes = converge(&leader_data, N + 2); // contains the leader and new node
|
|
|
|
// Transfer a little more from alice to bob
|
|
let mut leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 333, None).unwrap();
|
|
info!("leader balance {}", leader_balance);
|
|
loop {
|
|
let mut client = mk_client(&leader_data);
|
|
leader_balance = client.poll_get_balance(&bob_pubkey)?;
|
|
if leader_balance == 456 {
|
|
break;
|
|
}
|
|
sleep(Duration::from_millis(500));
|
|
}
|
|
assert_eq!(leader_balance, 456);
|
|
|
|
for server in nodes.iter() {
|
|
let id = server.id;
|
|
info!("1server: {}", id);
|
|
let mut client = mk_client(server);
|
|
let mut found = false;
|
|
for i in 0..30 {
|
|
let result = client.poll_get_balance(&bob_pubkey);
|
|
if let Ok(bal) = result {
|
|
if bal == leader_balance {
|
|
info!("validator {} bob2 balance {}", id, bal);
|
|
success += 1;
|
|
found = true;
|
|
break;
|
|
} else {
|
|
info!("validator {} bob2 balance {} incorrect: {}", id, i, bal);
|
|
}
|
|
} else {
|
|
info!(
|
|
"validator {} bob2 poll_get_balance {} failed: {:?}",
|
|
id, i, result
|
|
);
|
|
}
|
|
sleep(Duration::new(2, 0));
|
|
}
|
|
assert!(found);
|
|
}
|
|
assert_eq!(success, nodes.len());
|
|
|
|
trace!("done!");
|
|
|
|
for node_exit in node_exits {
|
|
node_exit();
|
|
}
|
|
|
|
for path in ledger_paths {
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_multi_node_basic() {
|
|
solana_logger::setup();
|
|
const N: usize = 5;
|
|
trace!("test_multi_node_basic");
|
|
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_data = leader.info.clone();
|
|
let bob_pubkey = Keypair::new().pubkey();
|
|
let mut ledger_paths = Vec::new();
|
|
|
|
let fullnode_config = FullnodeConfig::default();
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
let (alice, genesis_ledger_path, _tick_height, _last_entry_height, _last_id, _last_entry_id) =
|
|
create_tmp_sample_ledger(
|
|
"multi_node_basic",
|
|
10_000,
|
|
0,
|
|
leader_data.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(genesis_ledger_path.clone());
|
|
|
|
let leader_ledger_path =
|
|
tmp_copy_ledger(&genesis_ledger_path, "multi_node_basic", &blocktree_config);
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let server = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
None,
|
|
&fullnode_config,
|
|
);
|
|
|
|
let mut exit_signals = vec![server.run(None)];
|
|
for i in 0..N {
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator_pubkey = keypair.pubkey().clone();
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let ledger_path =
|
|
tmp_copy_ledger(&genesis_ledger_path, "multi_node_basic", &blocktree_config);
|
|
ledger_paths.push(ledger_path.clone());
|
|
|
|
// Send each validator some tokens to vote
|
|
let validator_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &validator_pubkey, 500, None)
|
|
.unwrap();
|
|
info!(
|
|
"validator #{} - {}, balance {}",
|
|
i, validator_pubkey, validator_balance
|
|
);
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let val = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&fullnode_config,
|
|
);
|
|
exit_signals.push(val.run(None));
|
|
}
|
|
let nodes = converge(&leader_data, N + 1);
|
|
|
|
// Verify leader can do transfer from alice to bob
|
|
let leader_bob_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 123, None).unwrap();
|
|
assert_eq!(leader_bob_balance, 123);
|
|
|
|
// Verify validators all have the same balance for bob
|
|
let mut success = 0usize;
|
|
for server in nodes.iter() {
|
|
let id = server.id;
|
|
info!("mk_client for {}", id);
|
|
let mut client = mk_client(server);
|
|
let mut found = false;
|
|
for _ in 1..20 {
|
|
let result = client.poll_get_balance(&bob_pubkey);
|
|
if let Ok(validator_bob_balance) = result {
|
|
trace!("validator {} bob balance {}", id, validator_bob_balance);
|
|
if validator_bob_balance == leader_bob_balance {
|
|
success += 1;
|
|
found = true;
|
|
break;
|
|
} else {
|
|
warn!(
|
|
"validator {} bob balance incorrect, expecting {}",
|
|
id, leader_bob_balance
|
|
);
|
|
}
|
|
} else {
|
|
warn!("validator {} bob poll_get_balance failed: {:?}", id, result);
|
|
}
|
|
sleep(Duration::new(1, 0));
|
|
}
|
|
assert!(found);
|
|
}
|
|
assert_eq!(success, nodes.len());
|
|
trace!("done!");
|
|
|
|
for exit_signal in exit_signals {
|
|
exit_signal()
|
|
}
|
|
|
|
for path in ledger_paths {
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_boot_validator_from_file() -> result::Result<()> {
|
|
solana_logger::setup();
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader_pubkey = leader_keypair.pubkey();
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let bob_pubkey = Keypair::new().pubkey();
|
|
let mut ledger_paths = Vec::new();
|
|
|
|
let fullnode_config = FullnodeConfig::default();
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
let (alice, genesis_ledger_path, _tick_height, _last_entry_height, _last_id, _last_entry_id) =
|
|
create_tmp_sample_ledger(
|
|
"boot_validator_from_file",
|
|
100_000,
|
|
0,
|
|
leader_pubkey,
|
|
1000,
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(genesis_ledger_path.clone());
|
|
|
|
let leader_ledger_path =
|
|
tmp_copy_ledger(&genesis_ledger_path, "multi_node_basic", &blocktree_config);
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
|
|
let leader_data = leader.info.clone();
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let leader_fullnode = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
None,
|
|
&fullnode_config,
|
|
);
|
|
let leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 500, Some(500)).unwrap();
|
|
assert_eq!(leader_balance, 500);
|
|
let leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 500, Some(1000)).unwrap();
|
|
assert_eq!(leader_balance, 1000);
|
|
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let validator_data = validator.info.clone();
|
|
let ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"boot_validator_from_file",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(ledger_path.clone());
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let val_fullnode = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&fullnode_config,
|
|
);
|
|
let mut client = mk_client(&validator_data);
|
|
let getbal = retry_get_balance(&mut client, &bob_pubkey, Some(leader_balance));
|
|
assert!(getbal == Some(leader_balance));
|
|
|
|
val_fullnode.close()?;
|
|
leader_fullnode.close()?;
|
|
|
|
for path in ledger_paths {
|
|
remove_dir_all(path)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn create_leader(
|
|
ledger_path: &str,
|
|
leader_keypair: Arc<Keypair>,
|
|
voting_keypair: VotingKeypair,
|
|
) -> (NodeInfo, Fullnode) {
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_data = leader.info.clone();
|
|
let leader_fullnode = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&ledger_path,
|
|
voting_keypair,
|
|
None,
|
|
&FullnodeConfig::default(),
|
|
);
|
|
(leader_data, leader_fullnode)
|
|
}
|
|
|
|
#[test]
|
|
fn test_leader_restart_validator_start_from_old_ledger() -> result::Result<()> {
|
|
// this test verifies that a freshly started leader makes its ledger available
|
|
// in the repair window to validators that are started with an older
|
|
// ledger (currently up to WINDOW_SIZE entries)
|
|
solana_logger::setup();
|
|
|
|
let blocktree_config = BlocktreeConfig::default();
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let initial_leader_balance = 500;
|
|
let fullnode_config = FullnodeConfig::default();
|
|
let (alice, ledger_path, _tick_height, _last_entry_height, _last_id, _last_entry_id) =
|
|
create_tmp_sample_ledger(
|
|
"leader_restart_validator_start_from_old_ledger",
|
|
100_000 + 500 * solana::window_service::MAX_REPAIR_BACKOFF as u64,
|
|
0,
|
|
leader_keypair.pubkey(),
|
|
initial_leader_balance,
|
|
&blocktree_config,
|
|
);
|
|
let bob_pubkey = Keypair::new().pubkey();
|
|
|
|
{
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let (leader_data, leader_fullnode) =
|
|
create_leader(&ledger_path, leader_keypair.clone(), voting_keypair);
|
|
|
|
// lengthen the ledger
|
|
let leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 500, Some(500))
|
|
.unwrap();
|
|
assert_eq!(leader_balance, 500);
|
|
|
|
// restart the leader
|
|
leader_fullnode.close()?;
|
|
}
|
|
|
|
// create a "stale" ledger by copying current ledger
|
|
let stale_ledger_path = tmp_copy_ledger(
|
|
&ledger_path,
|
|
"leader_restart_validator_start_from_old_ledger",
|
|
&blocktree_config,
|
|
);
|
|
|
|
{
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let (leader_data, leader_fullnode) =
|
|
create_leader(&ledger_path, leader_keypair.clone(), voting_keypair);
|
|
|
|
// lengthen the ledger
|
|
let leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 500, Some(1000))
|
|
.unwrap();
|
|
assert_eq!(leader_balance, 1000);
|
|
|
|
// restart the leader
|
|
leader_fullnode.close()?;
|
|
}
|
|
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let (leader_data, leader_fullnode) =
|
|
create_leader(&ledger_path, leader_keypair, voting_keypair);
|
|
|
|
// start validator from old ledger
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let validator_data = validator.info.clone();
|
|
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let val_fullnode = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&stale_ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&fullnode_config,
|
|
);
|
|
|
|
// trigger broadcast, validator should catch up from leader, whose window contains
|
|
// the entries missing from the stale ledger
|
|
// send requests so the validator eventually sees a gap and requests a repair
|
|
let mut expected = 1500;
|
|
let mut client = mk_client(&validator_data);
|
|
for _ in 0..solana::window_service::MAX_REPAIR_BACKOFF {
|
|
let leader_balance =
|
|
send_tx_and_retry_get_balance(&leader_data, &alice, &bob_pubkey, 500, Some(expected))
|
|
.unwrap();
|
|
assert_eq!(leader_balance, expected);
|
|
|
|
let getbal = retry_get_balance(&mut client, &bob_pubkey, Some(leader_balance));
|
|
if getbal == Some(leader_balance) {
|
|
break;
|
|
}
|
|
|
|
expected += 500;
|
|
}
|
|
let getbal = retry_get_balance(&mut client, &bob_pubkey, Some(expected));
|
|
assert_eq!(getbal, Some(expected));
|
|
|
|
val_fullnode.close()?;
|
|
leader_fullnode.close()?;
|
|
remove_dir_all(ledger_path)?;
|
|
remove_dir_all(stale_ledger_path)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // TODO: This test is unstable. Fix and re-enable
|
|
fn test_multi_node_dynamic_network() {
|
|
solana_logger::setup();
|
|
let key = "SOLANA_DYNAMIC_NODES";
|
|
let num_nodes: usize = match env::var(key) {
|
|
Ok(val) => val
|
|
.parse()
|
|
.expect(&format!("env var {} is not parse-able as usize", key)),
|
|
Err(_) => 5, // Small number of nodes by default, adjust with SOLANA_DYNAMIC_NODES
|
|
};
|
|
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader_pubkey = leader_keypair.pubkey().clone();
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let bob_pubkey = Keypair::new().pubkey();
|
|
let fullnode_config = FullnodeConfig::default();
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
let (alice, genesis_ledger_path, _tick_height, _last_entry_height, _last_id, _last_entry_id) =
|
|
create_tmp_sample_ledger(
|
|
"multi_node_dynamic_network",
|
|
10_000_000,
|
|
0,
|
|
leader_pubkey,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
|
|
let mut ledger_paths = Vec::new();
|
|
ledger_paths.push(genesis_ledger_path.clone());
|
|
|
|
let leader_ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"multi_node_dynamic_network",
|
|
&blocktree_config,
|
|
);
|
|
|
|
let alice_arc = Arc::new(RwLock::new(alice));
|
|
let leader_data = leader.info.clone();
|
|
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let server = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
None,
|
|
&fullnode_config,
|
|
);
|
|
let server_exit = server.run(None);
|
|
info!(
|
|
"found leader: {:?}",
|
|
poll_gossip_for_leader(leader_data.gossip, Some(5)).unwrap()
|
|
);
|
|
|
|
let bob_balance = retry_send_tx_and_retry_get_balance(
|
|
&leader_data,
|
|
&alice_arc.read().unwrap(),
|
|
&bob_pubkey,
|
|
Some(500),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(bob_balance, 500);
|
|
let bob_balance = retry_send_tx_and_retry_get_balance(
|
|
&leader_data,
|
|
&alice_arc.read().unwrap(),
|
|
&bob_pubkey,
|
|
Some(1000),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(bob_balance, 1000);
|
|
|
|
let t1: Vec<_> = (0..num_nodes)
|
|
.into_iter()
|
|
.map(|n| {
|
|
Builder::new()
|
|
.name("keypair-thread".to_string())
|
|
.spawn(move || {
|
|
info!("Spawned thread {}", n);
|
|
Keypair::new()
|
|
})
|
|
.unwrap()
|
|
})
|
|
.collect();
|
|
|
|
info!("Waiting for keypairs to be created");
|
|
let keypairs: Vec<_> = t1.into_iter().map(|t| t.join().unwrap()).collect();
|
|
info!("keypairs created");
|
|
keypairs.iter().enumerate().for_each(|(n, keypair)| {
|
|
// Send some tokens to the new validators
|
|
let bal = retry_send_tx_and_retry_get_balance(
|
|
&leader_data,
|
|
&alice_arc.read().unwrap(),
|
|
&keypair.pubkey(),
|
|
Some(500),
|
|
);
|
|
assert_eq!(bal, Some(500));
|
|
info!("sent balance to [{}/{}] {}", n, num_nodes, keypair.pubkey());
|
|
});
|
|
let t2: Vec<_> = keypairs
|
|
.into_iter()
|
|
.map(|keypair| {
|
|
let leader_data = leader_data.clone();
|
|
let ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"multi_node_dynamic_network",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(ledger_path.clone());
|
|
Builder::new()
|
|
.name("validator-launch-thread".to_string())
|
|
.spawn(move || {
|
|
let validator = Node::new_localhost_with_pubkey(keypair.pubkey());
|
|
let validator_info = validator.info.clone();
|
|
info!("starting {}", keypair.pubkey());
|
|
let keypair = Arc::new(keypair);
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let validator = Fullnode::new(
|
|
validator,
|
|
&keypair,
|
|
&ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_data),
|
|
&FullnodeConfig::default(),
|
|
);
|
|
let validator_exit = validator.run(None);
|
|
(validator_info, validator_exit)
|
|
})
|
|
.unwrap()
|
|
})
|
|
.collect();
|
|
|
|
let mut validators: Vec<_> = t2.into_iter().map(|t| t.join().unwrap()).collect();
|
|
|
|
let mut client = mk_client(&leader_data);
|
|
let start = Instant::now();
|
|
let mut consecutive_success = 0;
|
|
let mut expected_balance = bob_balance;
|
|
let mut last_id = client.get_last_id();
|
|
for i in 0..std::cmp::max(20, num_nodes) {
|
|
trace!("Getting last_id (iteration {})...", i);
|
|
let mut retries = 30;
|
|
loop {
|
|
let new_last_id = client.get_last_id();
|
|
if new_last_id != last_id {
|
|
last_id = new_last_id;
|
|
break;
|
|
}
|
|
debug!("waiting for new last_id, retries={}", retries);
|
|
retries -= 1;
|
|
if retries == 0 {
|
|
panic!("last_id stuck at {}", last_id);
|
|
}
|
|
sleep(Duration::from_millis(100));
|
|
}
|
|
debug!("last_id: {}", last_id);
|
|
trace!("Executing leader transfer of 100");
|
|
|
|
let mut transaction =
|
|
SystemTransaction::new_move(&alice_arc.read().unwrap(), bob_pubkey, 100, last_id, 0);
|
|
let sig = client
|
|
.retry_transfer(&alice_arc.read().unwrap(), &mut transaction, 5)
|
|
.unwrap();
|
|
trace!("transfer sig: {:?}", sig);
|
|
|
|
expected_balance += 100;
|
|
let mut retries = 30;
|
|
loop {
|
|
let balance = retry_get_balance(&mut client, &bob_pubkey, Some(expected_balance));
|
|
if let Some(balance) = balance {
|
|
if balance == expected_balance {
|
|
break;
|
|
}
|
|
}
|
|
retries -= 1;
|
|
debug!(
|
|
"balance not yet correct: {:?} != {:?}, retries={}",
|
|
balance,
|
|
Some(expected_balance),
|
|
retries
|
|
);
|
|
if retries == 0 {
|
|
assert_eq!(balance, Some(expected_balance));
|
|
}
|
|
sleep(Duration::from_millis(100));
|
|
}
|
|
consecutive_success += 1;
|
|
|
|
info!("SUCCESS[{}] balance: {}", i, expected_balance,);
|
|
|
|
if consecutive_success == 10 {
|
|
info!("Took {} s to converge", duration_as_s(&start.elapsed()),);
|
|
info!("Verifying signature of the last transaction in the validators");
|
|
|
|
let mut num_nodes_behind = 0u64;
|
|
validators.retain(|server| {
|
|
let mut client = mk_client(&server.0);
|
|
trace!("{} checking signature", server.0.id);
|
|
num_nodes_behind += if client.check_signature(&sig) { 0 } else { 1 };
|
|
true
|
|
});
|
|
|
|
info!(
|
|
"Validators lagging: {}/{}",
|
|
num_nodes_behind,
|
|
validators.len(),
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
info!("done!");
|
|
assert_eq!(consecutive_success, 10);
|
|
for (_, validator_exit) in validators {
|
|
validator_exit();
|
|
}
|
|
server_exit();
|
|
|
|
for path in ledger_paths {
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_leader_to_validator_transition() {
|
|
solana_logger::setup();
|
|
|
|
// Make a dummy validator id to be the next leader
|
|
let validator_keypair = Arc::new(Keypair::new());
|
|
|
|
// Create the leader node information
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader_node = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_info = leader_node.info.clone();
|
|
|
|
let mut fullnode_config = FullnodeConfig::default();
|
|
let ticks_per_slot = 5;
|
|
fullnode_config.leader_scheduler_config = LeaderSchedulerConfig::new(
|
|
ticks_per_slot,
|
|
1,
|
|
// Setup window length to exclude the genesis bootstrap leader vote at tick height 0, so
|
|
// that when the leader schedule is recomputed for epoch 1 only the validator vote at tick
|
|
// height 1 will be considered.
|
|
ticks_per_slot,
|
|
);
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
|
|
// Initialize the leader ledger. Make a mint and a genesis entry
|
|
// in the leader ledger
|
|
let (
|
|
mint_keypair,
|
|
leader_ledger_path,
|
|
tick_height,
|
|
genesis_entry_height,
|
|
last_id,
|
|
last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"test_leader_to_validator_transition",
|
|
10_000,
|
|
0,
|
|
leader_info.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
|
|
// Write the votes entries to the ledger that will cause leader rotation
|
|
// to validator_keypair at slot 2
|
|
let (active_set_entries, _) = make_active_set_entries(
|
|
&validator_keypair,
|
|
&mint_keypair,
|
|
100,
|
|
ticks_per_slot,
|
|
&last_entry_id,
|
|
&last_id,
|
|
0,
|
|
);
|
|
{
|
|
let blocktree = Blocktree::open_config(&leader_ledger_path, &blocktree_config).unwrap();
|
|
blocktree
|
|
.write_entries(
|
|
DEFAULT_SLOT_HEIGHT,
|
|
tick_height,
|
|
genesis_entry_height,
|
|
&active_set_entries,
|
|
)
|
|
.unwrap();
|
|
}
|
|
info!("leader id: {}", leader_keypair.pubkey());
|
|
info!("validator id: {}", validator_keypair.pubkey());
|
|
|
|
// Start the leader node
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let leader = Fullnode::new(
|
|
leader_node,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_info),
|
|
&fullnode_config,
|
|
);
|
|
let (rotation_sender, rotation_receiver) = channel();
|
|
let leader_exit = leader.run(Some(rotation_sender));
|
|
|
|
let expected_rotations = vec![(
|
|
FullnodeReturnType::LeaderToValidatorRotation,
|
|
ticks_per_slot,
|
|
)];
|
|
|
|
for expected_rotation in expected_rotations {
|
|
loop {
|
|
let transition = rotation_receiver.recv().unwrap();
|
|
info!("leader transition: {:?}", transition);
|
|
assert_eq!(transition, expected_rotation);
|
|
break;
|
|
}
|
|
}
|
|
|
|
info!("Shut down...");
|
|
leader_exit();
|
|
|
|
info!("Check the ledger to make sure it's the right height...");
|
|
let bank = new_bank_from_ledger(
|
|
&leader_ledger_path,
|
|
&BlocktreeConfig::default(),
|
|
&Arc::new(RwLock::new(LeaderScheduler::default())),
|
|
)
|
|
.0;
|
|
|
|
assert_eq!(
|
|
bank.tick_height(),
|
|
fullnode_config.leader_scheduler_config.ticks_per_slot - 1
|
|
);
|
|
remove_dir_all(leader_ledger_path).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_leader_validator_basic() {
|
|
solana_logger::setup();
|
|
|
|
// Create the leader node information
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader_node = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_info = leader_node.info.clone();
|
|
|
|
// Create the validator node information
|
|
let validator_keypair = Arc::new(Keypair::new());
|
|
let validator_node = Node::new_localhost_with_pubkey(validator_keypair.pubkey());
|
|
|
|
info!("leader id: {}", leader_keypair.pubkey());
|
|
info!("validator id: {}", validator_keypair.pubkey());
|
|
|
|
// Create the leader scheduler config
|
|
let mut fullnode_config = FullnodeConfig::default();
|
|
let ticks_per_slot = 5;
|
|
fullnode_config.leader_scheduler_config = LeaderSchedulerConfig::new(
|
|
ticks_per_slot,
|
|
1, // 1 slot per epoch
|
|
ticks_per_slot,
|
|
);
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
|
|
// Make a common mint and a genesis entry for both leader + validator ledgers
|
|
let (
|
|
mint_keypair,
|
|
leader_ledger_path,
|
|
tick_height,
|
|
genesis_entry_height,
|
|
last_id,
|
|
last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"test_leader_validator_basic",
|
|
10_000,
|
|
0,
|
|
leader_info.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
|
|
// Add validator vote on tick height 1
|
|
let (active_set_entries, _) = make_active_set_entries(
|
|
&validator_keypair,
|
|
&mint_keypair,
|
|
100,
|
|
1,
|
|
&last_entry_id,
|
|
&last_id,
|
|
0,
|
|
);
|
|
{
|
|
let blocktree = Blocktree::open_config(&leader_ledger_path, &blocktree_config).unwrap();
|
|
blocktree
|
|
.write_entries(
|
|
DEFAULT_SLOT_HEIGHT,
|
|
tick_height,
|
|
genesis_entry_height,
|
|
&active_set_entries,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// Initialize both leader + validator ledger
|
|
let mut ledger_paths = Vec::new();
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
let validator_ledger_path = tmp_copy_ledger(
|
|
&leader_ledger_path,
|
|
"test_leader_validator_basic",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(validator_ledger_path.clone());
|
|
|
|
// Start the validator node
|
|
let voting_keypair = VotingKeypair::new_local(&validator_keypair);
|
|
let validator = Fullnode::new(
|
|
validator_node,
|
|
&validator_keypair,
|
|
&validator_ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_info),
|
|
&fullnode_config,
|
|
);
|
|
let (validator_rotation_sender, validator_rotation_receiver) = channel();
|
|
let validator_exit = validator.run(Some(validator_rotation_sender));
|
|
|
|
// Start the leader fullnode
|
|
let voting_keypair = VotingKeypair::new_local(&leader_keypair);
|
|
let leader = Fullnode::new(
|
|
leader_node,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
voting_keypair,
|
|
Some(&leader_info),
|
|
&fullnode_config,
|
|
);
|
|
let (leader_rotation_sender, leader_rotation_receiver) = channel();
|
|
let leader_exit = leader.run(Some(leader_rotation_sender));
|
|
|
|
converge(&leader_info, 2);
|
|
|
|
info!("Waiting for slot 0 -> slot 1: bootstrap leader and the validator rotate");
|
|
assert_eq!(
|
|
leader_rotation_receiver.recv().unwrap(),
|
|
(
|
|
FullnodeReturnType::LeaderToValidatorRotation,
|
|
ticks_per_slot,
|
|
)
|
|
);
|
|
assert_eq!(
|
|
validator_rotation_receiver.recv().unwrap(),
|
|
(
|
|
FullnodeReturnType::ValidatorToLeaderRotation,
|
|
ticks_per_slot,
|
|
)
|
|
);
|
|
|
|
info!("Waiting for slot 1 -> slot 2: validator remains the slot leader due to no votes");
|
|
assert_eq!(
|
|
validator_rotation_receiver.recv().unwrap(),
|
|
(
|
|
FullnodeReturnType::LeaderToLeaderRotation,
|
|
ticks_per_slot * 2,
|
|
)
|
|
);
|
|
|
|
info!("Waiting for slot 2 -> slot 3: validator remains the slot leader due to no votes");
|
|
assert_eq!(
|
|
validator_rotation_receiver.recv().unwrap(),
|
|
(
|
|
FullnodeReturnType::LeaderToLeaderRotation,
|
|
ticks_per_slot * 3,
|
|
)
|
|
);
|
|
|
|
info!("Shut down");
|
|
validator_exit();
|
|
leader_exit();
|
|
|
|
// Check the ledger of the validator to make sure the entry height is correct
|
|
// and that the old leader and the new leader's ledgers agree up to the point
|
|
// of leader rotation
|
|
let validator_entries: Vec<Entry> = read_ledger(&validator_ledger_path, &blocktree_config);
|
|
|
|
let leader_entries = read_ledger(&leader_ledger_path, &blocktree_config);
|
|
assert!(leader_entries.len() as u64 >= ticks_per_slot);
|
|
|
|
for (v, l) in validator_entries.iter().zip(leader_entries) {
|
|
assert_eq!(*v, l);
|
|
}
|
|
|
|
info!("done!");
|
|
for path in ledger_paths {
|
|
Blocktree::destroy(&path).expect("Expected successful database destruction");
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_dropped_handoff_recovery() {
|
|
solana_logger::setup();
|
|
// The number of validators
|
|
const N: usize = 3;
|
|
assert!(N > 1);
|
|
solana_logger::setup();
|
|
|
|
// Create the bootstrap leader node information
|
|
let bootstrap_leader_keypair = Arc::new(Keypair::new());
|
|
let bootstrap_leader_node = Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey());
|
|
let bootstrap_leader_info = bootstrap_leader_node.info.clone();
|
|
|
|
// Create the common leader scheduling configuration
|
|
let slots_per_epoch = (N + 1) as u64;
|
|
let ticks_per_slot = 5;
|
|
let ticks_per_epoch = slots_per_epoch * ticks_per_slot;
|
|
let mut fullnode_config = FullnodeConfig::default();
|
|
fullnode_config.leader_scheduler_config =
|
|
LeaderSchedulerConfig::new(ticks_per_slot, slots_per_epoch, ticks_per_epoch);
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
|
|
// Make a common mint and a genesis entry for both leader + validator's ledgers
|
|
let num_ending_ticks = 1;
|
|
let (
|
|
mint_keypair,
|
|
genesis_ledger_path,
|
|
tick_height,
|
|
genesis_entry_height,
|
|
last_id,
|
|
last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"test_dropped_handoff_recovery",
|
|
10_000,
|
|
num_ending_ticks,
|
|
bootstrap_leader_info.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
|
|
// Create the validator keypair that will be the next leader in line
|
|
let next_leader_keypair = Arc::new(Keypair::new());
|
|
|
|
// Create a common ledger with entries in the beginning that will add only
|
|
// the "next_leader" validator to the active set for leader election, guaranteeing
|
|
// they are the next leader after bootstrap_height
|
|
let mut ledger_paths = Vec::new();
|
|
ledger_paths.push(genesis_ledger_path.clone());
|
|
|
|
// Make the entries to give the next_leader validator some stake so that they will be in
|
|
// leader election active set
|
|
let (active_set_entries, _) = make_active_set_entries(
|
|
&next_leader_keypair,
|
|
&mint_keypair,
|
|
100,
|
|
ticks_per_slot,
|
|
&last_entry_id,
|
|
&last_id,
|
|
0,
|
|
);
|
|
|
|
// Write the entries
|
|
{
|
|
let blocktree = Blocktree::open_config(&genesis_ledger_path, &blocktree_config).unwrap();
|
|
blocktree
|
|
.write_entries(
|
|
DEFAULT_SLOT_HEIGHT,
|
|
tick_height,
|
|
genesis_entry_height,
|
|
&active_set_entries,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
let next_leader_ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"test_dropped_handoff_recovery",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(next_leader_ledger_path.clone());
|
|
|
|
info!("bootstrap_leader: {}", bootstrap_leader_keypair.pubkey());
|
|
info!("'next leader': {}", next_leader_keypair.pubkey());
|
|
|
|
let voting_keypair = VotingKeypair::new_local(&bootstrap_leader_keypair);
|
|
// Start up the bootstrap leader fullnode
|
|
let bootstrap_leader_ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"test_dropped_handoff_recovery",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(bootstrap_leader_ledger_path.clone());
|
|
|
|
let bootstrap_leader = Fullnode::new(
|
|
bootstrap_leader_node,
|
|
&bootstrap_leader_keypair,
|
|
&bootstrap_leader_ledger_path,
|
|
voting_keypair,
|
|
Some(&bootstrap_leader_info),
|
|
&fullnode_config,
|
|
);
|
|
|
|
let (rotation_sender, rotation_receiver) = channel();
|
|
let mut node_exits = vec![bootstrap_leader.run(Some(rotation_sender))];
|
|
|
|
// Start up the validators other than the "next_leader" validator
|
|
for i in 0..(N - 1) {
|
|
let keypair = Arc::new(Keypair::new());
|
|
let validator_ledger_path = tmp_copy_ledger(
|
|
&genesis_ledger_path,
|
|
"test_dropped_handoff_recovery",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(validator_ledger_path.clone());
|
|
let validator_id = keypair.pubkey();
|
|
info!("validator {}: {}", i, validator_id);
|
|
let validator_node = Node::new_localhost_with_pubkey(validator_id);
|
|
let voting_keypair = VotingKeypair::new_local(&keypair);
|
|
let validator = Fullnode::new(
|
|
validator_node,
|
|
&keypair,
|
|
&validator_ledger_path,
|
|
voting_keypair,
|
|
Some(&bootstrap_leader_info),
|
|
&fullnode_config,
|
|
);
|
|
|
|
node_exits.push(validator.run(None));
|
|
}
|
|
|
|
converge(&bootstrap_leader_info, N);
|
|
|
|
info!("Wait for bootstrap_leader to transition to a validator",);
|
|
loop {
|
|
let transition = rotation_receiver.recv().unwrap();
|
|
info!("bootstrap leader transition event: {:?}", transition);
|
|
if transition.0 == FullnodeReturnType::LeaderToValidatorRotation {
|
|
break;
|
|
}
|
|
}
|
|
|
|
info!("Starting the 'next leader' node *after* rotation has occurred");
|
|
let next_leader_node = Node::new_localhost_with_pubkey(next_leader_keypair.pubkey());
|
|
let voting_keypair = VotingKeypair::new_local(&next_leader_keypair);
|
|
let next_leader = Fullnode::new(
|
|
next_leader_node,
|
|
&next_leader_keypair,
|
|
&next_leader_ledger_path,
|
|
voting_keypair,
|
|
Some(&bootstrap_leader_info),
|
|
&FullnodeConfig::default(),
|
|
);
|
|
let (rotation_sender, _rotation_receiver) = channel();
|
|
node_exits.push(next_leader.run(Some(rotation_sender)));
|
|
|
|
info!("Wait for 'next leader' to assume leader role");
|
|
error!("TODO: FIX https://github.com/solana-labs/solana/issues/2482");
|
|
// TODO: Once fixed restore the commented out code below
|
|
/*
|
|
loop {
|
|
let transition = _rotation_receiver.recv().unwrap();
|
|
info!("next leader transition event: {:?}", transition);
|
|
if transition == FullnodeReturnType::ValidatorToLeaderRotation {
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
|
|
info!("done!");
|
|
for exit in node_exits {
|
|
exit();
|
|
}
|
|
|
|
for path in ledger_paths {
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_full_leader_validator_network() {
|
|
solana_logger::setup();
|
|
// The number of validators
|
|
const N: usize = 2;
|
|
|
|
// Create the common leader scheduling configuration
|
|
let slots_per_epoch = (N + 1) as u64;
|
|
let ticks_per_slot = 5;
|
|
let ticks_per_epoch = slots_per_epoch * ticks_per_slot;
|
|
let mut fullnode_config = FullnodeConfig::default();
|
|
fullnode_config.leader_scheduler_config =
|
|
LeaderSchedulerConfig::new(ticks_per_slot, slots_per_epoch, ticks_per_epoch * 3);
|
|
|
|
// Create the bootstrap leader node information
|
|
let bootstrap_leader_keypair = Arc::new(Keypair::new());
|
|
info!("bootstrap leader: {:?}", bootstrap_leader_keypair.pubkey());
|
|
let bootstrap_leader_node = Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey());
|
|
let bootstrap_leader_info = bootstrap_leader_node.info.clone();
|
|
|
|
let mut node_keypairs = VecDeque::new();
|
|
let blocktree_config = BlocktreeConfig::default();
|
|
|
|
// Create the validator keypairs
|
|
for _ in 0..N {
|
|
let validator_keypair = Arc::new(Keypair::new());
|
|
node_keypairs.push_back(validator_keypair);
|
|
}
|
|
|
|
// Make a common mint and a genesis entry for both leader + validator's ledgers
|
|
let num_ending_ticks = 1;
|
|
let (
|
|
mint_keypair,
|
|
bootstrap_leader_ledger_path,
|
|
tick_height,
|
|
mut entry_height,
|
|
last_id,
|
|
mut last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"test_full_leader_validator_network",
|
|
10_000,
|
|
num_ending_ticks,
|
|
bootstrap_leader_info.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
|
|
// Create a common ledger with entries in the beginnging that will add all the validators
|
|
// to the active set for leader election.
|
|
let mut ledger_paths = Vec::new();
|
|
ledger_paths.push(bootstrap_leader_ledger_path.clone());
|
|
|
|
for node_keypair in node_keypairs.iter() {
|
|
// Make entries to give each validator node some stake so that they will be in the
|
|
// leader election active set
|
|
let (active_set_entries, _) = make_active_set_entries(
|
|
node_keypair,
|
|
&mint_keypair,
|
|
100,
|
|
1,
|
|
&last_entry_id,
|
|
&last_id,
|
|
0,
|
|
);
|
|
|
|
// Write the entries
|
|
last_entry_id = active_set_entries
|
|
.last()
|
|
.expect("expected at least one genesis entry")
|
|
.id;
|
|
{
|
|
let blocktree =
|
|
Blocktree::open_config(&bootstrap_leader_ledger_path, &blocktree_config).unwrap();
|
|
blocktree
|
|
.write_entries(
|
|
DEFAULT_SLOT_HEIGHT,
|
|
tick_height,
|
|
entry_height,
|
|
&active_set_entries,
|
|
)
|
|
.unwrap();
|
|
entry_height += active_set_entries.len() as u64;
|
|
}
|
|
}
|
|
|
|
let mut nodes = vec![];
|
|
|
|
info!("Start up the validators");
|
|
// Start up the validators
|
|
for kp in node_keypairs.into_iter() {
|
|
let validator_ledger_path = tmp_copy_ledger(
|
|
&bootstrap_leader_ledger_path,
|
|
"test_full_leader_validator_network",
|
|
&blocktree_config,
|
|
);
|
|
|
|
ledger_paths.push(validator_ledger_path.clone());
|
|
|
|
let validator_id = kp.pubkey();
|
|
let validator_node = Node::new_localhost_with_pubkey(validator_id);
|
|
let voting_keypair = VotingKeypair::new_local(&kp);
|
|
info!("validator: {:?}", validator_id);
|
|
let validator = Fullnode::new(
|
|
validator_node,
|
|
&kp,
|
|
&validator_ledger_path,
|
|
voting_keypair,
|
|
Some(&bootstrap_leader_info),
|
|
&fullnode_config,
|
|
);
|
|
|
|
let (rotation_sender, rotation_receiver) = channel();
|
|
nodes.push((
|
|
validator_id,
|
|
validator.run(Some(rotation_sender)),
|
|
rotation_receiver,
|
|
));
|
|
}
|
|
|
|
info!("Start up the bootstrap leader");
|
|
let voting_keypair = VotingKeypair::new_local(&bootstrap_leader_keypair);
|
|
let bootstrap_leader = Fullnode::new(
|
|
bootstrap_leader_node,
|
|
&bootstrap_leader_keypair,
|
|
&bootstrap_leader_ledger_path,
|
|
voting_keypair,
|
|
Some(&bootstrap_leader_info),
|
|
&fullnode_config,
|
|
);
|
|
let (bootstrap_leader_rotation_sender, bootstrap_leader_rotation_receiver) = channel();
|
|
let bootstrap_leader_exit = bootstrap_leader.run(Some(bootstrap_leader_rotation_sender));
|
|
|
|
converge(&bootstrap_leader_info, N + 1);
|
|
|
|
// Wait for the bootstrap_leader to transition to a validator
|
|
loop {
|
|
let transition = bootstrap_leader_rotation_receiver.recv().unwrap();
|
|
info!("bootstrap leader transition event: {:?}", transition);
|
|
if transition.0 == FullnodeReturnType::LeaderToValidatorRotation {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ensure each node in the cluster rotates into the leader role
|
|
for (id, _, rotation_receiver) in &nodes {
|
|
info!("Waiting for {:?} to become the leader", id);
|
|
loop {
|
|
let transition = rotation_receiver.recv().unwrap();
|
|
info!("node {:?} transition event: {:?}", id, transition);
|
|
if transition.0 == FullnodeReturnType::ValidatorToLeaderRotation {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
info!("Exit all nodes");
|
|
for node in nodes {
|
|
node.1();
|
|
}
|
|
bootstrap_leader_exit();
|
|
|
|
let mut node_entries = vec![];
|
|
info!("Check that all the ledgers match");
|
|
for ledger_path in ledger_paths.iter() {
|
|
let entries = read_ledger(ledger_path, &blocktree_config);
|
|
node_entries.push(entries.into_iter());
|
|
}
|
|
|
|
let mut shortest = None;
|
|
let mut length = 0;
|
|
loop {
|
|
let mut expected_entry_option = None;
|
|
let mut empty_iterators = HashSet::new();
|
|
for (i, entries_for_specific_node) in node_entries.iter_mut().enumerate() {
|
|
if let Some(next_entry) = entries_for_specific_node.next() {
|
|
// Check if another earlier ledger iterator had another entry. If so, make
|
|
// sure they match
|
|
if let Some(ref expected_entry) = expected_entry_option {
|
|
// TODO: This assert fails sometimes....why?
|
|
//assert_eq!(*expected_entry, next_entry);
|
|
if *expected_entry != next_entry {
|
|
error!("THIS IS A FAILURE. SEE https://github.com/solana-labs/solana/issues/2481");
|
|
error!("* expected_entry: {:?}", *expected_entry);
|
|
error!("* next_entry: {:?}", next_entry);
|
|
}
|
|
} else {
|
|
expected_entry_option = Some(next_entry);
|
|
}
|
|
} else {
|
|
// The shortest iterator is the first one to return a None when
|
|
// calling next()
|
|
if shortest.is_none() {
|
|
shortest = Some(length);
|
|
}
|
|
empty_iterators.insert(i);
|
|
}
|
|
}
|
|
|
|
// Remove the empty iterators
|
|
node_entries = node_entries
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(i, x)| match empty_iterators.get(&i) {
|
|
None => Some(x),
|
|
_ => None,
|
|
})
|
|
.collect();
|
|
|
|
if node_entries.len() == 0 {
|
|
break;
|
|
}
|
|
|
|
length += 1;
|
|
}
|
|
|
|
let shortest = shortest.unwrap();
|
|
assert!(shortest >= fullnode_config.leader_scheduler_config.ticks_per_slot * 3,);
|
|
|
|
for path in ledger_paths {
|
|
Blocktree::destroy(&path).expect("Expected successful database destruction");
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_broadcast_last_tick() {
|
|
solana_logger::setup();
|
|
// The number of validators
|
|
const N: usize = 5;
|
|
solana_logger::setup();
|
|
|
|
// Create the bootstrap leader node information
|
|
let bootstrap_leader_keypair = Keypair::new();
|
|
let bootstrap_leader_node = Node::new_localhost_with_pubkey(bootstrap_leader_keypair.pubkey());
|
|
let bootstrap_leader_info = bootstrap_leader_node.info.clone();
|
|
|
|
// Create the fullnode configuration
|
|
let ticks_per_slot = 40;
|
|
let slots_per_epoch = 2;
|
|
let ticks_per_epoch = slots_per_epoch * ticks_per_slot;
|
|
|
|
let mut fullnode_config = FullnodeConfig::default();
|
|
fullnode_config.leader_scheduler_config =
|
|
LeaderSchedulerConfig::new(ticks_per_slot, slots_per_epoch, ticks_per_epoch);
|
|
|
|
// Create leader ledger
|
|
let blocktree_config = BlocktreeConfig::default();
|
|
let (
|
|
_mint_keypair,
|
|
bootstrap_leader_ledger_path,
|
|
_tick_height,
|
|
genesis_entry_height,
|
|
_last_id,
|
|
_last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"test_broadcast_last_tick",
|
|
10_000,
|
|
0,
|
|
bootstrap_leader_info.id,
|
|
500,
|
|
&blocktree_config,
|
|
);
|
|
|
|
let genesis_ledger_len = genesis_entry_height;
|
|
debug!("genesis_ledger_len: {}", genesis_ledger_len);
|
|
let blob_receiver_exit = Arc::new(AtomicBool::new(false));
|
|
|
|
// Create the listeners
|
|
let mut listening_nodes: Vec<_> = (0..N)
|
|
.map(|_| make_listening_node(&bootstrap_leader_info))
|
|
.collect();
|
|
|
|
let blob_fetch_stages: Vec<_> = listening_nodes
|
|
.iter_mut()
|
|
.map(|(_, _, node, _)| {
|
|
let (blob_fetch_sender, blob_fetch_receiver) = channel();
|
|
(
|
|
BlobFetchStage::new(
|
|
Arc::new(node.sockets.tvu.pop().unwrap()),
|
|
&blob_fetch_sender,
|
|
blob_receiver_exit.clone(),
|
|
),
|
|
blob_fetch_receiver,
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
// Start up the bootstrap leader fullnode
|
|
let bootstrap_leader_keypair = Arc::new(bootstrap_leader_keypair);
|
|
let voting_keypair = VotingKeypair::new_local(&bootstrap_leader_keypair);
|
|
|
|
let bootstrap_leader = Fullnode::new(
|
|
bootstrap_leader_node,
|
|
&bootstrap_leader_keypair,
|
|
&bootstrap_leader_ledger_path,
|
|
voting_keypair,
|
|
Some(&bootstrap_leader_info),
|
|
&fullnode_config,
|
|
);
|
|
|
|
let (bootstrap_leader_rotation_sender, bootstrap_leader_rotation_receiver) = channel();
|
|
let bootstrap_leader_exit = bootstrap_leader.run(Some(bootstrap_leader_rotation_sender));
|
|
|
|
// Wait for convergence
|
|
converge(&bootstrap_leader_info, N + 1);
|
|
|
|
info!("Waiting for leader rotation...");
|
|
|
|
// Wait for the bootstrap_leader to move beyond slot 0
|
|
loop {
|
|
let transition = bootstrap_leader_rotation_receiver.recv().unwrap();
|
|
info!("bootstrap leader transition event: {:?}", transition);
|
|
if transition.0 == FullnodeReturnType::LeaderToLeaderRotation {
|
|
break;
|
|
}
|
|
}
|
|
info!("Shutting down the leader...");
|
|
bootstrap_leader_exit();
|
|
|
|
// Index of the last tick must be at least ticks_per_slot - 1
|
|
let last_tick_entry_index = ticks_per_slot as usize - 1;
|
|
let entries = read_ledger(&bootstrap_leader_ledger_path, &blocktree_config);
|
|
assert!(entries.len() >= last_tick_entry_index + 1);
|
|
let expected_last_tick = &entries[last_tick_entry_index];
|
|
debug!("last_tick_entry_index: {:?}", last_tick_entry_index);
|
|
debug!("expected_last_tick: {:?}", expected_last_tick);
|
|
|
|
info!("Check that the nodes got the last broadcasted blob");
|
|
for (_, receiver) in blob_fetch_stages.iter() {
|
|
info!("Checking a node...");
|
|
let mut blobs = vec![];
|
|
while let Ok(new_blobs) = receiver.try_recv() {
|
|
blobs.extend(new_blobs);
|
|
}
|
|
|
|
for b in blobs {
|
|
let b_r = b.read().unwrap();
|
|
if b_r.index() == last_tick_entry_index as u64 {
|
|
assert!(b_r.is_last_in_slot());
|
|
debug!("last_tick_blob: {:?}", b_r);
|
|
let actual_last_tick = &reconstruct_entries_from_blobs(vec![&*b_r])
|
|
.expect("Expected to be able to reconstruct entries from blob")
|
|
.0[0];
|
|
assert_eq!(actual_last_tick, expected_last_tick);
|
|
break;
|
|
} else {
|
|
assert!(!b_r.is_last_in_slot());
|
|
}
|
|
}
|
|
}
|
|
|
|
info!("done!");
|
|
|
|
// Shut down blob fetch stages
|
|
blob_receiver_exit.store(true, Ordering::Relaxed);
|
|
for (bf, _) in blob_fetch_stages {
|
|
bf.join().unwrap();
|
|
}
|
|
|
|
// Shut down the listeners
|
|
for node in listening_nodes {
|
|
node.0.close().unwrap();
|
|
}
|
|
remove_dir_all(bootstrap_leader_ledger_path).unwrap();
|
|
}
|
|
|
|
fn send_tx_and_retry_get_balance(
|
|
leader: &NodeInfo,
|
|
alice: &Keypair,
|
|
bob_pubkey: &Pubkey,
|
|
transfer_amount: u64,
|
|
expected: Option<u64>,
|
|
) -> Option<u64> {
|
|
let mut client = mk_client(leader);
|
|
trace!("getting leader last_id");
|
|
let last_id = client.get_last_id();
|
|
let mut tx = SystemTransaction::new_account(&alice, *bob_pubkey, transfer_amount, last_id, 0);
|
|
info!(
|
|
"executing transfer of {} from {} to {}",
|
|
transfer_amount,
|
|
alice.pubkey(),
|
|
*bob_pubkey
|
|
);
|
|
if client.retry_transfer(&alice, &mut tx, 5).is_err() {
|
|
None
|
|
} else {
|
|
retry_get_balance(&mut client, bob_pubkey, expected)
|
|
}
|
|
}
|
|
|
|
fn retry_send_tx_and_retry_get_balance(
|
|
leader: &NodeInfo,
|
|
alice: &Keypair,
|
|
bob_pubkey: &Pubkey,
|
|
expected: Option<u64>,
|
|
) -> Option<u64> {
|
|
let mut client = mk_client(leader);
|
|
trace!("getting leader last_id");
|
|
let last_id = client.get_last_id();
|
|
info!("executing leader transfer");
|
|
const LAST: usize = 30;
|
|
for run in 0..(LAST + 1) {
|
|
let _sig = client.transfer(500, &alice, *bob_pubkey, &last_id).unwrap();
|
|
let out = client.poll_get_balance(bob_pubkey);
|
|
if expected.is_none() || run == LAST {
|
|
return out.ok().clone();
|
|
}
|
|
trace!(
|
|
"retry_send_tx_and_retry_get_balance[{}] {:?} {:?}",
|
|
run,
|
|
out,
|
|
expected
|
|
);
|
|
if let (Some(e), Ok(o)) = (expected, out) {
|
|
if o == e {
|
|
return Some(o);
|
|
}
|
|
}
|
|
sleep(Duration::from_millis(20));
|
|
}
|
|
None
|
|
}
|
|
|
|
fn test_fullnode_rotate(
|
|
ticks_per_slot: u64,
|
|
slots_per_epoch: u64,
|
|
include_validator: bool,
|
|
transact: bool,
|
|
) {
|
|
solana_logger::setup();
|
|
info!(
|
|
"fullnode_rotate_fast: ticks_per_slot={} slots_per_epoch={} include_validator={} transact={}",
|
|
ticks_per_slot, slots_per_epoch, include_validator, transact
|
|
);
|
|
|
|
let mut fullnode_config = FullnodeConfig::default();
|
|
fullnode_config.leader_scheduler_config.ticks_per_slot = ticks_per_slot;
|
|
fullnode_config.leader_scheduler_config.slots_per_epoch = slots_per_epoch;
|
|
|
|
// Note: when debugging failures in this test, disabling voting can help keep the log noise
|
|
// down by removing the extra vote transactions
|
|
/*
|
|
fullnode_config.voting_disabled = true;
|
|
*/
|
|
|
|
let blocktree_config = fullnode_config.ledger_config();
|
|
fullnode_config
|
|
.leader_scheduler_config
|
|
.active_window_tick_length = std::u64::MAX;
|
|
|
|
// Create the leader node information
|
|
let leader_keypair = Arc::new(Keypair::new());
|
|
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
|
|
let leader_info = leader.info.clone();
|
|
|
|
// Create the validator node information
|
|
let validator_keypair = Arc::new(Keypair::new());
|
|
let validator = Node::new_localhost_with_pubkey(validator_keypair.pubkey());
|
|
|
|
info!("leader id: {}", leader_keypair.pubkey());
|
|
if include_validator {
|
|
info!("validator id: {}", validator_keypair.pubkey());
|
|
}
|
|
|
|
// Make a common mint and a genesis entry for both leader + validator ledgers
|
|
let (
|
|
mint_keypair,
|
|
leader_ledger_path,
|
|
mut tick_height,
|
|
mut last_entry_height,
|
|
last_id,
|
|
mut last_entry_id,
|
|
) = create_tmp_sample_ledger(
|
|
"test_fullnode_rotate",
|
|
1_000_000_000_000_000_000,
|
|
0,
|
|
leader_keypair.pubkey(),
|
|
123,
|
|
&blocktree_config,
|
|
);
|
|
assert_eq!(tick_height, 1);
|
|
|
|
let mut ledger_paths = Vec::new();
|
|
ledger_paths.push(leader_ledger_path.clone());
|
|
info!("ledger is {}", leader_ledger_path);
|
|
|
|
let mut entries = vec![];
|
|
|
|
// Setup the cluster with a single node
|
|
if include_validator {
|
|
// Add validator vote on tick height 1
|
|
let (active_set_entries, _) = make_active_set_entries(
|
|
&validator_keypair,
|
|
&mint_keypair,
|
|
100,
|
|
1,
|
|
&last_entry_id,
|
|
&last_id,
|
|
0,
|
|
);
|
|
entries.extend(active_set_entries);
|
|
last_entry_id = entries.last().unwrap().id;
|
|
}
|
|
|
|
let mut leader_tick_height_of_next_rotation = ticks_per_slot;
|
|
let mut leader_should_be_leader = true;
|
|
if fullnode_config.leader_scheduler_config.ticks_per_slot == 1 {
|
|
// Add another tick to the ledger if the cluster has been configured for 1 tick_per_slot.
|
|
// The "pseudo-tick" entry0 currently added by bank::process_ledger cannot be rotated on
|
|
// since it has no last id (so at 1 ticks_per_slot rotation must start at a tick_height of
|
|
// 2)
|
|
let tick = solana::entry::create_ticks(1, last_entry_id);
|
|
entries.extend(tick);
|
|
last_entry_id = entries.last().unwrap().id;
|
|
|
|
leader_tick_height_of_next_rotation += 2;
|
|
if include_validator {
|
|
leader_should_be_leader = false;
|
|
}
|
|
}
|
|
|
|
let mut validator_should_be_leader = !leader_should_be_leader;
|
|
let mut validator_tick_height_of_next_rotation = leader_tick_height_of_next_rotation;
|
|
|
|
// Write additional ledger entires
|
|
{
|
|
trace!("last_entry_id: {:?}", last_entry_id);
|
|
trace!("entries: {:?}", entries);
|
|
|
|
let mut start_slot = 0;
|
|
if fullnode_config.leader_scheduler_config.ticks_per_slot == 1 {
|
|
start_slot = 1;
|
|
tick_height = 0;
|
|
last_entry_height = 0;
|
|
}
|
|
let blocktree = Blocktree::open_config(&leader_ledger_path, &blocktree_config).unwrap();
|
|
blocktree
|
|
.write_entries(start_slot, tick_height, last_entry_height, &entries)
|
|
.unwrap();
|
|
}
|
|
|
|
// Start up the node(s)
|
|
let mut node_exits = vec![];
|
|
|
|
let (validator_rotation_sender, validator_rotation_receiver) = channel();
|
|
if include_validator {
|
|
let validator_ledger_path = tmp_copy_ledger(
|
|
&leader_ledger_path,
|
|
"test_fullnode_rotate",
|
|
&blocktree_config,
|
|
);
|
|
ledger_paths.push(validator_ledger_path.clone());
|
|
let validator_fullnode = Fullnode::new(
|
|
validator,
|
|
&validator_keypair,
|
|
&validator_ledger_path,
|
|
VotingKeypair::new_local(&validator_keypair),
|
|
Some(&leader_info),
|
|
&fullnode_config,
|
|
);
|
|
|
|
node_exits.push(validator_fullnode.run(Some(validator_rotation_sender)));
|
|
}
|
|
|
|
let (leader_rotation_sender, leader_rotation_receiver) = channel();
|
|
let leader_fullnode = Fullnode::new(
|
|
leader,
|
|
&leader_keypair,
|
|
&leader_ledger_path,
|
|
VotingKeypair::new_local(&leader_keypair),
|
|
None,
|
|
&fullnode_config,
|
|
);
|
|
|
|
node_exits.push(leader_fullnode.run(Some(leader_rotation_sender)));
|
|
|
|
converge(&leader_info, node_exits.len());
|
|
info!(
|
|
"found leader: {:?}",
|
|
poll_gossip_for_leader(leader_info.gossip, Some(5)).unwrap()
|
|
);
|
|
|
|
let bob = Keypair::new().pubkey();
|
|
let mut expected_bob_balance = 0;
|
|
let mut client = mk_client(&leader_info);
|
|
let mut client_last_id = solana_sdk::hash::Hash::default();
|
|
|
|
let max_tick_height = 8;
|
|
while leader_tick_height_of_next_rotation < max_tick_height
|
|
&& validator_tick_height_of_next_rotation < max_tick_height
|
|
{
|
|
// Check for leader rotation
|
|
{
|
|
match leader_rotation_receiver.try_recv() {
|
|
Ok((rotation_type, tick_height)) => {
|
|
info!(
|
|
"leader rotation event {:?} at tick_height={}",
|
|
rotation_type, tick_height
|
|
);
|
|
info!("leader should be leader? {}", leader_should_be_leader);
|
|
assert_eq!(tick_height, leader_tick_height_of_next_rotation);
|
|
if include_validator {
|
|
assert_eq!(
|
|
rotation_type,
|
|
if leader_should_be_leader {
|
|
FullnodeReturnType::LeaderToValidatorRotation
|
|
} else {
|
|
FullnodeReturnType::ValidatorToLeaderRotation
|
|
}
|
|
);
|
|
leader_should_be_leader = !leader_should_be_leader;
|
|
} else {
|
|
assert_eq!(rotation_type, FullnodeReturnType::LeaderToLeaderRotation);
|
|
}
|
|
leader_tick_height_of_next_rotation += ticks_per_slot;
|
|
}
|
|
Err(TryRecvError::Empty) => {}
|
|
err => panic!(err),
|
|
}
|
|
}
|
|
|
|
// Check for validator rotation
|
|
if include_validator {
|
|
match validator_rotation_receiver.try_recv() {
|
|
Ok((rotation_type, tick_height)) => {
|
|
info!(
|
|
"validator rotation event {:?} at tick_height={}",
|
|
rotation_type, tick_height
|
|
);
|
|
info!("validator should be leader? {}", validator_should_be_leader);
|
|
assert_eq!(tick_height, validator_tick_height_of_next_rotation);
|
|
assert_eq!(
|
|
rotation_type,
|
|
if validator_should_be_leader {
|
|
FullnodeReturnType::LeaderToValidatorRotation
|
|
} else {
|
|
FullnodeReturnType::ValidatorToLeaderRotation
|
|
}
|
|
);
|
|
validator_tick_height_of_next_rotation += ticks_per_slot;
|
|
validator_should_be_leader = !validator_should_be_leader;
|
|
}
|
|
Err(TryRecvError::Empty) => {}
|
|
err => panic!(err),
|
|
}
|
|
}
|
|
|
|
if transact {
|
|
client_last_id = client.get_next_last_id(&client_last_id);
|
|
info!("Transferring 500 tokens, last_id={:?}", client_last_id);
|
|
expected_bob_balance += 500;
|
|
|
|
let signature = client
|
|
.transfer(500, &mint_keypair, bob, &client_last_id)
|
|
.unwrap();
|
|
debug!("transfer send, signature is {:?}", signature);
|
|
client.poll_for_signature(&signature).unwrap();
|
|
debug!("transfer signature confirmed");
|
|
let actual_bob_balance =
|
|
retry_get_balance(&mut client, &bob, Some(expected_bob_balance)).unwrap();
|
|
assert_eq!(actual_bob_balance, expected_bob_balance);
|
|
debug!("account balance confirmed: {}", actual_bob_balance);
|
|
|
|
client_last_id = client.get_next_last_id(&client_last_id);
|
|
} else {
|
|
if include_validator {
|
|
trace!("waiting for leader and validator to reach max tick height...");
|
|
} else {
|
|
trace!("waiting for leader to reach max tick height...");
|
|
}
|
|
}
|
|
}
|
|
|
|
if transact {
|
|
// Make sure at least one transfer succeeded.
|
|
assert!(expected_bob_balance > 0);
|
|
}
|
|
|
|
info!("Shutting down");
|
|
for node_exit in node_exits {
|
|
node_exit();
|
|
}
|
|
|
|
for path in ledger_paths {
|
|
Blocktree::destroy(&path)
|
|
.unwrap_or_else(|err| warn!("Expected successful database destruction: {:?}", err));
|
|
remove_dir_all(path).unwrap();
|
|
}
|
|
|
|
trace!(
|
|
"final validator_tick_height_of_next_rotation: {}",
|
|
validator_tick_height_of_next_rotation
|
|
);
|
|
trace!(
|
|
"final leader_tick_height_of_next_rotation: {}",
|
|
leader_tick_height_of_next_rotation
|
|
);
|
|
trace!("final leader_should_be_leader: {}", leader_should_be_leader);
|
|
trace!(
|
|
"final validator_should_be_leader: {}",
|
|
validator_should_be_leader
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_one_fullnode_rotate_every_tick() {
|
|
test_fullnode_rotate(1, 1, false, false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_one_fullnode_rotate_every_second_tick() {
|
|
test_fullnode_rotate(2, 1, false, false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_fullnodes_rotate_every_tick() {
|
|
test_fullnode_rotate(1, 1, true, false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_fullnodes_rotate_every_second_tick() {
|
|
test_fullnode_rotate(2, 1, true, false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_one_fullnode_rotate_every_tick_with_transactions() {
|
|
test_fullnode_rotate(1, 1, false, true);
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_fullnodes_rotate_every_tick_with_transactions() {
|
|
test_fullnode_rotate(1, 1, true, true);
|
|
}
|