From 1d0be265d98f2faa4db0120036591233fafe6607 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Thu, 19 Sep 2019 20:50:34 -0700 Subject: [PATCH] Add explicit validator-cuda crate (#5985) --- Cargo.lock | 8 + Cargo.toml | 66 ++- banking_bench/Cargo.toml | 4 - bench-exchange/Cargo.toml | 4 - bench-streamer/Cargo.toml | 4 - bench-tps/Cargo.toml | 4 - book/src/getting-started.md | 2 +- ci/iterations-localnet.sh | 2 +- ci/publish-tarball.sh | 26 +- ci/test-bench.sh | 2 +- ci/test-checks.sh | 2 +- ci/test-stable.sh | 14 +- cli/Cargo.toml | 3 +- core/build.rs | 13 +- drone/Cargo.toml | 3 +- genesis/Cargo.toml | 4 - gossip/Cargo.toml | 3 +- install/Cargo.toml | 3 +- keygen/Cargo.toml | 3 +- ledger-tool/Cargo.toml | 4 - multinode-demo/common.sh | 8 +- replicator/Cargo.toml | 2 - run.sh | 4 +- scripts/cargo-install-all.sh | 31 +- scripts/cargo-install-custom-programs.sh | 2 +- scripts/coverage.sh | 2 +- validator-cuda/.gitignore | 2 + validator-cuda/Cargo.toml | 14 + validator-cuda/src/main.rs | 3 + validator/Cargo.toml | 3 - validator/src/lib.rs | 620 +++++++++++++++++++++++ validator/src/main.rs | 619 +--------------------- 32 files changed, 778 insertions(+), 706 deletions(-) create mode 100644 validator-cuda/.gitignore create mode 100644 validator-cuda/Cargo.toml create mode 100644 validator-cuda/src/main.rs create mode 100644 validator/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f93af7fd9..25e6b1c7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3755,6 +3755,14 @@ dependencies = [ "ureq 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "solana-validator-cuda" +version = "0.19.0-pre0" +dependencies = [ + "solana-core 0.19.0-pre0", + "solana-validator 0.19.0-pre0", +] + [[package]] name = "solana-vote-api" version = "0.19.0-pre0" diff --git a/Cargo.toml b/Cargo.toml index b41d8a050..c31a73f15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] -members = [ +# The members list excluding the `validator-cuda` crate +default-members = [ "bench-exchange", "bench-streamer", "bench-tps", @@ -58,6 +59,69 @@ members = [ "cli", "rayon-threadlimit", ] + +# The default-members list and the `validator-cuda` crate +members = [ + "bench-exchange", + "bench-streamer", + "bench-tps", + "banking_bench", + "chacha-sys", + "client", + "core", + "drone", + "validator", + "genesis", + "genesis_programs", + "gossip", + "install", + "keygen", + "kvstore", + "ledger-tool", + "local_cluster", + "logger", + "merkle-tree", + "measure", + "metrics", + "programs/bpf_loader_api", + "programs/bpf_loader_program", + "programs/budget_api", + "programs/budget_program", + "programs/btc_spv_program", + "programs/btc_spv_api", + "programs/btc_spv_bin", + "programs/config_api", + "programs/config_program", + "programs/config_tests", + "programs/exchange_api", + "programs/exchange_program", + "programs/failure_program", + "programs/move_loader_api", + "programs/move_loader_program", + "programs/librapay_api", + "programs/noop_program", + "programs/stake_api", + "programs/stake_program", + "programs/stake_tests", + "programs/storage_api", + "programs/storage_program", + "programs/token_api", + "programs/token_program", + "programs/vote_api", + "programs/vote_program", + "replicator", + "runtime", + "sdk", + "sdk-c", + "upload-perf", + "netutil", + "fixed-buf", + "vote-signer", + "cli", + "rayon-threadlimit", + "validator-cuda", +] + exclude = [ "programs/bpf", ] diff --git a/banking_bench/Cargo.toml b/banking_bench/Cargo.toml index a37bcd901..2f6f1710d 100644 --- a/banking_bench/Cargo.toml +++ b/banking_bench/Cargo.toml @@ -17,7 +17,3 @@ solana-measure = { path = "../measure", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } rand = "0.6.5" crossbeam-channel = "0.3" - -[features] -cuda = [] - diff --git a/bench-exchange/Cargo.toml b/bench-exchange/Cargo.toml index 46c395b05..8218cc383 100644 --- a/bench-exchange/Cargo.toml +++ b/bench-exchange/Cargo.toml @@ -37,7 +37,3 @@ solana-runtime = { path = "../runtime", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } untrusted = "0.7.0" ws = "0.9.0" - -[features] -cuda = ["solana-core/cuda"] - diff --git a/bench-streamer/Cargo.toml b/bench-streamer/Cargo.toml index a073f19a7..a7196abaa 100644 --- a/bench-streamer/Cargo.toml +++ b/bench-streamer/Cargo.toml @@ -12,7 +12,3 @@ clap = "2.33.0" solana-core = { path = "../core", version = "0.19.0-pre0" } solana-logger = { path = "../logger", version = "0.19.0-pre0" } solana-netutil = { path = "../netutil", version = "0.19.0-pre0" } - -[features] -cuda = ["solana-core/cuda"] - diff --git a/bench-tps/Cargo.toml b/bench-tps/Cargo.toml index 246022190..1a1152ae0 100644 --- a/bench-tps/Cargo.toml +++ b/bench-tps/Cargo.toml @@ -33,7 +33,3 @@ solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.19 [dev-dependencies] serial_test = "0.2.0" serial_test_derive = "0.2.0" - -[features] -cuda = ["solana-core/cuda"] - diff --git a/book/src/getting-started.md b/book/src/getting-started.md index 5632a50a7..7624967a3 100644 --- a/book/src/getting-started.md +++ b/book/src/getting-started.md @@ -44,7 +44,7 @@ $ git checkout $TAG Ensure important programs such as the vote program are built before any nodes are started ```bash -$ cargo build --all +$ cargo build ``` The network is initialized with a genesis ledger generated by running the diff --git a/ci/iterations-localnet.sh b/ci/iterations-localnet.sh index 6dc3b2990..0a2b61b47 100755 --- a/ci/iterations-localnet.sh +++ b/ci/iterations-localnet.sh @@ -26,7 +26,7 @@ build() { $genPipeline && return source ci/rust-version.sh stable source scripts/ulimit-n.sh - _ cargo +$rust_stable build --all + _ cargo +$rust_stable build } runTest() { diff --git a/ci/publish-tarball.sh b/ci/publish-tarball.sh index fa724d3e9..a807781eb 100755 --- a/ci/publish-tarball.sh +++ b/ci/publish-tarball.sh @@ -37,14 +37,14 @@ if [[ -z $CHANNEL_OR_TAG ]]; then exit 1 fi -PERF_LIBS=false +maybeCUDA= case "$CI_OS_NAME" in osx) TARGET=x86_64-apple-darwin ;; linux) TARGET=x86_64-unknown-linux-gnu - PERF_LIBS=true + maybeCUDA=cuda ;; windows) TARGET=x86_64-pc-windows-msvc @@ -70,27 +70,18 @@ echo --- Creating tarball ) > solana-release/version.yml source ci/rust-version.sh stable - scripts/cargo-install-all.sh +"$rust_stable" solana-release + scripts/cargo-install-all.sh +"$rust_stable" solana-release $maybeCUDA - # Reduce the archive size until + # Reduce the Windows archive size until # https://github.com/appveyor/ci/issues/2997 is fixed if [[ -n $APPVEYOR ]]; then rm -f solana-release/bin/solana-validator.exe solana-release/bin/solana-bench-exchange.exe fi - if $PERF_LIBS; then - rm -rf target/perf-libs - ./fetch-perf-libs.sh - mkdir solana-release/target + if $maybeCUDA; then + # Wrap `solana-validator-cuda` with a script that loads perf-libs + # automatically cp -a target/perf-libs solana-release/target/ - - # shellcheck source=/dev/null - source ./target/perf-libs/env.sh - ( - cd validator - cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda - ) - mkdir solana-release/.bin cp solana-release-cuda/bin/solana-validator solana-release/.bin/solana-validator-cuda cat > solana-release/bin/solana-validator-cuda <<'EOF' @@ -105,9 +96,10 @@ if [[ -z $SOLANA_PERF_LIBS_CUDA ]]; then fi exec .bin/solana-validator-cuda "$@" EOF - chmod +x solana-release/bin/solana-validator-cuda + chmod +x solana-release/bin/solana-validator-cuda fi + # TODO: Remove scripts/ and multinode/... from tarball cp -a scripts multinode-demo solana-release/ # Add a wrapper script for validator.sh diff --git a/ci/test-bench.sh b/ci/test-bench.sh index b259fe12a..6f957971e 100755 --- a/ci/test-bench.sh +++ b/ci/test-bench.sh @@ -45,7 +45,7 @@ test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete test -d target/release/bpf && find target/release/bpf -name '*.d' -delete # Ensure all dependencies are built -_ cargo +$rust_nightly build --all --release +_ cargo +$rust_nightly build --release # Remove "BENCH_FILE", if it exists so that the following commands can append rm -f "$BENCH_FILE" diff --git a/ci/test-checks.sh b/ci/test-checks.sh index f09f550a2..3ca63a03c 100755 --- a/ci/test-checks.sh +++ b/ci/test-checks.sh @@ -15,7 +15,7 @@ _ cargo +"$rust_stable" fmt --all -- --check # Clippy gets stuck for unknown reasons if sdk-c is included in the build, so check it separately. # See https://github.com/solana-labs/solana/issues/5503 _ cargo +"$rust_stable" clippy --version -_ cargo +"$rust_stable" clippy --all --exclude solana-sdk-c -- --deny=warnings +_ cargo +"$rust_stable" clippy --all --exclude solana-sdk-c --exclude solana-validator-cuda -- --deny=warnings _ cargo +"$rust_stable" clippy --manifest-path sdk-c/Cargo.toml -- --deny=warnings _ cargo +"$rust_stable" audit --version diff --git a/ci/test-stable.sh b/ci/test-stable.sh index 711a3a580..7c345078a 100755 --- a/ci/test-stable.sh +++ b/ci/test-stable.sh @@ -32,8 +32,8 @@ case $testName in test-stable) echo "Executing $testName" - _ cargo +"$rust_stable" build --all --tests --bins ${V:+--verbose} - _ cargo +"$rust_stable" test --all --exclude solana-local-cluster ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture + _ cargo +"$rust_stable" build --tests --bins ${V:+--verbose} + _ cargo +"$rust_stable" test --all --exclude solana-local-cluster --exclude solana-validator-cuda ${V:+--verbose} -- --nocapture ;; test-stable-perf) echo "Executing $testName" @@ -62,7 +62,7 @@ test-stable-perf) --no-default-features --features=bpf_c,bpf_rust # Run root package tests with these features - ROOT_FEATURES= + maybeCuda= if [[ $(uname) = Linux ]]; then # Enable persistence mode to keep the CUDA kernel driver loaded, avoiding a # lengthy and unexpected delay the first time CUDA is involved when the driver @@ -73,17 +73,17 @@ test-stable-perf) ./fetch-perf-libs.sh # shellcheck source=/dev/null source ./target/perf-libs/env.sh - ROOT_FEATURES=cuda + maybeCuda=--features=cuda export SOLANA_CUDA=1 fi # Run root package library tests - _ cargo +"$rust_stable" build --all --tests --bins ${V:+--verbose} --features="$ROOT_FEATURES" - _ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture + _ cargo +"$rust_stable" build --tests --bins ${V:+--verbose} + _ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} $maybeCuda -- --nocapture ;; test-local-cluster) echo "Executing $testName" - _ cargo +"$rust_stable" build --all --release --tests --bins ${V:+--verbose} + _ cargo +"$rust_stable" build --release --tests --bins ${V:+--verbose} _ cargo +"$rust_stable" test --release --manifest-path=local_cluster/Cargo.toml ${V:+--verbose} -- --nocapture exit 0 ;; diff --git a/cli/Cargo.toml b/cli/Cargo.toml index bb6bc1864..8640d0634 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -44,8 +44,7 @@ url = "2.1.0" solana-core = { path = "../core", version = "0.19.0-pre0" } solana-budget-program = { path = "../programs/budget_program", version = "0.19.0-pre0" } -[features] -cuda = [] + [[bin]] name = "solana" diff --git a/core/build.rs b/core/build.rs index aa3851044..9e1b51072 100644 --- a/core/build.rs +++ b/core/build.rs @@ -1,11 +1,16 @@ use std::env; use std::fs; use std::path::Path; +use std::process::exit; fn main() { println!("cargo:rerun-if-changed=build.rs"); if env::var("CARGO_FEATURE_CUDA").is_ok() { + if cfg!(not(target_os = "linux")) { + eprintln!("Error: CUDA feature is only available on Linux"); + exit(1); + } println!("cargo:rustc-cfg=cuda"); let perf_libs_dir = { @@ -13,10 +18,10 @@ fn main() { let mut path = Path::new(&manifest_dir); path = path.parent().unwrap(); let mut path = path.join(Path::new("target/perf-libs")); - path.push( - env::var("SOLANA_PERF_LIBS_CUDA") - .unwrap_or_else(|err| panic!("SOLANA_PERF_LIBS_CUDA not defined: {}", err)), - ); + path.push(env::var("SOLANA_PERF_LIBS_CUDA").unwrap_or_else(|err| { + eprintln!("Error: SOLANA_PERF_LIBS_CUDA not defined: {}", err); + exit(1); + })); path }; let perf_libs_dir = perf_libs_dir.to_str().unwrap(); diff --git a/drone/Cargo.toml b/drone/Cargo.toml index 2996244ea..33daa91d6 100644 --- a/drone/Cargo.toml +++ b/drone/Cargo.toml @@ -8,8 +8,7 @@ license = "Apache-2.0" homepage = "https://solana.com/" edition = "2018" -[features] -cuda = [] + [dependencies] diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index b7d432255..e47c4891c 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -23,7 +23,3 @@ solana-stake-api = { path = "../programs/stake_api", version = "0.19.0-pre0" } solana-storage-api = { path = "../programs/storage_api", version = "0.19.0-pre0" } solana-vote-api = { path = "../programs/vote_api", version = "0.19.0-pre0" } tempfile = "3.1.0" - -[features] -cuda = ["solana-core/cuda"] - diff --git a/gossip/Cargo.toml b/gossip/Cargo.toml index 0bb4684c4..35e2a8a7c 100644 --- a/gossip/Cargo.toml +++ b/gossip/Cargo.toml @@ -16,6 +16,5 @@ solana-logger = { path = "../logger", version = "0.19.0-pre0" } solana-netutil = { path = "../netutil", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } -[features] -cuda = [] + diff --git a/install/Cargo.toml b/install/Cargo.toml index 079c300ab..8c16c4707 100644 --- a/install/Cargo.toml +++ b/install/Cargo.toml @@ -8,8 +8,7 @@ repository = "https://github.com/solana-labs/solana" license = "Apache-2.0" homepage = "https://solana.com/" -[features] -cuda = [] + [dependencies] diff --git a/keygen/Cargo.toml b/keygen/Cargo.toml index 5656b044d..04de6aca3 100644 --- a/keygen/Cargo.toml +++ b/keygen/Cargo.toml @@ -8,8 +8,7 @@ license = "Apache-2.0" homepage = "https://solana.com/" edition = "2018" -[features] -cuda = [] + [dependencies] diff --git a/ledger-tool/Cargo.toml b/ledger-tool/Cargo.toml index b04d557c1..473a6b24d 100644 --- a/ledger-tool/Cargo.toml +++ b/ledger-tool/Cargo.toml @@ -22,7 +22,3 @@ solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } [dev-dependencies] assert_cmd = "0.11" - -[features] -cuda = [] - diff --git a/multinode-demo/common.sh b/multinode-demo/common.sh index cd364c50b..cbcb8e0a1 100644 --- a/multinode-demo/common.sh +++ b/multinode-demo/common.sh @@ -35,12 +35,6 @@ if [[ -n $USE_INSTALL || ! -f "$SOLANA_ROOT"/Cargo.toml ]]; then else solana_program() { declare program="$1" - declare features="--features=" - if [[ "$program" =~ ^(.*)-cuda$ ]]; then - program=${BASH_REMATCH[1]} - features+="cuda" - fi - declare crate="$program" if [[ -z $program ]]; then crate="cli" @@ -56,7 +50,7 @@ else maybe_release=--release fi declare manifest_path="--manifest-path=$SOLANA_ROOT/$crate/Cargo.toml" - printf "cargo $CARGO_TOOLCHAIN run $manifest_path $maybe_release $maybe_package --bin %s %s -- " "$program" "$features" + printf "cargo $CARGO_TOOLCHAIN run $manifest_path $maybe_release $maybe_package --bin %s %s -- " "$program" } fi diff --git a/replicator/Cargo.toml b/replicator/Cargo.toml index b411fbf6a..678a4b8c5 100644 --- a/replicator/Cargo.toml +++ b/replicator/Cargo.toml @@ -16,5 +16,3 @@ solana-metrics = { path = "../metrics", version = "0.19.0-pre0" } solana-netutil = { path = "../netutil", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } -[features] -cuda = ["solana-core/cuda"] diff --git a/run.sh b/run.sh index e4212818c..910c8868a 100755 --- a/run.sh +++ b/run.sh @@ -3,11 +3,11 @@ # Run a minimal Solana cluster. Ctrl-C to exit. # # Before running this script ensure standard Solana programs are available -# in the PATH, or that `cargo build --all` ran successfully +# in the PATH, or that `cargo build` ran successfully # set -e -# Prefer possible `cargo build --all` binaries over PATH binaries +# Prefer possible `cargo build` binaries over PATH binaries cd "$(dirname "$0")/" PATH=$PWD/target/debug:$PATH diff --git a/scripts/cargo-install-all.sh b/scripts/cargo-install-all.sh index 48489d35e..771d94e0f 100755 --- a/scripts/cargo-install-all.sh +++ b/scripts/cargo-install-all.sh @@ -20,6 +20,11 @@ cargo=cargo cargoFeatures="$2" debugBuild="$3" +if [[ -n $cargoFeatures && $cargoFeatures != cuda ]]; then + echo "Unsupported feature flag: $cargoFeatures" + exit 1 +fi + buildVariant=release maybeReleaseFlag=--release if [[ -n "$debugBuild" ]]; then @@ -36,10 +41,13 @@ SECONDS=0 ( set -x # shellcheck disable=SC2086 # Don't want to double quote $rust_version - $cargo $rust_version build --all $maybeReleaseFlag --features="$cargoFeatures" + $cargo $rust_version build --all $maybeReleaseFlag ) BINS=( + solana + solana-bench-exchange + solana-bench-tps solana-drone solana-gossip solana-install @@ -48,9 +56,6 @@ BINS=( solana-ledger-tool solana-replicator solana-validator - solana - solana-bench-exchange - solana-bench-tps ) #XXX: Ensure `solana-genesis` is built LAST! @@ -65,7 +70,7 @@ done ( set -x # shellcheck disable=SC2086 # Don't want to double quote $rust_version - $cargo $rust_version build $maybeReleaseFlag "${binArgs[@]}" --features="$cargoFeatures" + $cargo $rust_version build $maybeReleaseFlag "${binArgs[@]}" ) mkdir -p "$installDir/bin" @@ -73,6 +78,22 @@ for bin in "${BINS[@]}"; do cp -fv "target/$buildVariant/$bin" "$installDir"/bin done + +if [[ "$cargoFeatures" = cuda ]]; then + ( + set -x + ./fetch-perf-libs.sh + + # shellcheck source=/dev/null + source ./target/perf-libs/env.sh + + cd validator-cuda + # shellcheck disable=SC2086 # Don't want to double quote $rust_version + cargo $rust_version build $maybeReleaseFlag + ) + cp -fv "target/$buildVariant/solana-validator-cuda" "$installDir"/bin +fi + for dir in programs/*; do for program in echo target/$buildVariant/deps/libsolana_"$(basename "$dir")".{so,dylib,dll}; do if [[ -f $program ]]; then diff --git a/scripts/cargo-install-custom-programs.sh b/scripts/cargo-install-custom-programs.sh index cfd210a87..5c9e0d09b 100755 --- a/scripts/cargo-install-custom-programs.sh +++ b/scripts/cargo-install-custom-programs.sh @@ -13,7 +13,7 @@ programDir="$2" ( set -x cd "$programDir" - cargo build --all --release + cargo build --release ) for dir in "$programDir"/*; do diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 6c3d57e25..5954c0b55 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -15,7 +15,7 @@ reportName="lcov-${CI_COMMIT:0:9}" if [[ -n $1 ]]; then crate="--manifest-path=$1/Cargo.toml" else - crate="--all --exclude solana-local-cluster" + crate="--all --exclude solana-local-cluster --exclude solana-validator-cuda" fi coverageFlags=(-Zprofile) # Enable coverage diff --git a/validator-cuda/.gitignore b/validator-cuda/.gitignore new file mode 100644 index 000000000..5404b132d --- /dev/null +++ b/validator-cuda/.gitignore @@ -0,0 +1,2 @@ +/target/ +/farf/ diff --git a/validator-cuda/Cargo.toml b/validator-cuda/Cargo.toml new file mode 100644 index 000000000..cbde01e36 --- /dev/null +++ b/validator-cuda/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["Solana Maintainers "] +edition = "2018" +name = "solana-validator-cuda" +description = "Blockchain, Rebuilt for Scale" +version = "0.19.0-pre0" +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +publish = false + +[dependencies] +solana-core = { path = "../core", version = "0.19.0-pre0", features=["cuda"] } +solana-validator = { path = "../validator", version = "0.19.0-pre0" } diff --git a/validator-cuda/src/main.rs b/validator-cuda/src/main.rs new file mode 100644 index 000000000..1bba484af --- /dev/null +++ b/validator-cuda/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + solana_validator::main() +} diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 668371acd..ed481e5f5 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -28,6 +28,3 @@ solana-vote-signer = { path = "../vote-signer", version = "0.19.0-pre0" } tempfile = "3.1.0" tar = "0.4.26" ureq = { version = "0.11.1", default-features = false } - -[features] -cuda = ["solana-core/cuda"] diff --git a/validator/src/lib.rs b/validator/src/lib.rs new file mode 100644 index 000000000..234a6aeff --- /dev/null +++ b/validator/src/lib.rs @@ -0,0 +1,620 @@ +use bzip2::bufread::BzDecoder; +use clap::{crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, Arg}; +use console::{style, Emoji}; +use indicatif::{ProgressBar, ProgressStyle}; +use log::*; +use solana_client::rpc_client::RpcClient; +use solana_core::bank_forks::SnapshotConfig; +use solana_core::cluster_info::{Node, FULLNODE_PORT_RANGE}; +use solana_core::contact_info::ContactInfo; +use solana_core::gossip_service::discover; +use solana_core::ledger_cleanup_service::DEFAULT_MAX_LEDGER_SLOTS; +use solana_core::service::Service; +use solana_core::socketaddr; +use solana_core::validator::{Validator, ValidatorConfig}; +use solana_sdk::clock::Slot; +use solana_sdk::hash::Hash; +use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; +use std::fs::{self, File}; +use std::io::{self, Read}; +use std::net::{SocketAddr, TcpListener}; +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 solana_netutil::parse_port_range(&port_range).is_some() { + Ok(()) + } else { + Err("Invalid port range".to_string()) + } +} + +static TRUCK: Emoji = Emoji("🚚 ", ""); +static SPARKLE: Emoji = Emoji("✨ ", ""); + +/// Creates a new process bar for processing that will take an unknown amount of time +fn new_spinner_progress_bar() -> ProgressBar { + let progress_bar = ProgressBar::new(42); + progress_bar + .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}")); + progress_bar.enable_steady_tick(100); + progress_bar +} + +fn download_tar_bz2( + 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); + let download_start = Instant::now(); + + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_message(&format!("{}Downloading {}...", TRUCK, url)); + + let client = ureq::agent(); + let response = client.get(url.as_str()).call(); + if response.error() { + let error = if let Some(err) = response.synthetic_error().as_ref() { + format!("Unable to get: {:?}", err) + } else { + "Unable to get: unspecified error".to_string() + }; + Err(error)? + } + let download_size = { + response + .header("Content-Length") + .and_then(|content_length| content_length.parse().ok()) + .unwrap_or(0) + }; + progress_bar.set_length(download_size); + progress_bar.set_style( + ProgressStyle::default_bar() + .template(&format!( + "{}{}Downloading {} {}", + "{spinner:.green} ", + TRUCK, + url, + "[{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})" + )) + .progress_chars("=> "), + ); + + struct DownloadProgress { + progress_bar: ProgressBar, + response: R, + } + + impl Read for DownloadProgress { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.response.read(buf).map(|n| { + self.progress_bar.inc(n as u64); + n + }) + } + } + + let mut source = DownloadProgress { + progress_bar, + response: response.into_reader(), + }; + + let mut file = File::create(&temp_archive_path) + .map_err(|err| format!("Unable to create {:?}: {:?}", temp_archive_path, err))?; + std::io::copy(&mut source, &mut file) + .map_err(|err| format!("Unable to write {:?}: {:?}", temp_archive_path, err))?; + + source.progress_bar.finish_and_clear(); + println!( + " {}{}", + SPARKLE, + format!( + "Downloaded {} ({} bytes) in {:?}", + url, + download_size, + 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, + ledger_path: &Path, + no_snapshot_fetch: bool, +) -> Result { + let (nodes, _replicators) = discover( + &entrypoint.gossip, + Some(1), + Some(60), + None, + Some(entrypoint.gossip.ip()), + None, + ) + .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); + }); + + let client = RpcClient::new_socket(rpc_addr); + let genesis_blockhash = client + .get_genesis_blockhash() + .map_err(|err| err.to_string())?; + + fs::create_dir_all(ledger_path).map_err(|err| err.to_string())?; + + download_tar_bz2(&rpc_addr, "genesis.tar.bz2", ledger_path, true)?; + + if !no_snapshot_fetch { + let snapshot_package = solana_core::snapshot_utils::get_snapshot_tar_path(ledger_path); + if snapshot_package.exists() { + fs::remove_file(&snapshot_package) + .unwrap_or_else(|err| warn!("error removing {:?}: {}", snapshot_package, err)); + } + download_tar_bz2( + &rpc_addr, + snapshot_package.file_name().unwrap().to_str().unwrap(), + snapshot_package.parent().unwrap(), + false, + ) + .unwrap_or_else(|err| eprintln!("Warning: Unable to fetch snapshot: {:?}", err)); + } + + match client.get_slot() { + Ok(slot) => info!("Entrypoint currently at slot {}", slot), + Err(err) => warn!("Failed to get_slot from entrypoint: {}", err), + } + + Ok(genesis_blockhash) +} + +// Return an error if a keypair file cannot be parsed. +fn is_keypair(string: String) -> Result<(), String> { + read_keypair(&string) + .map(|_| ()) + .map_err(|err| format!("{:?}", err)) +} + +pub fn main() { + solana_logger::setup_with_filter("solana=info"); + solana_metrics::set_panic_hook("validator"); + + let default_dynamic_port_range = + &format!("{}-{}", FULLNODE_PORT_RANGE.0, FULLNODE_PORT_RANGE.1); + + let matches = App::new(crate_name!()).about(crate_description!()) + .version(crate_version!()) + .arg( + Arg::with_name("blockstream_unix_socket") + .long("blockstream") + .takes_value(true) + .value_name("UNIX DOMAIN SOCKET") + .help("Stream entries to this unix domain socket path") + ) + .arg( + Arg::with_name("identity") + .short("i") + .long("identity") + .value_name("PATH") + .takes_value(true) + .validator(is_keypair) + .help("File containing the identity keypair for the validator"), + ) + .arg( + Arg::with_name("voting_keypair") + .long("voting-keypair") + .value_name("PATH") + .takes_value(true) + .validator(is_keypair) + .help("File containing the authorized voting keypair. Default is an ephemeral keypair"), + ) + .arg( + Arg::with_name("vote_account") + .long("vote-account") + .value_name("PUBKEY") + .takes_value(true) + .validator(is_keypair) + .help("Public key of the vote account to vote with. Default is the public key of the voting keypair"), + ) + .arg( + Arg::with_name("storage_keypair") + .long("storage-keypair") + .value_name("PATH") + .takes_value(true) + .validator(is_keypair) + .help("File containing the storage account keypair. Default is an ephemeral keypair"), + ) + .arg( + Arg::with_name("init_complete_file") + .long("init-complete-file") + .value_name("FILE") + .takes_value(true) + .help("Create this file, if it doesn't already exist, once node initialization is complete"), + ) + .arg( + Arg::with_name("ledger_path") + .short("l") + .long("ledger") + .value_name("DIR") + .takes_value(true) + .required(true) + .help("Use DIR as persistent ledger location"), + ) + .arg( + Arg::with_name("entrypoint") + .short("n") + .long("entrypoint") + .value_name("HOST:PORT") + .takes_value(true) + .validator(solana_netutil::is_host_port) + .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 new snapshot from the cluster entrypoint, start from a local snapshot if present"), + ) + .arg( + Arg::with_name("no_voting") + .long("no-voting") + .takes_value(false) + .help("Launch node without voting"), + ) + .arg( + Arg::with_name("dev_no_sigverify") + .long("dev-no-sigverify") + .takes_value(false) + .help("Run without signature verification"), + ) + .arg( + Arg::with_name("dev_halt_at_slot") + .long("dev-halt-at-slot") + .value_name("SLOT") + .takes_value(true) + .help("Halt the validator when it reaches the given slot"), + ) + .arg( + Arg::with_name("rpc_port") + .long("rpc-port") + .value_name("PORT") + .takes_value(true) + .help("RPC port to use for this node"), + ) + .arg( + Arg::with_name("enable_rpc_exit") + .long("enable-rpc-exit") + .takes_value(false) + .help("Enable the JSON RPC 'fullnodeExit' API. Only enable in a debug environment"), + ) + .arg( + Arg::with_name("rpc_drone_addr") + .long("rpc-drone-address") + .value_name("HOST:PORT") + .takes_value(true) + .validator(solana_netutil::is_host_port) + .help("Enable the JSON RPC 'requestAirdrop' API with this drone address."), + ) + .arg( + Arg::with_name("signer_addr") + .long("vote-signer-address") + .value_name("HOST:PORT") + .takes_value(true) + .hidden(true) // Don't document this argument to discourage its use + .validator(solana_netutil::is_host_port) + .help("Rendezvous with the vote signer at this RPC end point"), + ) + .arg( + Arg::with_name("account_paths") + .long("accounts") + .value_name("PATHS") + .takes_value(true) + .help("Comma separated persistent accounts location"), + ) + .arg( + clap::Arg::with_name("gossip_port") + .long("gossip-port") + .value_name("HOST:PORT") + .takes_value(true) + .help("Gossip port number for the node"), + ) + .arg( + clap::Arg::with_name("dynamic_port_range") + .long("dynamic-port-range") + .value_name("MIN_PORT-MAX_PORT") + .takes_value(true) + .default_value(default_dynamic_port_range) + .validator(port_range_validator) + .help("Range to use for dynamically assigned ports"), + ) + .arg( + clap::Arg::with_name("snapshot_interval_slots") + .long("snapshot-interval-slots") + .value_name("SNAPSHOT_INTERVAL_SLOTS") + .takes_value(true) + .default_value("100") + .help("Number of slots between generating snapshots, 0 to disable snapshots"), + ) + .arg( + clap::Arg::with_name("limit_ledger_size") + .long("limit-ledger-size") + .takes_value(false) + .help("drop older slots in the ledger"), + ) + .arg( + clap::Arg::with_name("skip_ledger_verify") + .long("skip-ledger-verify") + .takes_value(false) + .help("Skip ledger verification at node bootup"), + ) + .get_matches(); + + let mut validator_config = ValidatorConfig::default(); + let keypair = if let Some(identity) = matches.value_of("identity") { + read_keypair(identity).unwrap_or_else(|err| { + eprintln!("{}: Unable to open keypair file: {}", err, identity); + exit(1); + }) + } else { + Keypair::new() + }; + + let voting_keypair = if let Some(identity) = matches.value_of("voting_keypair") { + read_keypair(identity).unwrap_or_else(|err| { + eprintln!("{}: Unable to open keypair file: {}", err, identity); + exit(1); + }) + } else { + Keypair::new() + }; + let storage_keypair = if let Some(storage_keypair) = matches.value_of("storage_keypair") { + read_keypair(storage_keypair).unwrap_or_else(|err| { + eprintln!("{}: Unable to open keypair file: {}", err, storage_keypair); + exit(1); + }) + } else { + Keypair::new() + }; + + let vote_account = matches + .value_of("vote_account") + .map_or(voting_keypair.pubkey(), |pubkey| { + pubkey.parse().expect("failed to parse vote_account") + }); + + let ledger_path = PathBuf::from(matches.value_of("ledger_path").unwrap()); + + validator_config.dev_sigverify_disabled = matches.is_present("dev_no_sigverify"); + validator_config.dev_halt_at_slot = value_t!(matches, "dev_halt_at_slot", Slot).ok(); + + validator_config.voting_disabled = matches.is_present("no_voting"); + + validator_config.rpc_config.enable_fullnode_exit = matches.is_present("enable_rpc_exit"); + + validator_config.rpc_config.drone_addr = matches.value_of("rpc_drone_addr").map(|address| { + solana_netutil::parse_host_port(address).expect("failed to parse drone address") + }); + + let dynamic_port_range = + solana_netutil::parse_port_range(matches.value_of("dynamic_port_range").unwrap()) + .expect("invalid dynamic_port_range"); + + let mut gossip_addr = solana_netutil::parse_port_or_addr( + matches.value_of("gossip_port"), + socketaddr!( + [127, 0, 0, 1], + solana_netutil::find_available_port_in_range(dynamic_port_range) + .expect("unable to find an available gossip port") + ), + ); + + 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()); + } + + let snapshot_interval_slots = value_t_or_exit!(matches, "snapshot_interval_slots", usize); + let snapshot_path = ledger_path.clone().join("snapshot"); + fs::create_dir_all(&snapshot_path).unwrap_or_else(|err| { + eprintln!( + "Failed to create snapshots directory {:?}: {}", + snapshot_path, err + ); + exit(1); + }); + + validator_config.snapshot_config = Some(SnapshotConfig { + snapshot_interval_slots: if snapshot_interval_slots > 0 { + snapshot_interval_slots + } else { + std::usize::MAX + }, + snapshot_path, + snapshot_package_output_path: ledger_path.clone(), + }); + + if matches.is_present("limit_ledger_size") { + validator_config.max_ledger_slots = Some(DEFAULT_MAX_LEDGER_SLOTS); + } + let cluster_entrypoint = matches.value_of("entrypoint").map(|entrypoint| { + let entrypoint_addr = solana_netutil::parse_host_port(entrypoint) + .expect("failed to parse entrypoint address"); + let ip_addr = solana_netutil::get_public_ip_addr(&entrypoint_addr).unwrap_or_else(|err| { + eprintln!( + "Failed to contact cluster entrypoint {} ({}): {}", + entrypoint, entrypoint_addr, err + ); + exit(1); + }); + gossip_addr.set_ip(ip_addr); + + ContactInfo::new_gossip_entry_point(&entrypoint_addr) + }); + + if matches.value_of("signer_addr").is_some() { + warn!("--vote-signer-address ignored"); + /* + let (_signer_service, _signer_addr) = if let Some(signer_addr) = matches.value_of("signer_addr") + { + ( + None, + signer_addr.to_string().parse().expect("Signer IP Address"), + ) + } else { + // Run a local vote signer if a vote signer service address was not provided + let (signer_service, signer_addr) = solana_core::local_vote_signer_service::LocalVoteSignerService::new(dynamic_port_range); + (Some(signer_service), signer_addr) + }; + */ + } + + let init_complete_file = matches.value_of("init_complete_file"); + let verify_ledger = !matches.is_present("skip_ledger_verify"); + validator_config.blockstream_unix_socket = matches + .value_of("blockstream_unix_socket") + .map(PathBuf::from); + + println!( + "{} version {} (branch={}, commit={})", + style(crate_name!()).bold(), + crate_version!(), + option_env!("CI_BRANCH").unwrap_or("unknown"), + option_env!("CI_COMMIT").unwrap_or("unknown") + ); + solana_metrics::set_host_id(keypair.pubkey().to_string()); + + let mut tcp_ports = vec![]; + 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"); + if port_number == 0 { + eprintln!("Invalid RPC port requested: {:?}", port); + exit(1); + } + node.info.rpc = SocketAddr::new(node.info.gossip.ip(), port_number); + node.info.rpc_pubsub = SocketAddr::new(node.info.gossip.ip(), port_number + 1); + tcp_ports = vec![port_number, port_number + 1]; + }; + + if let Some(ref cluster_entrypoint) = cluster_entrypoint { + let udp_sockets = [ + &node.sockets.gossip, + &node.sockets.broadcast, + &node.sockets.repair, + &node.sockets.retransmit, + ]; + + let mut tcp_listeners: Vec<(_, _)> = tcp_ports + .iter() + .map(|port| { + ( + *port, + TcpListener::bind(&SocketAddr::from(([0, 0, 0, 0], *port))).unwrap_or_else( + |err| { + error!("Unable to bind to tcp/{}: {}", port, err); + std::process::exit(1); + }, + ), + ) + }) + .collect(); + if let Some(ip_echo) = &node.sockets.ip_echo { + let ip_echo = ip_echo.try_clone().expect("unable to clone tcp_listener"); + tcp_listeners.push((node.info.gossip.port(), ip_echo)); + } + solana_netutil::verify_reachable_ports( + &cluster_entrypoint.gossip, + tcp_listeners, + &udp_sockets, + ); + + let expected_genesis_blockhash = initialize_ledger_path( + cluster_entrypoint, + &ledger_path, + matches.is_present("no_snapshot_fetch"), + ) + .unwrap_or_else(|err| { + eprintln!("Failed to download ledger: {}", err); + exit(1); + }); + validator_config.expected_genesis_blockhash = Some(expected_genesis_blockhash); + } 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 validator = Validator::new( + node, + &Arc::new(keypair), + &ledger_path, + &vote_account, + &Arc::new(voting_keypair), + &Arc::new(storage_keypair), + cluster_entrypoint.as_ref(), + verify_ledger, + &validator_config, + ); + + if let Some(filename) = init_complete_file { + File::create(filename).unwrap_or_else(|_| { + eprintln!("Unable to create: {}", filename); + exit(1); + }); + } + info!("Validator initialized"); + validator.join().expect("validator exit"); + info!("Validator exiting.."); +} diff --git a/validator/src/main.rs b/validator/src/main.rs index 050b8e88f..1bba484af 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -1,620 +1,3 @@ -use bzip2::bufread::BzDecoder; -use clap::{crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, Arg}; -use console::{style, Emoji}; -use indicatif::{ProgressBar, ProgressStyle}; -use log::*; -use solana_client::rpc_client::RpcClient; -use solana_core::bank_forks::SnapshotConfig; -use solana_core::cluster_info::{Node, FULLNODE_PORT_RANGE}; -use solana_core::contact_info::ContactInfo; -use solana_core::gossip_service::discover; -use solana_core::ledger_cleanup_service::DEFAULT_MAX_LEDGER_SLOTS; -use solana_core::service::Service; -use solana_core::socketaddr; -use solana_core::validator::{Validator, ValidatorConfig}; -use solana_sdk::clock::Slot; -use solana_sdk::hash::Hash; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; -use std::fs::{self, File}; -use std::io::{self, Read}; -use std::net::{SocketAddr, TcpListener}; -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 solana_netutil::parse_port_range(&port_range).is_some() { - Ok(()) - } else { - Err("Invalid port range".to_string()) - } -} - -static TRUCK: Emoji = Emoji("🚚 ", ""); -static SPARKLE: Emoji = Emoji("✨ ", ""); - -/// Creates a new process bar for processing that will take an unknown amount of time -fn new_spinner_progress_bar() -> ProgressBar { - let progress_bar = ProgressBar::new(42); - progress_bar - .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}")); - progress_bar.enable_steady_tick(100); - progress_bar -} - -fn download_tar_bz2( - 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); - let download_start = Instant::now(); - - let progress_bar = new_spinner_progress_bar(); - progress_bar.set_message(&format!("{}Downloading {}...", TRUCK, url)); - - let client = ureq::agent(); - let response = client.get(url.as_str()).call(); - if response.error() { - let error = if let Some(err) = response.synthetic_error().as_ref() { - format!("Unable to get: {:?}", err) - } else { - "Unable to get: unspecified error".to_string() - }; - Err(error)? - } - let download_size = { - response - .header("Content-Length") - .and_then(|content_length| content_length.parse().ok()) - .unwrap_or(0) - }; - progress_bar.set_length(download_size); - progress_bar.set_style( - ProgressStyle::default_bar() - .template(&format!( - "{}{}Downloading {} {}", - "{spinner:.green} ", - TRUCK, - url, - "[{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})" - )) - .progress_chars("=> "), - ); - - struct DownloadProgress { - progress_bar: ProgressBar, - response: R, - } - - impl Read for DownloadProgress { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.response.read(buf).map(|n| { - self.progress_bar.inc(n as u64); - n - }) - } - } - - let mut source = DownloadProgress { - progress_bar, - response: response.into_reader(), - }; - - let mut file = File::create(&temp_archive_path) - .map_err(|err| format!("Unable to create {:?}: {:?}", temp_archive_path, err))?; - std::io::copy(&mut source, &mut file) - .map_err(|err| format!("Unable to write {:?}: {:?}", temp_archive_path, err))?; - - source.progress_bar.finish_and_clear(); - println!( - " {}{}", - SPARKLE, - format!( - "Downloaded {} ({} bytes) in {:?}", - url, - download_size, - 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, - ledger_path: &Path, - no_snapshot_fetch: bool, -) -> Result { - let (nodes, _replicators) = discover( - &entrypoint.gossip, - Some(1), - Some(60), - None, - Some(entrypoint.gossip.ip()), - None, - ) - .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); - }); - - let client = RpcClient::new_socket(rpc_addr); - let genesis_blockhash = client - .get_genesis_blockhash() - .map_err(|err| err.to_string())?; - - fs::create_dir_all(ledger_path).map_err(|err| err.to_string())?; - - download_tar_bz2(&rpc_addr, "genesis.tar.bz2", ledger_path, true)?; - - if !no_snapshot_fetch { - let snapshot_package = solana_core::snapshot_utils::get_snapshot_tar_path(ledger_path); - if snapshot_package.exists() { - fs::remove_file(&snapshot_package) - .unwrap_or_else(|err| warn!("error removing {:?}: {}", snapshot_package, err)); - } - download_tar_bz2( - &rpc_addr, - snapshot_package.file_name().unwrap().to_str().unwrap(), - snapshot_package.parent().unwrap(), - false, - ) - .unwrap_or_else(|err| eprintln!("Warning: Unable to fetch snapshot: {:?}", err)); - } - - match client.get_slot() { - Ok(slot) => info!("Entrypoint currently at slot {}", slot), - Err(err) => warn!("Failed to get_slot from entrypoint: {}", err), - } - - Ok(genesis_blockhash) -} - -// Return an error if a keypair file cannot be parsed. -fn is_keypair(string: String) -> Result<(), String> { - read_keypair(&string) - .map(|_| ()) - .map_err(|err| format!("{:?}", err)) -} - fn main() { - solana_logger::setup_with_filter("solana=info"); - solana_metrics::set_panic_hook("validator"); - - let default_dynamic_port_range = - &format!("{}-{}", FULLNODE_PORT_RANGE.0, FULLNODE_PORT_RANGE.1); - - let matches = App::new(crate_name!()).about(crate_description!()) - .version(crate_version!()) - .arg( - Arg::with_name("blockstream_unix_socket") - .long("blockstream") - .takes_value(true) - .value_name("UNIX DOMAIN SOCKET") - .help("Stream entries to this unix domain socket path") - ) - .arg( - Arg::with_name("identity") - .short("i") - .long("identity") - .value_name("PATH") - .takes_value(true) - .validator(is_keypair) - .help("File containing the identity keypair for the validator"), - ) - .arg( - Arg::with_name("voting_keypair") - .long("voting-keypair") - .value_name("PATH") - .takes_value(true) - .validator(is_keypair) - .help("File containing the authorized voting keypair. Default is an ephemeral keypair"), - ) - .arg( - Arg::with_name("vote_account") - .long("vote-account") - .value_name("PUBKEY") - .takes_value(true) - .validator(is_keypair) - .help("Public key of the vote account to vote with. Default is the public key of the voting keypair"), - ) - .arg( - Arg::with_name("storage_keypair") - .long("storage-keypair") - .value_name("PATH") - .takes_value(true) - .validator(is_keypair) - .help("File containing the storage account keypair. Default is an ephemeral keypair"), - ) - .arg( - Arg::with_name("init_complete_file") - .long("init-complete-file") - .value_name("FILE") - .takes_value(true) - .help("Create this file, if it doesn't already exist, once node initialization is complete"), - ) - .arg( - Arg::with_name("ledger_path") - .short("l") - .long("ledger") - .value_name("DIR") - .takes_value(true) - .required(true) - .help("Use DIR as persistent ledger location"), - ) - .arg( - Arg::with_name("entrypoint") - .short("n") - .long("entrypoint") - .value_name("HOST:PORT") - .takes_value(true) - .validator(solana_netutil::is_host_port) - .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 new snapshot from the cluster entrypoint, start from a local snapshot if present"), - ) - .arg( - Arg::with_name("no_voting") - .long("no-voting") - .takes_value(false) - .help("Launch node without voting"), - ) - .arg( - Arg::with_name("dev_no_sigverify") - .long("dev-no-sigverify") - .takes_value(false) - .help("Run without signature verification"), - ) - .arg( - Arg::with_name("dev_halt_at_slot") - .long("dev-halt-at-slot") - .value_name("SLOT") - .takes_value(true) - .help("Halt the validator when it reaches the given slot"), - ) - .arg( - Arg::with_name("rpc_port") - .long("rpc-port") - .value_name("PORT") - .takes_value(true) - .help("RPC port to use for this node"), - ) - .arg( - Arg::with_name("enable_rpc_exit") - .long("enable-rpc-exit") - .takes_value(false) - .help("Enable the JSON RPC 'fullnodeExit' API. Only enable in a debug environment"), - ) - .arg( - Arg::with_name("rpc_drone_addr") - .long("rpc-drone-address") - .value_name("HOST:PORT") - .takes_value(true) - .validator(solana_netutil::is_host_port) - .help("Enable the JSON RPC 'requestAirdrop' API with this drone address."), - ) - .arg( - Arg::with_name("signer_addr") - .long("vote-signer-address") - .value_name("HOST:PORT") - .takes_value(true) - .hidden(true) // Don't document this argument to discourage its use - .validator(solana_netutil::is_host_port) - .help("Rendezvous with the vote signer at this RPC end point"), - ) - .arg( - Arg::with_name("account_paths") - .long("accounts") - .value_name("PATHS") - .takes_value(true) - .help("Comma separated persistent accounts location"), - ) - .arg( - clap::Arg::with_name("gossip_port") - .long("gossip-port") - .value_name("HOST:PORT") - .takes_value(true) - .help("Gossip port number for the node"), - ) - .arg( - clap::Arg::with_name("dynamic_port_range") - .long("dynamic-port-range") - .value_name("MIN_PORT-MAX_PORT") - .takes_value(true) - .default_value(default_dynamic_port_range) - .validator(port_range_validator) - .help("Range to use for dynamically assigned ports"), - ) - .arg( - clap::Arg::with_name("snapshot_interval_slots") - .long("snapshot-interval-slots") - .value_name("SNAPSHOT_INTERVAL_SLOTS") - .takes_value(true) - .default_value("100") - .help("Number of slots between generating snapshots, 0 to disable snapshots"), - ) - .arg( - clap::Arg::with_name("limit_ledger_size") - .long("limit-ledger-size") - .takes_value(false) - .help("drop older slots in the ledger"), - ) - .arg( - clap::Arg::with_name("skip_ledger_verify") - .long("skip-ledger-verify") - .takes_value(false) - .help("Skip ledger verification at node bootup"), - ) - .get_matches(); - - let mut validator_config = ValidatorConfig::default(); - let keypair = if let Some(identity) = matches.value_of("identity") { - read_keypair(identity).unwrap_or_else(|err| { - eprintln!("{}: Unable to open keypair file: {}", err, identity); - exit(1); - }) - } else { - Keypair::new() - }; - - let voting_keypair = if let Some(identity) = matches.value_of("voting_keypair") { - read_keypair(identity).unwrap_or_else(|err| { - eprintln!("{}: Unable to open keypair file: {}", err, identity); - exit(1); - }) - } else { - Keypair::new() - }; - let storage_keypair = if let Some(storage_keypair) = matches.value_of("storage_keypair") { - read_keypair(storage_keypair).unwrap_or_else(|err| { - eprintln!("{}: Unable to open keypair file: {}", err, storage_keypair); - exit(1); - }) - } else { - Keypair::new() - }; - - let vote_account = matches - .value_of("vote_account") - .map_or(voting_keypair.pubkey(), |pubkey| { - pubkey.parse().expect("failed to parse vote_account") - }); - - let ledger_path = PathBuf::from(matches.value_of("ledger_path").unwrap()); - - validator_config.dev_sigverify_disabled = matches.is_present("dev_no_sigverify"); - validator_config.dev_halt_at_slot = value_t!(matches, "dev_halt_at_slot", Slot).ok(); - - validator_config.voting_disabled = matches.is_present("no_voting"); - - validator_config.rpc_config.enable_fullnode_exit = matches.is_present("enable_rpc_exit"); - - validator_config.rpc_config.drone_addr = matches.value_of("rpc_drone_addr").map(|address| { - solana_netutil::parse_host_port(address).expect("failed to parse drone address") - }); - - let dynamic_port_range = - solana_netutil::parse_port_range(matches.value_of("dynamic_port_range").unwrap()) - .expect("invalid dynamic_port_range"); - - let mut gossip_addr = solana_netutil::parse_port_or_addr( - matches.value_of("gossip_port"), - socketaddr!( - [127, 0, 0, 1], - solana_netutil::find_available_port_in_range(dynamic_port_range) - .expect("unable to find an available gossip port") - ), - ); - - 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()); - } - - let snapshot_interval_slots = value_t_or_exit!(matches, "snapshot_interval_slots", usize); - let snapshot_path = ledger_path.clone().join("snapshot"); - fs::create_dir_all(&snapshot_path).unwrap_or_else(|err| { - eprintln!( - "Failed to create snapshots directory {:?}: {}", - snapshot_path, err - ); - exit(1); - }); - - validator_config.snapshot_config = Some(SnapshotConfig { - snapshot_interval_slots: if snapshot_interval_slots > 0 { - snapshot_interval_slots - } else { - std::usize::MAX - }, - snapshot_path, - snapshot_package_output_path: ledger_path.clone(), - }); - - if matches.is_present("limit_ledger_size") { - validator_config.max_ledger_slots = Some(DEFAULT_MAX_LEDGER_SLOTS); - } - let cluster_entrypoint = matches.value_of("entrypoint").map(|entrypoint| { - let entrypoint_addr = solana_netutil::parse_host_port(entrypoint) - .expect("failed to parse entrypoint address"); - let ip_addr = solana_netutil::get_public_ip_addr(&entrypoint_addr).unwrap_or_else(|err| { - eprintln!( - "Failed to contact cluster entrypoint {} ({}): {}", - entrypoint, entrypoint_addr, err - ); - exit(1); - }); - gossip_addr.set_ip(ip_addr); - - ContactInfo::new_gossip_entry_point(&entrypoint_addr) - }); - - if matches.value_of("signer_addr").is_some() { - warn!("--vote-signer-address ignored"); - /* - let (_signer_service, _signer_addr) = if let Some(signer_addr) = matches.value_of("signer_addr") - { - ( - None, - signer_addr.to_string().parse().expect("Signer IP Address"), - ) - } else { - // Run a local vote signer if a vote signer service address was not provided - let (signer_service, signer_addr) = solana_core::local_vote_signer_service::LocalVoteSignerService::new(dynamic_port_range); - (Some(signer_service), signer_addr) - }; - */ - } - - let init_complete_file = matches.value_of("init_complete_file"); - let verify_ledger = !matches.is_present("skip_ledger_verify"); - validator_config.blockstream_unix_socket = matches - .value_of("blockstream_unix_socket") - .map(PathBuf::from); - - println!( - "{} version {} (branch={}, commit={})", - style(crate_name!()).bold(), - crate_version!(), - option_env!("CI_BRANCH").unwrap_or("unknown"), - option_env!("CI_COMMIT").unwrap_or("unknown") - ); - solana_metrics::set_host_id(keypair.pubkey().to_string()); - - let mut tcp_ports = vec![]; - 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"); - if port_number == 0 { - eprintln!("Invalid RPC port requested: {:?}", port); - exit(1); - } - node.info.rpc = SocketAddr::new(node.info.gossip.ip(), port_number); - node.info.rpc_pubsub = SocketAddr::new(node.info.gossip.ip(), port_number + 1); - tcp_ports = vec![port_number, port_number + 1]; - }; - - if let Some(ref cluster_entrypoint) = cluster_entrypoint { - let udp_sockets = [ - &node.sockets.gossip, - &node.sockets.broadcast, - &node.sockets.repair, - &node.sockets.retransmit, - ]; - - let mut tcp_listeners: Vec<(_, _)> = tcp_ports - .iter() - .map(|port| { - ( - *port, - TcpListener::bind(&SocketAddr::from(([0, 0, 0, 0], *port))).unwrap_or_else( - |err| { - error!("Unable to bind to tcp/{}: {}", port, err); - std::process::exit(1); - }, - ), - ) - }) - .collect(); - if let Some(ip_echo) = &node.sockets.ip_echo { - let ip_echo = ip_echo.try_clone().expect("unable to clone tcp_listener"); - tcp_listeners.push((node.info.gossip.port(), ip_echo)); - } - solana_netutil::verify_reachable_ports( - &cluster_entrypoint.gossip, - tcp_listeners, - &udp_sockets, - ); - - let expected_genesis_blockhash = initialize_ledger_path( - cluster_entrypoint, - &ledger_path, - matches.is_present("no_snapshot_fetch"), - ) - .unwrap_or_else(|err| { - eprintln!("Failed to download ledger: {}", err); - exit(1); - }); - validator_config.expected_genesis_blockhash = Some(expected_genesis_blockhash); - } 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 validator = Validator::new( - node, - &Arc::new(keypair), - &ledger_path, - &vote_account, - &Arc::new(voting_keypair), - &Arc::new(storage_keypair), - cluster_entrypoint.as_ref(), - verify_ledger, - &validator_config, - ); - - if let Some(filename) = init_complete_file { - File::create(filename).unwrap_or_else(|_| { - eprintln!("Unable to create: {}", filename); - exit(1); - }); - } - info!("Validator initialized"); - validator.join().expect("validator exit"); - info!("Validator exiting.."); + solana_validator::main() }