Add explicit validator-cuda crate (#5985)

This commit is contained in:
Michael Vines 2019-09-19 20:50:34 -07:00 committed by GitHub
parent d379786c90
commit 1d0be265d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 778 additions and 706 deletions

8
Cargo.lock generated
View File

@ -3755,6 +3755,14 @@ dependencies = [
"ureq 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "solana-vote-api" name = "solana-vote-api"
version = "0.19.0-pre0" version = "0.19.0-pre0"

View File

@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ # The members list excluding the `validator-cuda` crate
default-members = [
"bench-exchange", "bench-exchange",
"bench-streamer", "bench-streamer",
"bench-tps", "bench-tps",
@ -58,6 +59,69 @@ members = [
"cli", "cli",
"rayon-threadlimit", "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 = [ exclude = [
"programs/bpf", "programs/bpf",
] ]

View File

@ -17,7 +17,3 @@ solana-measure = { path = "../measure", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
rand = "0.6.5" rand = "0.6.5"
crossbeam-channel = "0.3" crossbeam-channel = "0.3"
[features]
cuda = []

View File

@ -37,7 +37,3 @@ solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
untrusted = "0.7.0" untrusted = "0.7.0"
ws = "0.9.0" ws = "0.9.0"
[features]
cuda = ["solana-core/cuda"]

View File

@ -12,7 +12,3 @@ clap = "2.33.0"
solana-core = { path = "../core", version = "0.19.0-pre0" } solana-core = { path = "../core", version = "0.19.0-pre0" }
solana-logger = { path = "../logger", version = "0.19.0-pre0" } solana-logger = { path = "../logger", version = "0.19.0-pre0" }
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" } solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
[features]
cuda = ["solana-core/cuda"]

View File

@ -33,7 +33,3 @@ solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.19
[dev-dependencies] [dev-dependencies]
serial_test = "0.2.0" serial_test = "0.2.0"
serial_test_derive = "0.2.0" serial_test_derive = "0.2.0"
[features]
cuda = ["solana-core/cuda"]

View File

@ -44,7 +44,7 @@ $ git checkout $TAG
Ensure important programs such as the vote program are built before any Ensure important programs such as the vote program are built before any
nodes are started nodes are started
```bash ```bash
$ cargo build --all $ cargo build
``` ```
The network is initialized with a genesis ledger generated by running the The network is initialized with a genesis ledger generated by running the

View File

@ -26,7 +26,7 @@ build() {
$genPipeline && return $genPipeline && return
source ci/rust-version.sh stable source ci/rust-version.sh stable
source scripts/ulimit-n.sh source scripts/ulimit-n.sh
_ cargo +$rust_stable build --all _ cargo +$rust_stable build
} }
runTest() { runTest() {

View File

@ -37,14 +37,14 @@ if [[ -z $CHANNEL_OR_TAG ]]; then
exit 1 exit 1
fi fi
PERF_LIBS=false maybeCUDA=
case "$CI_OS_NAME" in case "$CI_OS_NAME" in
osx) osx)
TARGET=x86_64-apple-darwin TARGET=x86_64-apple-darwin
;; ;;
linux) linux)
TARGET=x86_64-unknown-linux-gnu TARGET=x86_64-unknown-linux-gnu
PERF_LIBS=true maybeCUDA=cuda
;; ;;
windows) windows)
TARGET=x86_64-pc-windows-msvc TARGET=x86_64-pc-windows-msvc
@ -70,27 +70,18 @@ echo --- Creating tarball
) > solana-release/version.yml ) > solana-release/version.yml
source ci/rust-version.sh stable 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 # https://github.com/appveyor/ci/issues/2997 is fixed
if [[ -n $APPVEYOR ]]; then if [[ -n $APPVEYOR ]]; then
rm -f solana-release/bin/solana-validator.exe solana-release/bin/solana-bench-exchange.exe rm -f solana-release/bin/solana-validator.exe solana-release/bin/solana-bench-exchange.exe
fi fi
if $PERF_LIBS; then if $maybeCUDA; then
rm -rf target/perf-libs # Wrap `solana-validator-cuda` with a script that loads perf-libs
./fetch-perf-libs.sh # automatically
mkdir solana-release/target
cp -a target/perf-libs solana-release/target/ 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 mkdir solana-release/.bin
cp solana-release-cuda/bin/solana-validator solana-release/.bin/solana-validator-cuda cp solana-release-cuda/bin/solana-validator solana-release/.bin/solana-validator-cuda
cat > solana-release/bin/solana-validator-cuda <<'EOF' cat > solana-release/bin/solana-validator-cuda <<'EOF'
@ -105,9 +96,10 @@ if [[ -z $SOLANA_PERF_LIBS_CUDA ]]; then
fi fi
exec .bin/solana-validator-cuda "$@" exec .bin/solana-validator-cuda "$@"
EOF EOF
chmod +x solana-release/bin/solana-validator-cuda chmod +x solana-release/bin/solana-validator-cuda
fi fi
# TODO: Remove scripts/ and multinode/... from tarball
cp -a scripts multinode-demo solana-release/ cp -a scripts multinode-demo solana-release/
# Add a wrapper script for validator.sh # Add a wrapper script for validator.sh

