diff --git a/Cargo.lock b/Cargo.lock index 6b204465b7..5b0f5cbe63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1068,7 +1068,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "miniz_oxide_c_api 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1813,15 +1812,6 @@ dependencies = [ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "miniz-sys" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "miniz_oxide" version = "0.3.0" @@ -3053,12 +3043,12 @@ dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "core_affinity 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dir-diff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3830,8 +3820,10 @@ dependencies = [ name = "solana-validator" version = "0.18.0-pre1" dependencies = [ + "bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.9.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "solana 0.18.0-pre1", "solana-drone 0.18.0-pre1", @@ -3842,6 +3834,8 @@ dependencies = [ "solana-sdk 0.18.0-pre1", "solana-vote-api 0.18.0-pre1", "solana-vote-signer 0.18.0-pre1", + "tar 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -5430,7 +5424,6 @@ dependencies = [ "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425" "checksum mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30de2e4613efcba1ec63d8133f344076952090c122992a903359be5a4f99c3ed" -"checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" "checksum miniz_oxide 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c061edee74a88eb35d876ce88b94d77a0448a201de111c244b70d047f5820516" "checksum miniz_oxide_c_api 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6c675792957b0d19933816c4e1d56663c341dd9bfa31cb2140ff2267c1d8ecf4" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" diff --git a/ci/testnet-deploy.sh b/ci/testnet-deploy.sh index ad69c3cdf1..7213da8b64 100755 --- a/ci/testnet-deploy.sh +++ b/ci/testnet-deploy.sh @@ -90,7 +90,7 @@ Deploys a CD testnet - Attempt to generate a TLS certificate using this DNS name --fullnode-additional-disk-size-gb [number] - Size of additional disk in GB for all fullnodes - --no-snapshot + --no-snapshot-fetch - If set, disables booting validators from a snapshot Note: the SOLANA_METRICS_CONFIG environment variable is used to configure @@ -137,7 +137,7 @@ while [[ -n $1 ]]; do elif [[ $1 == --machine-type* ]]; then # Bypass quoted long args for GPUs shortArgs+=("$1") shift - elif [[ $1 = --no-snapshot ]]; then + elif [[ $1 = --no-snapshot-fetch ]]; then maybeNoSnapshot="$1" shift 1 else diff --git a/core/Cargo.toml b/core/Cargo.toml index 36c31248b6..4285070937 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,12 +21,12 @@ kvstore = ["solana-kvstore"] bincode = "1.1.4" bs58 = "0.2.0" byteorder = "1.3.2" +bzip2 = "0.3.3" chrono = { version = "0.4.7", features = ["serde"] } core_affinity = "0.5.9" crc = { version = "1.8.1", optional = true } crossbeam-channel = "0.3" dir-diff = "0.3.1" -flate2 = "1.0.9" fs_extra = "1.1.0" indexmap = "1.0" itertools = "0.8.0" diff --git a/core/src/lib.rs b/core/src/lib.rs index a4e84ca343..5ba3c1e12c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -102,9 +102,9 @@ extern crate solana_metrics; #[macro_use] extern crate matches; +extern crate bzip2; extern crate crossbeam_channel; extern crate dir_diff; -extern crate flate2; extern crate fs_extra; extern crate tar; extern crate tempfile; diff --git a/core/src/rpc_service.rs b/core/src/rpc_service.rs index f5884ecec6..91505034c7 100644 --- a/core/src/rpc_service.rs +++ b/core/src/rpc_service.rs @@ -70,8 +70,8 @@ impl RequestMiddleware for RpcRequestMiddleware { fn on_request(&self, request: hyper::Request) -> RequestMiddlewareAction { trace!("request uri: {}", request.uri()); match request.uri().path() { - "/snapshot.tgz" => self.get("snapshot.tgz"), - "/genesis.tgz" => self.get("genesis.tgz"), + "/snapshot.tar.bz2" => self.get("snapshot.tar.bz2"), + "/genesis.tar.bz2" => self.get("genesis.tar.bz2"), _ => RequestMiddlewareAction::Proceed { should_continue_on_invalid_cors: false, request, diff --git a/core/src/snapshot_package.rs b/core/src/snapshot_package.rs index e5e5c5508c..ace39fb110 100644 --- a/core/src/snapshot_package.rs +++ b/core/src/snapshot_package.rs @@ -1,7 +1,6 @@ use crate::result::{Error, Result}; use crate::service::Service; -use flate2::write::GzEncoder; -use flate2::Compression; +use bzip2::write::BzEncoder; use solana_runtime::accounts_db::AccountStorageEntry; use std::fs; use std::path::Path; @@ -77,11 +76,11 @@ impl SnapshotPackagerService { // Create the tar builder let tar_gz = tempfile::Builder::new() .prefix("new_state") - .suffix(".tgz") + .suffix(".tar.bz2") .tempfile_in(tar_dir)?; let temp_tar_path = tar_gz.path(); - let enc = GzEncoder::new(&tar_gz, Compression::default()); + let enc = BzEncoder::new(&tar_gz, bzip2::Compression::Default); let mut tar = tar::Builder::new(enc); // Create the list of paths to compress, starting with the snapshots diff --git a/core/src/snapshot_utils.rs b/core/src/snapshot_utils.rs index 626b16542c..6bcb57a29f 100644 --- a/core/src/snapshot_utils.rs +++ b/core/src/snapshot_utils.rs @@ -3,7 +3,7 @@ use crate::result::{Error, Result}; use crate::snapshot_package::SnapshotPackage; use crate::snapshot_package::{TAR_ACCOUNTS_DIR, TAR_SNAPSHOTS_DIR}; use bincode::{deserialize_from, serialize_into}; -use flate2::read::GzDecoder; +use bzip2::bufread::BzDecoder; use fs_extra::dir::CopyOptions; use solana_runtime::bank::Bank; use solana_runtime::status_cache::SlotDelta; @@ -198,15 +198,15 @@ pub fn bank_from_archive>( } pub fn get_snapshot_tar_path>(snapshot_output_dir: P) -> PathBuf { - snapshot_output_dir.as_ref().join("snapshot.tgz") + snapshot_output_dir.as_ref().join("snapshot.tar.bz2") } pub fn untar_snapshot_in, Q: AsRef>( snapshot_tar: P, unpack_dir: Q, ) -> Result<()> { - let tar_gz = File::open(snapshot_tar)?; - let tar = GzDecoder::new(tar_gz); + let tar_bz2 = File::open(snapshot_tar)?; + let tar = BzDecoder::new(BufReader::new(tar_bz2)); let mut archive = Archive::new(tar); archive.unpack(&unpack_dir)?; Ok(()) diff --git a/multinode-demo/setup.sh b/multinode-demo/setup.sh index 3c2141063c..0f28bb6a8f 100755 --- a/multinode-demo/setup.sh +++ b/multinode-demo/setup.sh @@ -34,5 +34,5 @@ $solana_genesis "${args[@]}" ( cd "$SOLANA_CONFIG_DIR"/bootstrap-leader set -x - tar zcvfS genesis.tgz genesis.bin rocksdb + tar jcvfS genesis.tar.bz2 genesis.bin rocksdb ) diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index fcad0e6846..b1d751ae8d 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -41,10 +41,6 @@ label= identity_keypair_path= voting_keypair_path= no_restart=0 -# TODO: Enable boot_from_snapshot when snapshots work -#boot_from_snapshot=1 -boot_from_snapshot=0 -reset_ledger=0 gossip_entrypoint= ledger_dir= @@ -57,9 +53,6 @@ while [[ -n $1 ]]; do elif [[ $1 = --no-restart ]]; then no_restart=1 shift - elif [[ $1 = --no-snapshot ]]; then - boot_from_snapshot=0 - shift elif [[ $1 = --poll-for-new-genesis-block ]]; then poll_for_new_genesis_block=1 shift @@ -91,6 +84,9 @@ while [[ -n $1 ]]; do elif [[ $1 = --node-lamports ]]; then node_lamports="$2" shift 2 + elif [[ $1 = --no-snapshot-fetch ]]; then + args+=("$1") + shift elif [[ $1 = --no-voting ]]; then args+=("$1") shift @@ -115,9 +111,6 @@ while [[ -n $1 ]]; do elif [[ $1 = --no-airdrop ]]; then airdrops_enabled=0 shift - elif [[ $1 = --reset-ledger ]]; then - reset_ledger=1 - shift elif [[ $1 = --ledger ]]; then ledger_dir=$2 shift 2 @@ -187,14 +180,10 @@ if ((airdrops_enabled)); then default_arg --rpc-drone-address "$drone_address" fi -accounts_dir="$ledger_dir"/accounts -snapshot_dir="$ledger_dir"/snapshots - default_arg --identity "$identity_keypair_path" default_arg --voting-keypair "$voting_keypair_path" default_arg --storage-keypair "$storage_keypair_path" default_arg --ledger "$ledger_dir" -default_arg --accounts "$accounts_dir" #default_arg --snapshot-interval-slots 100 if [[ -n $SOLANA_CUDA ]]; then @@ -213,14 +202,14 @@ new_genesis_block() { return fi - rm -f "$ledger_dir"/new-genesis.tgz + rm -f "$ledger_dir"/new-genesis.tar.bz2 ( set -x - curl -f "$rpc_url"/genesis.tgz -o "$ledger_dir"/new-genesis.tgz + curl -f "$rpc_url"/genesis.tar.bz2 -o "$ledger_dir"/new-genesis.tar.bz2 ) || { echo "Error: failed to fetch new genesis ledger" } - ! diff -q "$ledger_dir"/new-genesis.tgz "$ledger_dir"/genesis.tgz >/dev/null 2>&1 + ! diff -q "$ledger_dir"/new-genesis.tar.bz2 "$ledger_dir"/genesis.tar.bz2 >/dev/null 2>&1 } set -e @@ -245,15 +234,6 @@ kill_node_and_exit() { } trap 'kill_node_and_exit' INT TERM ERR -if ((reset_ledger)); then - echo "Resetting ledger..." - ( - set -x - rm -rf "$ledger_dir" - ) -fi - - wallet() { ( set -x @@ -301,48 +281,6 @@ while true; do ) fi - if [[ ! -f "$ledger_dir"/.genesis ]]; then - echo "Fetching ledger from $rpc_url/genesis.tgz..." - SECONDS= - mkdir -p "$ledger_dir" - while ! curl -f "$rpc_url"/genesis.tgz -o "$ledger_dir"/genesis.tgz; do - echo "Genesis ledger fetch failed" - sleep 5 - done - echo "Fetched genesis ledger in $SECONDS seconds" - - ( - set -x - cd "$ledger_dir" - tar -zxf genesis.tgz - touch .genesis - ) - - ( - if ((boot_from_snapshot)); then - SECONDS= - - echo "Fetching state snapshot $rpc_url/snapshot.tgz..." - mkdir -p "$snapshot_dir" - if ! curl -f "$rpc_url"/snapshot.tgz -o "$snapshot_dir"/snapshot.tgz; then - echo "State snapshot fetch failed" - rm -f "$snapshot_dir"/snapshot.tgz - exit 0 # None fatal - fi - echo "Fetched snapshot in $SECONDS seconds" - - SECONDS= - ( - set -x - cd "$snapshot_dir" - tar -zxf snapshot.tgz - rm snapshot.tgz - ) - echo "Extracted snapshot in $SECONDS seconds" - fi - ) - fi - [[ -r "$identity_keypair_path" ]] || $solana_keygen new -o "$identity_keypair_path" [[ -r "$voting_keypair_path" ]] || $solana_keygen new -o "$voting_keypair_path" [[ -r "$storage_keypair_path" ]] || $solana_keygen new -o "$storage_keypair_path" @@ -360,8 +298,6 @@ identity pubkey: $identity_pubkey vote pubkey: $vote_pubkey storage pubkey: $storage_pubkey ledger: $ledger_dir -accounts: $accounts_dir -snapshots: $snapshot_dir ======================================================================== EOF diff --git a/net/net.sh b/net/net.sh index c328dd978a..0c3b670a1b 100755 --- a/net/net.sh +++ b/net/net.sh @@ -66,7 +66,7 @@ Operate a configured testnet - Amount to fund internal nodes in genesis block. --external-accounts-file FILE_PATH - A YML file with a list of account pubkeys and corresponding lamport balances in genesis block for external nodes - --no-snapshot + --no-snapshot-fetch - If set, disables booting validators from a snapshot --skip-ledger-verify - If set, validators will skip verifying @@ -139,7 +139,7 @@ while [[ -n $1 ]]; do elif [[ $1 = --lamports ]]; then genesisOptions="$genesisOptions $1 $2" shift 2 - elif [[ $1 = --no-snapshot ]]; then + elif [[ $1 = --no-snapshot-fetch ]]; then maybeNoSnapshot="$1" shift 1 elif [[ $1 = --no-deploy ]]; then diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 6b9a000ff8..3864c8036c 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -9,8 +9,10 @@ license = "Apache-2.0" homepage = "https://solana.com/" [dependencies] +bzip2 = "0.3.3" clap = "2.33.0" log = "0.4.8" +reqwest = "0.9.19" serde_json = "1.0.40" solana = { path = "../core", version = "0.18.0-pre1" } solana-drone = { path = "../drone", version = "0.18.0-pre1" } @@ -21,6 +23,8 @@ solana-runtime = { path = "../runtime", version = "0.18.0-pre1" } solana-sdk = { path = "../sdk", version = "0.18.0-pre1" } solana-vote-api = { path = "../programs/vote_api", version = "0.18.0-pre1" } solana-vote-signer = { path = "../vote-signer", version = "0.18.0-pre1" } +tempfile = "3.1.0" +tar = "0.4.26" [features] cuda = ["solana/cuda"] diff --git a/validator/src/main.rs b/validator/src/main.rs index 05a3322271..2bafa5dcaf 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -1,8 +1,10 @@ +use bzip2::bufread::BzDecoder; use clap::{crate_description, crate_name, crate_version, value_t, App, Arg}; use log::*; use solana::bank_forks::SnapshotConfig; use solana::cluster_info::{Node, FULLNODE_PORT_RANGE}; use solana::contact_info::ContactInfo; +use solana::gossip_service::discover; use solana::ledger_cleanup_service::DEFAULT_MAX_LEDGER_SLOTS; use solana::local_vote_signer_service::LocalVoteSignerService; use solana::service::Service; @@ -11,12 +13,12 @@ use solana::validator::{Validator, ValidatorConfig}; use solana_netutil::parse_port_range; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; use solana_sdk::timing::Slot; -use std::fs; -use std::fs::File; +use std::fs::{self, File}; use std::net::SocketAddr; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::exit; use std::sync::Arc; +use std::time::Instant; fn port_range_validator(port_range: String) -> Result<(), String> { if parse_port_range(&port_range).is_some() { @@ -26,6 +28,100 @@ fn port_range_validator(port_range: String) -> Result<(), String> { } } +fn download_archive( + rpc_addr: &SocketAddr, + archive_name: &str, + download_path: &Path, + extract: bool, +) -> Result<(), String> { + let archive_path = download_path.join(archive_name); + if archive_path.is_file() { + return Ok(()); + } + let temp_archive_path = { + let mut p = archive_path.clone(); + p.set_extension(".tmp"); + p + }; + + let url = format!("http://{}/{}", rpc_addr, archive_name); + println!("Downloading {}...", url); + let download_start = Instant::now(); + + let mut response = reqwest::get(&url).map_err(|err| format!("Unable to get: {:?}", err))?; + let mut file = File::create(&temp_archive_path) + .map_err(|err| format!("Unable to create {:?}: {:?}", temp_archive_path, err))?; + std::io::copy(&mut response, &mut file) + .map_err(|err| format!("Unable to write {:?}: {:?}", temp_archive_path, err))?; + + println!( + "Downloaded {} in {:?}", + archive_name, + Instant::now().duration_since(download_start) + ); + + if extract { + println!("Extracting {:?}...", archive_path); + let extract_start = Instant::now(); + let tar_bz2 = File::open(&temp_archive_path) + .map_err(|err| format!("Unable to open {}: {:?}", archive_name, err))?; + let tar = BzDecoder::new(std::io::BufReader::new(tar_bz2)); + let mut archive = tar::Archive::new(tar); + archive + .unpack(download_path) + .map_err(|err| format!("Unable to unpack {}: {:?}", archive_name, err))?; + println!( + "Extracted {} in {:?}", + archive_name, + Instant::now().duration_since(extract_start) + ); + } + std::fs::rename(temp_archive_path, archive_path) + .map_err(|err| format!("Unable to rename: {:?}", err))?; + + Ok(()) +} + +fn initialize_ledger_path( + entrypoint: &ContactInfo, + gossip_addr: &SocketAddr, + ledger_path: &Path, + no_snapshot_fetch: bool, +) -> Result<(), String> { + let (nodes, _replicators) = discover( + &entrypoint.gossip, + Some(1), + Some(60), + None, + Some(&gossip_addr), + ) + .map_err(|err| err.to_string())?; + + let rpc_addr = nodes + .iter() + .filter_map(ContactInfo::valid_client_facing_addr) + .map(|addrs| addrs.0) + .find(|rpc_addr| rpc_addr.ip() == entrypoint.gossip.ip()) + .unwrap_or_else(|| { + eprintln!( + "Entrypoint ({:?}) is not running the RPC service", + entrypoint.gossip.ip() + ); + exit(1); + }); + + fs::create_dir_all(ledger_path).map_err(|err| err.to_string())?; + + download_archive(&rpc_addr, "genesis.tar.bz2", ledger_path, true)?; + if !no_snapshot_fetch { + let _ = fs::remove_file(ledger_path.join("snapshot.tar.bz2")); + download_archive(&rpc_addr, "snapshot.tar.bz2", ledger_path, false) + .unwrap_or_else(|err| eprintln!("Warning: Unable to fetch snapshot: {:?}", err)); + } + + Ok(()) +} + fn main() { solana_logger::setup_with_filter("solana=info"); solana_metrics::set_panic_hook("validator"); @@ -40,7 +136,7 @@ fn main() { .long("blockstream") .takes_value(true) .value_name("UNIX DOMAIN SOCKET") - .help("Open blockstream at this unix domain socket path") + .help("Stream entries to this unix domain socket path") ) .arg( Arg::with_name("identity") @@ -95,6 +191,13 @@ fn main() { .takes_value(true) .help("Rendezvous with the cluster at this entry point"), ) + .arg( + Arg::with_name("no_snapshot_fetch") + .long("no-snapshot-fetch") + .takes_value(false) + .requires("entrypoint") + .help("Do not attempt to fetch a snapshot from the cluster entrypoint"), + ) .arg( Arg::with_name("no_voting") .long("no-voting") @@ -142,7 +245,7 @@ fn main() { .help("Rendezvous with the vote signer at this RPC end point"), ) .arg( - Arg::with_name("accounts") + Arg::with_name("account_paths") .long("accounts") .value_name("PATHS") .takes_value(true) @@ -243,21 +346,19 @@ fn main() { ), ); - if let Some(paths) = matches.value_of("accounts") { - validator_config.account_paths = Some(paths.to_string()); + if let Some(account_paths) = matches.value_of("account_paths") { + validator_config.account_paths = Some(account_paths.to_string()); + } else { + validator_config.account_paths = + Some(ledger_path.join("accounts").to_str().unwrap().to_string()); } validator_config.snapshot_config = matches.value_of("snapshot_interval_slots").map(|s| { let snapshots_dir = ledger_path.clone().join("snapshot"); - let snapshots_bank_state_dir = snapshots_dir.join("bank_states"); - let snapshots_tar_dir = snapshots_dir.join("tar"); fs::create_dir_all(&snapshots_dir).expect("Failed to create snapshots directory"); - fs::create_dir_all(&snapshots_bank_state_dir) - .expect("Failed to create snapshots bank state directory"); - fs::create_dir_all(&snapshots_tar_dir).expect("Failed to create snapshots tar directory"); SnapshotConfig::new( - snapshots_bank_state_dir, - snapshots_tar_dir, + snapshots_dir, + ledger_path.clone(), s.parse::().unwrap(), ) }); @@ -291,7 +392,28 @@ fn main() { .value_of("blockstream_unix_socket") .map(PathBuf::from); - let keypair = Arc::new(keypair); + if let Some(ref entrypoint_addr) = cluster_entrypoint { + initialize_ledger_path( + entrypoint_addr, + &gossip_addr, + &ledger_path, + !matches.is_present("no_snapshot_fetch"), + ) + .unwrap_or_else(|err| { + eprintln!("Failed to download ledger: {}", err); + exit(1); + }); + } else { + // Without a cluster entrypoint, ledger_path must already be present + if !ledger_path.is_dir() { + eprintln!( + "Error: ledger directory does not exist or is not accessible: {:?}", + ledger_path + ); + exit(1); + } + } + let mut node = Node::new_with_external_ip(&keypair.pubkey(), &gossip_addr, dynamic_port_range); if let Some(port) = matches.value_of("rpc_port") { let port_number = port.to_string().parse().expect("integer"); @@ -307,7 +429,7 @@ fn main() { let validator = Validator::new( node, - &keypair, + &Arc::new(keypair), &ledger_path, &vote_account, &Arc::new(voting_keypair),