169 lines
5.2 KiB
Rust
169 lines
5.2 KiB
Rust
use bzip2::bufread::BzDecoder;
|
|
use log::*;
|
|
use solana_client::rpc_client::RpcClient;
|
|
use solana_net_utils::parse_host;
|
|
use solana_notifier::Notifier;
|
|
use solana_sdk::{
|
|
clock::{Epoch, Slot},
|
|
genesis_config::GenesisConfig,
|
|
timing::duration_as_ms,
|
|
};
|
|
use std::{
|
|
fs::File,
|
|
io,
|
|
net::SocketAddr,
|
|
path::Path,
|
|
thread::sleep,
|
|
time::{Duration, Instant},
|
|
};
|
|
use tar::Archive;
|
|
|
|
const GENESIS_ARCHIVE_NAME: &str = "genesis.tar.bz2";
|
|
|
|
/// Inspired by solana_local_cluster::cluster_tests
|
|
fn slots_to_secs(num_slots: u64, genesis_config: &GenesisConfig) -> u64 {
|
|
let poh_config = &genesis_config.poh_config;
|
|
let ticks_per_slot = genesis_config.ticks_per_slot;
|
|
let num_ticks_to_sleep = num_slots as f64 * ticks_per_slot as f64;
|
|
let num_ticks_per_second = (1000 / duration_as_ms(&poh_config.target_tick_duration)) as f64;
|
|
((num_ticks_to_sleep + num_ticks_per_second - 1.0) / num_ticks_per_second) as u64
|
|
}
|
|
|
|
fn sleep_n_slots(num_slots: u64, genesis_config: &GenesisConfig) {
|
|
let secs = slots_to_secs(num_slots, genesis_config);
|
|
let mins = secs / 60;
|
|
let hours = mins / 60;
|
|
if hours >= 5 {
|
|
debug!("Sleeping for {} slots ({} hours)", num_slots, hours);
|
|
} else if mins >= 5 {
|
|
debug!("Sleeping for {} slots ({} minutes)", num_slots, mins);
|
|
} else if secs > 0 {
|
|
debug!("Sleeping for {} slots ({} seconds)", num_slots, secs);
|
|
}
|
|
sleep(Duration::from_secs(secs));
|
|
}
|
|
|
|
/// Sleep until the target epoch has started or bail if cluster is stuck
|
|
pub fn sleep_until_epoch(
|
|
rpc_client: &RpcClient,
|
|
notifier: &Notifier,
|
|
genesis_config: &GenesisConfig,
|
|
mut current_slot: Slot,
|
|
target_epoch: Epoch,
|
|
) {
|
|
let target_slot = genesis_config
|
|
.epoch_schedule
|
|
.get_first_slot_in_epoch(target_epoch);
|
|
info!(
|
|
"sleep_until_epoch() target_epoch: {}, target_slot: {}",
|
|
target_epoch, target_slot
|
|
);
|
|
|
|
loop {
|
|
let sleep_slots = target_slot.saturating_sub(current_slot);
|
|
if sleep_slots == 0 {
|
|
break;
|
|
}
|
|
|
|
sleep_n_slots(sleep_slots.max(50), genesis_config);
|
|
let latest_slot = rpc_client.get_slot().unwrap_or_else(|err| {
|
|
bail(
|
|
notifier,
|
|
&format!("Error: Could not fetch current slot: {}", err),
|
|
);
|
|
});
|
|
|
|
if current_slot == latest_slot {
|
|
bail(
|
|
notifier,
|
|
&format!("Error: Slot did not advance from {}", current_slot),
|
|
);
|
|
} else {
|
|
current_slot = latest_slot;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn is_host(string: String) -> Result<(), String> {
|
|
parse_host(&string)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn bail(notifier: &Notifier, msg: &str) -> ! {
|
|
notifier.send(msg);
|
|
sleep(Duration::from_secs(30)); // Wait for notifications to send
|
|
std::process::exit(1);
|
|
}
|
|
|
|
/// Inspired by solana_validator::download_tar_bz2
|
|
pub fn download_genesis(rpc_addr: &SocketAddr, download_path: &Path) -> Result<(), String> {
|
|
let archive_name = GENESIS_ARCHIVE_NAME;
|
|
let archive_path = download_path.join(archive_name);
|
|
let url = format!("http://{}/{}", rpc_addr, archive_name);
|
|
let download_start = Instant::now();
|
|
debug!("Downloading genesis ({})...", url);
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
let mut response = client
|
|
.get(url.as_str())
|
|
.send()
|
|
.and_then(|response| response.error_for_status())
|
|
.map_err(|err| format!("Unable to get: {:?}", err))?;
|
|
let download_size = {
|
|
response
|
|
.headers()
|
|
.get(reqwest::header::CONTENT_LENGTH)
|
|
.and_then(|content_length| content_length.to_str().ok())
|
|
.and_then(|content_length| content_length.parse().ok())
|
|
.unwrap_or(0)
|
|
};
|
|
|
|
let mut file = File::create(&archive_path)
|
|
.map_err(|err| format!("Unable to create {:?}: {:?}", archive_path, err))?;
|
|
io::copy(&mut response, &mut file)
|
|
.map_err(|err| format!("Unable to write {:?}: {:?}", archive_path, err))?;
|
|
|
|
debug!(
|
|
"Downloaded genesis ({} bytes) in {:?}",
|
|
download_size,
|
|
Instant::now().duration_since(download_start),
|
|
);
|
|
|
|
debug!("Extracting genesis ({})...", archive_name);
|
|
let extract_start = Instant::now();
|
|
let tar_bz2 = File::open(&archive_path)
|
|
.map_err(|err| format!("Unable to open {}: {:?}", archive_name, err))?;
|
|
let tar = BzDecoder::new(io::BufReader::new(tar_bz2));
|
|
let mut archive = Archive::new(tar);
|
|
archive
|
|
.unpack(download_path)
|
|
.map_err(|err| format!("Unable to unpack {}: {:?}", archive_name, err))?;
|
|
debug!(
|
|
"Extracted {} in {:?}",
|
|
archive_name,
|
|
Instant::now().duration_since(extract_start)
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_slots_to_secs() {
|
|
let mut genesis_config = GenesisConfig::default();
|
|
genesis_config.poh_config.target_tick_duration = Duration::from_millis(500);
|
|
|
|
genesis_config.ticks_per_slot = 10;
|
|
assert_eq!(slots_to_secs(2, &genesis_config), 10);
|
|
|
|
genesis_config.ticks_per_slot = 1;
|
|
assert_eq!(slots_to_secs(1, &genesis_config), 1);
|
|
|
|
genesis_config.ticks_per_slot = 0;
|
|
assert_eq!(slots_to_secs(10, &genesis_config), 0);
|
|
}
|
|
}
|