View File

@ -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 test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Ensure all dependencies are built # 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 # Remove "BENCH_FILE", if it exists so that the following commands can append
rm -f "$BENCH_FILE" rm -f "$BENCH_FILE"

View File

@ -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. # 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 # See https://github.com/solana-labs/solana/issues/5503
_ cargo +"$rust_stable" clippy --version _ 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" clippy --manifest-path sdk-c/Cargo.toml -- --deny=warnings
_ cargo +"$rust_stable" audit --version _ cargo +"$rust_stable" audit --version

View File

@ -32,8 +32,8 @@ case $testName in
test-stable) test-stable)
echo "Executing $testName" echo "Executing $testName"
_ cargo +"$rust_stable" build --all --tests --bins ${V:+--verbose} _ cargo +"$rust_stable" build --tests --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --all --exclude solana-local-cluster ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture _ cargo +"$rust_stable" test --all --exclude solana-local-cluster --exclude solana-validator-cuda ${V:+--verbose} -- --nocapture
;; ;;
test-stable-perf) test-stable-perf)
echo "Executing $testName" echo "Executing $testName"
@ -62,7 +62,7 @@ test-stable-perf)
--no-default-features --features=bpf_c,bpf_rust --no-default-features --features=bpf_c,bpf_rust
# Run root package tests with these features # Run root package tests with these features
ROOT_FEATURES= maybeCuda=
if [[ $(uname) = Linux ]]; then if [[ $(uname) = Linux ]]; then
# Enable persistence mode to keep the CUDA kernel driver loaded, avoiding a # 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 # lengthy and unexpected delay the first time CUDA is involved when the driver
@ -73,17 +73,17 @@ test-stable-perf)
./fetch-perf-libs.sh ./fetch-perf-libs.sh
# shellcheck source=/dev/null # shellcheck source=/dev/null
source ./target/perf-libs/env.sh source ./target/perf-libs/env.sh
ROOT_FEATURES=cuda maybeCuda=--features=cuda
export SOLANA_CUDA=1 export SOLANA_CUDA=1
fi fi
# Run root package library tests # Run root package library tests
_ cargo +"$rust_stable" build --all --tests --bins ${V:+--verbose} --features="$ROOT_FEATURES" _ cargo +"$rust_stable" build --tests --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture _ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} $maybeCuda -- --nocapture
;; ;;
test-local-cluster) test-local-cluster)
echo "Executing $testName" 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 _ cargo +"$rust_stable" test --release --manifest-path=local_cluster/Cargo.toml ${V:+--verbose} -- --nocapture
exit 0 exit 0
;; ;;

View File

