diff --git a/Cargo.lock b/Cargo.lock index c79bdedce9..b8bcdcdd9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2483,6 +2483,7 @@ dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "solana 0.14.0", + "solana-client 0.14.0", "solana-netutil 0.14.0", "solana-sdk 0.14.0", ] diff --git a/book/src/testnet-participation.md b/book/src/testnet-participation.md index 577b8b67b6..f16dcd7163 100644 --- a/book/src/testnet-participation.md +++ b/book/src/testnet-participation.md @@ -41,7 +41,7 @@ Inspect the blockexplorer at [http://beta.testnet.solana.com/](http://beta.testn Run the following command to join the gossip network and view all the other nodes in the cluster: ```bash -$ solana-gossip --network beta.testnet.solana.com:8001 +$ solana-gossip --network beta.testnet.solana.com:8001 spy ``` View the [metrics dashboard]( diff --git a/ci/localnet-sanity.sh b/ci/localnet-sanity.sh index 2d3e7bdf00..a88ee26ad2 100755 --- a/ci/localnet-sanity.sh +++ b/ci/localnet-sanity.sh @@ -306,8 +306,7 @@ while [[ $iteration -le $iterations ]]; do set -x client_id=/tmp/client-id.json-$$ $solana_keygen -o $client_id || exit $? - $solana_gossip \ - --num-nodes-exactly $numNodes || exit $? + $solana_gossip spy --num-nodes-exactly $numNodes || exit $? rm -rf $client_id ) || flag_error diff --git a/gossip/Cargo.toml b/gossip/Cargo.toml index f13b5fbe2b..f2e53dbb8d 100644 --- a/gossip/Cargo.toml +++ b/gossip/Cargo.toml @@ -12,6 +12,7 @@ homepage = "https://solana.com/" clap = "2.33.0" env_logger = "0.6.1" solana = { path = "../core", version = "0.14.0" } +solana-client = { path = "../client", version = "0.14.0" } solana-netutil = { path = "../netutil", version = "0.14.0" } solana-sdk = { path = "../sdk", version = "0.14.0" } diff --git a/gossip/src/main.rs b/gossip/src/main.rs index 6882018f07..f3c9f52c8d 100644 --- a/gossip/src/main.rs +++ b/gossip/src/main.rs @@ -1,7 +1,9 @@ //! A command-line executable for monitoring a cluster's gossip plane. -use clap::{crate_description, crate_name, crate_version, App, Arg}; +use clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg, SubCommand}; +use solana::contact_info::ContactInfo; use solana::gossip_service::discover; +use solana_client::rpc_client::RpcClient; use solana_sdk::pubkey::Pubkey; use std::error; use std::net::SocketAddr; @@ -22,6 +24,7 @@ fn main() -> Result<(), Box> { let matches = App::new(crate_name!()) .about(crate_description!()) .version(crate_version!()) + .setting(AppSettings::SubcommandRequiredElseHelp) .arg( Arg::with_name("network") .short("n") @@ -31,38 +34,58 @@ fn main() -> Result<(), Box> { .default_value(&network_string) .help("Rendezvous with the cluster at this gossip entry point"), ) - .arg( - Arg::with_name("num_nodes") - .short("N") - .long("num-nodes") - .value_name("NUM") - .takes_value(true) - .conflicts_with("num_nodes_exactly") - .help("Wait for at least NUM nodes to converge"), + .subcommand( + SubCommand::with_name("spy") + .about("Monitor the gossip network") + .setting(AppSettings::DisableVersion) + .arg( + Arg::with_name("num_nodes") + .short("N") + .long("num-nodes") + .value_name("NUM") + .takes_value(true) + .conflicts_with("num_nodes_exactly") + .help("Wait for at least NUM nodes to converge"), + ) + .arg( + Arg::with_name("num_nodes_exactly") + .short("E") + .long("num-nodes-exactly") + .value_name("NUM") + .takes_value(true) + .help("Wait for exactly NUM nodes to converge"), + ) + .arg( + Arg::with_name("node_pubkey") + .short("p") + .long("pubkey") + .value_name("PUBKEY") + .takes_value(true) + .validator(pubkey_validator) + .help("Public key of a specific node to wait for"), + ) + .arg( + Arg::with_name("timeout") + .long("timeout") + .value_name("SECS") + .takes_value(true) + .help( + "Maximum time to wait for cluster to converge [default: wait forever]", + ), + ), ) - .arg( - Arg::with_name("num_nodes_exactly") - .short("E") - .long("num-nodes-exactly") - .value_name("NUM") - .takes_value(true) - .help("Wait for exactly NUM nodes to converge"), - ) - .arg( - Arg::with_name("node_pubkey") - .short("p") - .long("pubkey") - .value_name("PUBKEY") - .takes_value(true) - .validator(pubkey_validator) - .help("Public key of a specific node to wait for"), - ) - .arg( - Arg::with_name("timeout") - .long("timeout") - .value_name("SECS") - .takes_value(true) - .help("Maximum time to wait for cluster to converge [default: wait forever]"), + .subcommand( + SubCommand::with_name("stop") + .about("Send stop request to a node") + .setting(AppSettings::DisableVersion) + .arg( + Arg::with_name("node_pubkey") + .index(1) + .required(true) + .value_name("PUBKEY") + .validator(pubkey_validator) + .help("Public key of a specific node to stop"), + ), ) .get_matches(); @@ -72,48 +95,74 @@ fn main() -> Result<(), Box> { exit(1) }); } + match matches.subcommand() { + ("spy", Some(matches)) => { + let num_nodes_exactly = matches + .value_of("num_nodes_exactly") + .map(|num| num.to_string().parse().unwrap()); + let num_nodes = matches + .value_of("num_nodes") + .map(|num| num.to_string().parse().unwrap()) + .or(num_nodes_exactly); + let timeout = matches + .value_of("timeout") + .map(|secs| secs.to_string().parse().unwrap()); + let pubkey = matches + .value_of("node_pubkey") + .map(|pubkey_str| pubkey_str.parse::().unwrap()); - let num_nodes_exactly = matches - .value_of("num_nodes_exactly") - .map(|num| num.to_string().parse().unwrap()); - let num_nodes = matches - .value_of("num_nodes") - .map(|num| num.to_string().parse().unwrap()) - .or(num_nodes_exactly); - let timeout = matches - .value_of("timeout") - .map(|secs| secs.to_string().parse().unwrap()); - let pubkey = matches - .value_of("node_pubkey") - .map(|pubkey_str| pubkey_str.parse::().unwrap()); + let nodes = discover(&network_addr, num_nodes, timeout, pubkey)?; - let nodes = discover(&network_addr, num_nodes, timeout, pubkey)?; - - if timeout.is_some() { - if let Some(num) = num_nodes { - if nodes.len() < num { - let add = if num_nodes_exactly.is_some() { - "" - } else { - " or more" - }; + if timeout.is_some() { + if let Some(num) = num_nodes { + if nodes.len() < num { + let add = if num_nodes_exactly.is_some() { + "" + } else { + " or more" + }; + eprintln!( + "Error: Insufficient nodes discovered. Expecting {}{}", + num, add, + ); + } + } + if let Some(node) = pubkey { + if nodes.iter().find(|x| x.id == node).is_none() { + eprintln!("Error: Could not find node {:?}", node); + } + } + } + if num_nodes_exactly.is_some() && nodes.len() > num_nodes_exactly.unwrap() { eprintln!( - "Error: Insufficient nodes discovered. Expecting {}{}", - num, add, + "Error: Extra nodes discovered. Expecting exactly {}", + num_nodes_exactly.unwrap() ); } } - if let Some(node) = pubkey { - if nodes.iter().find(|x| x.id == node).is_none() { - eprintln!("Error: Could not find node {:?}", node); + ("stop", Some(matches)) => { + let pubkey = matches + .value_of("node_pubkey") + .unwrap() + .parse::() + .unwrap(); + let nodes = discover(&network_addr, None, None, Some(pubkey))?; + let node = nodes.iter().find(|x| x.id == pubkey).unwrap(); + + if !ContactInfo::is_valid_address(&node.rpc) { + eprintln!("Error: RPC service is not enabled on node {:?}", pubkey); + } + println!("\nSending stop request to node {:?}", pubkey); + + let result = RpcClient::new_socket(node.rpc).fullnode_exit()?; + if result { + println!("Stop signal accepted"); + } else { + eprintln!("Error: Stop signal ignored"); } } + _ => unreachable!(), } - if num_nodes_exactly.is_some() && nodes.len() > num_nodes_exactly.unwrap() { - eprintln!( - "Error: Extra nodes discovered. Expecting exactly {}", - num_nodes_exactly.unwrap() - ); - } + Ok(()) } diff --git a/net/remote/remote-node.sh b/net/remote/remote-node.sh index 11a2779dc8..689f20df48 100755 --- a/net/remote/remote-node.sh +++ b/net/remote/remote-node.sh @@ -81,12 +81,13 @@ local|tar) fi ./multinode-demo/drone.sh > drone.log 2>&1 & - maybePublicAddress= + args=() if $publicNetwork; then - maybePublicAddress="--public-address" + args+=(--public-address) fi + args+=(--enable-rpc-exit) - ./multinode-demo/bootstrap-leader.sh $maybePublicAddress > bootstrap-leader.log 2>&1 & + ./multinode-demo/bootstrap-leader.sh "${args[@]}" > bootstrap-leader.log 2>&1 & ln -sTf bootstrap-leader.log fullnode.log ;; fullnode|blockstreamer) @@ -116,6 +117,7 @@ local|tar) fi args+=( + --enable-rpc-exit --gossip-port 8001 --rpc-port 8899 ) @@ -147,7 +149,9 @@ local|tar) # Confirm the blockexplorer is now globally accessible curl --head "$(curl ifconfig.io)" fi - ./multinode-demo/fullnode.sh "${args[@]}" "$entrypointIp":~/solana "$entrypointIp:8001" > fullnode.log 2>&1 & + + args+=("$entrypointIp":~/solana "$entrypointIp:8001") + ./multinode-demo/fullnode.sh "${args[@]}" > fullnode.log 2>&1 & ;; *) echo "Error: unknown node type: $nodeType" diff --git a/net/remote/remote-sanity.sh b/net/remote/remote-sanity.sh index d029a4d393..132aa01e54 100755 --- a/net/remote/remote-sanity.sh +++ b/net/remote/remote-sanity.sh @@ -89,9 +89,8 @@ echo "+++ $entrypointIp: node count ($numNodes expected)" nodeArg="num-nodes-exactly" fi - timeout 2m $solana_gossip \ - --network "$entrypointIp:8001" \ - --$nodeArg "$numNodes" \ + timeout 2m $solana_gossip --network "$entrypointIp:8001" \ + spy --$nodeArg "$numNodes" \ ) echo "--- RPC API: getTransactionCount"