@ -44,8 +44,7 @@ url = "2.1.0"
solana-core = { path = "../core", version = "0.19.0-pre0" } solana-core = { path = "../core", version = "0.19.0-pre0" }
solana-budget-program = { path = "../programs/budget_program", version = "0.19.0-pre0" } solana-budget-program = { path = "../programs/budget_program", version = "0.19.0-pre0" }
[features]
cuda = []
[[bin]] [[bin]]
name = "solana" name = "solana"

View File

@ -1,11 +1,16 @@
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::process::exit;
fn main() { fn main() {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
if env::var("CARGO_FEATURE_CUDA").is_ok() { 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"); println!("cargo:rustc-cfg=cuda");
let perf_libs_dir = { let perf_libs_dir = {
@ -13,10 +18,10 @@ fn main() {
let mut path = Path::new(&manifest_dir); let mut path = Path::new(&manifest_dir);
path = path.parent().unwrap(); path = path.parent().unwrap();
let mut path = path.join(Path::new("target/perf-libs")); let mut path = path.join(Path::new("target/perf-libs"));
path.push( path.push(env::var("SOLANA_PERF_LIBS_CUDA").unwrap_or_else(|err| {
env::var("SOLANA_PERF_LIBS_CUDA") eprintln!("Error: SOLANA_PERF_LIBS_CUDA not defined: {}", err);
.unwrap_or_else(|err| panic!("SOLANA_PERF_LIBS_CUDA not defined: {}", err)), exit(1);
); }));
path path
}; };
let perf_libs_dir = perf_libs_dir.to_str().unwrap(); let perf_libs_dir = perf_libs_dir.to_str().unwrap();

View File

@ -8,8 +8,7 @@ license = "Apache-2.0"
homepage = "https://solana.com/" homepage = "https://solana.com/"
edition = "2018" edition = "2018"
[features]
cuda = []
[dependencies] [dependencies]

View File

@ -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-storage-api = { path = "../programs/storage_api", version = "0.19.0-pre0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.19.0-pre0" } solana-vote-api = { path = "../programs/vote_api", version = "0.19.0-pre0" }
tempfile = "3.1.0" tempfile = "3.1.0"
[features]
cuda = ["solana-core/cuda"]

View File

@ -16,6 +16,5 @@ solana-logger = { path = "../logger", version = "0.19.0-pre0" }
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" } solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
[features]
cuda = []

View File

@ -8,8 +8,7 @@ repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0" license = "Apache-2.0"
homepage = "https://solana.com/" homepage = "https://solana.com/"
[features]
cuda = []
[dependencies] [dependencies]

View File

@ -8,8 +8,7 @@ license = "Apache-2.0"
homepage = "https://solana.com/" homepage = "https://solana.com/"
edition = "2018" edition = "2018"
[features]
cuda = []
[dependencies] [dependencies]

View File

@ -22,7 +22,3 @@ solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
[dev-dependencies] [dev-dependencies]
assert_cmd = "0.11" assert_cmd = "0.11"
[features]
cuda = []

View File

@ -35,12 +35,6 @@ if [[ -n $USE_INSTALL || ! -f "$SOLANA_ROOT"/Cargo.toml ]]; then
else else
solana_program() { solana_program() {
declare program="$1" declare program="$1"
declare features="--features="
if [[ "$program" =~ ^(.*)-cuda$ ]]; then
program=${BASH_REMATCH[1]}
features+="cuda"
fi
declare crate="$program" declare crate="$program"
if [[ -z $program ]]; then if [[ -z $program ]]; then
crate="cli" crate="cli"
@ -56,7 +50,7 @@ else
maybe_release=--release maybe_release=--release
fi fi
declare manifest_path="--manifest-path=$SOLANA_ROOT/$crate/Cargo.toml" 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 fi

View File

@ -16,5 +16,3 @@ solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" } solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" } solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
[features]
cuda = ["solana-core/cuda"]

4
run.sh
View File

@ -3,11 +3,11 @@
# Run a minimal Solana cluster. Ctrl-C to exit. # Run a minimal Solana cluster. Ctrl-C to exit.
# #
# Before running this script ensure standard Solana programs are available # 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 set -e
# Prefer possible `cargo build --all` binaries over PATH binaries # Prefer possible `cargo build` binaries over PATH binaries
cd "$(dirname "$0")/" cd "$(dirname "$0")/"
PATH=$PWD/target/debug:$PATH PATH=$PWD/target/debug:$PATH

View File

@ -20,6 +20,11 @@ cargo=cargo
cargoFeatures="$2" cargoFeatures="$2"
debugBuild="$3" debugBuild="$3"
if [[ -n $cargoFeatures && $cargoFeatures != cuda ]]; then
echo "Unsupported feature flag: $cargoFeatures"
exit 1
fi
buildVariant=release buildVariant=release
maybeReleaseFlag=--release maybeReleaseFlag=--release
if [[ -n "$debugBuild" ]]; then if [[ -n "$debugBuild" ]]; then
@ -36,10 +41,13 @@ SECONDS=0
( (
set -x set -x
# shellcheck disable=SC2086 # Don't want to double quote $rust_version # 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=( BINS=(
solana
solana-bench-exchange
solana-bench-tps
solana-drone solana-drone
solana-gossip solana-gossip
solana-install solana-install
@ -48,9 +56,6 @@ BINS=(
solana-ledger-tool solana-ledger-tool
solana-replicator solana-replicator
solana-validator solana-validator
solana
solana-bench-exchange
solana-bench-tps
) )
#XXX: Ensure `solana-genesis` is built LAST! #XXX: Ensure `solana-genesis` is built LAST!
@ -65,7 +70,7 @@ done
( (
set -x set -x
# shellcheck disable=SC2086 # Don't want to double quote $rust_version # 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" mkdir -p "$installDir/bin"
@ -73,6 +78,22 @@ for bin in "${BINS[@]}"; do
cp -fv "target/$buildVariant/$bin" "$installDir"/bin cp -fv "target/$buildVariant/$bin" "$installDir"/bin
done 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 dir in programs/*; do
for program in echo target/$buildVariant/deps/libsolana_"$(basename "$dir")".{so,dylib,dll}; do for program in echo target/$buildVariant/deps/libsolana_"$(basename "$dir")".{so,dylib,dll}; do
if [[ -f $program ]]; then if [[ -f $program ]]; then

View File

@ -13,7 +13,7 @@ programDir="$2"
( (
set -x set -x
cd "$programDir" cd "$programDir"
cargo build --all --release cargo build --release
) )
for dir in "$programDir"/*; do for dir in "$programDir"/*; do

View File

@ -15,7 +15,7 @@ reportName="lcov-${CI_COMMIT:0:9}"
if [[ -n $1 ]]; then if [[ -n $1 ]]; then
crate="--manifest-path=$1/Cargo.toml" crate="--manifest-path=$1/Cargo.toml"
else else
crate="--all --exclude solana-local-cluster" crate="--all --exclude solana-local-cluster --exclude solana-validator-cuda"
fi fi
coverageFlags=(-Zprofile) # Enable coverage coverageFlags=(-Zprofile) # Enable coverage

2
validator-cuda/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
/farf/

14
validator-cuda/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
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" }

View File

@ -0,0 +1,3 @@
fn main() {
solana_validator::main()
}

View File

@ -28,6 +28,3 @@ solana-vote-signer = { path = "../vote-signer", version = "0.19.0-pre0" }
tempfile = "3.1.0" tempfile = "3.1.0"
tar = "0.4.26" tar = "0.4.26"
ureq = { version = "0.11.1", default-features = false } ureq = { version = "0.11.1", default-features = false }
[features]
cuda = ["solana-core/cuda"]

620
validator/src/lib.rs Normal file
View File

@ -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<R> {
progress_bar: ProgressBar,
response: R,
}
impl<R: Read> Read for DownloadProgress<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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<Hash, String> {
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..");
}

View File

@ -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<R> {
progress_bar: ProgressBar,
response: R,
}
impl<R: Read> Read for DownloadProgress<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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<Hash, String> {
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() { fn main() {
solana_logger::setup_with_filter("solana=info"); solana_validator::main()
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..");
} }