From db291234ed21e5d05739db120ec439ee7d2f01cf Mon Sep 17 00:00:00 2001 From: Sunny Gleason Date: Sat, 29 Feb 2020 11:39:07 -0500 Subject: [PATCH] feat: implement websocket_url as a get/set-able global parameter w/ value computation --- cli-config/src/config.rs | 4 +- cli/src/cli.rs | 105 ++++++++++++++++++++++++++++++++++++--- cli/src/cluster_query.rs | 36 ++++++-------- cli/src/display.rs | 23 +++++---- cli/src/main.rs | 102 +++++++++++++++++++++++-------------- 5 files changed, 193 insertions(+), 77 deletions(-) diff --git a/cli-config/src/config.rs b/cli-config/src/config.rs index 13c14de1f6..0f0eb82db5 100644 --- a/cli-config/src/config.rs +++ b/cli-config/src/config.rs @@ -18,13 +18,15 @@ lazy_static! { #[derive(Serialize, Deserialize, Default, Debug, PartialEq)] pub struct Config { pub url: String, + pub websocket_url: String, pub keypair_path: String, } impl Config { - pub fn new(url: &str, keypair_path: &str) -> Self { + pub fn new(url: &str, websocket_url: &str, keypair_path: &str) -> Self { Self { url: url.to_string(), + websocket_url: websocket_url.to_string(), keypair_path: keypair_path.to_string(), } } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index d3ae64b31e..b822880c7c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -52,6 +52,7 @@ use std::{ time::Duration, {error, fmt}, }; +use url::Url; pub type CliSigners = Vec>; pub type SignerIndex = usize; @@ -185,9 +186,7 @@ pub enum CliCommand { commitment_config: CommitmentConfig, }, LeaderSchedule, - LiveSlots { - url: String, - }, + LiveSlots, Ping { lamports: u64, interval: Duration, @@ -435,9 +434,16 @@ impl From> for CliError { } } +pub enum SettingType { + Explicit, + Computed, + SystemDefault, +} + pub struct CliConfig<'a> { pub command: CliCommand, pub json_rpc_url: String, + pub websocket_url: String, pub signers: Vec<&'a dyn Signer>, pub keypair_path: String, pub derivation_path: Option, @@ -446,16 +452,97 @@ pub struct CliConfig<'a> { } impl CliConfig<'_> { - pub fn default_keypair_path() -> String { + fn default_keypair_path() -> String { let mut keypair_path = dirs::home_dir().expect("home directory"); keypair_path.extend(&[".config", "solana", "id.json"]); keypair_path.to_str().unwrap().to_string() } - pub fn default_json_rpc_url() -> String { + fn default_json_rpc_url() -> String { "http://127.0.0.1:8899".to_string() } + fn default_websocket_url() -> String { + Self::compute_ws_url(&Self::default_json_rpc_url()) + } + + fn compute_ws_url(rpc_url: &str) -> String { + let rpc_url: Option = rpc_url.parse().ok(); + if rpc_url.is_none() { + return "".to_string(); + } + let rpc_url = rpc_url.unwrap(); + let is_secure = rpc_url.scheme().to_ascii_lowercase() == "https"; + let mut ws_url = rpc_url.clone(); + ws_url + .set_scheme(if is_secure { "wss" } else { "ws" }) + .expect("unable to set scheme"); + let ws_port = match rpc_url.port() { + Some(port) => port + 1, + None => { + if is_secure { + 8901 + } else { + 8900 + } + } + }; + ws_url.set_port(Some(ws_port)).expect("unable to set port"); + ws_url.to_string() + } + + fn first_nonempty_setting( + settings: std::vec::Vec<(SettingType, String)>, + ) -> (SettingType, String) { + settings + .into_iter() + .find(|(_, value)| value != "") + .expect("no nonempty setting") + } + + pub fn compute_websocket_url_setting( + websocket_cmd_url: &str, + websocket_cfg_url: &str, + json_rpc_cmd_url: &str, + json_rpc_cfg_url: &str, + ) -> (SettingType, String) { + Self::first_nonempty_setting(vec![ + (SettingType::Explicit, websocket_cmd_url.to_string()), + (SettingType::Explicit, websocket_cfg_url.to_string()), + ( + SettingType::Computed, + Self::compute_ws_url(json_rpc_cmd_url), + ), + ( + SettingType::Computed, + Self::compute_ws_url(json_rpc_cfg_url), + ), + (SettingType::SystemDefault, Self::default_websocket_url()), + ]) + } + + pub fn compute_json_rpc_url_setting( + json_rpc_cmd_url: &str, + json_rpc_cfg_url: &str, + ) -> (SettingType, String) { + Self::first_nonempty_setting(vec![ + (SettingType::Explicit, json_rpc_cmd_url.to_string()), + (SettingType::Explicit, json_rpc_cfg_url.to_string()), + (SettingType::SystemDefault, Self::default_json_rpc_url()), + ]) + } + + pub fn compute_keypair_path_setting( + keypair_cmd_path: &str, + keypair_cfg_path: &str, + ) -> (SettingType, String) { + Self::first_nonempty_setting(vec![ + (SettingType::Explicit, keypair_cmd_path.to_string()), + (SettingType::Explicit, keypair_cfg_path.to_string()), + (SettingType::SystemDefault, Self::default_keypair_path()), + ]) + } + pub(crate) fn pubkey(&self) -> Result { if !self.signers.is_empty() { self.signers[0].try_pubkey() @@ -475,6 +562,7 @@ impl Default for CliConfig<'_> { use_lamports_unit: false, }, json_rpc_url: Self::default_json_rpc_url(), + websocket_url: Self::default_websocket_url(), signers: Vec::new(), keypair_path: Self::default_keypair_path(), derivation_path: None, @@ -514,7 +602,10 @@ pub fn parse_command( signers: vec![], }), ("ping", Some(matches)) => parse_cluster_ping(matches, default_signer_path, wallet_manager), - ("live-slots", Some(matches)) => parse_live_slots(matches), + ("live-slots", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::LiveSlots, + signers: vec![], + }), ("block-production", Some(matches)) => parse_show_block_production(matches), ("gossip", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::ShowGossip, @@ -1474,7 +1565,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { process_get_transaction_count(&rpc_client, commitment_config) } CliCommand::LeaderSchedule => process_leader_schedule(&rpc_client), - CliCommand::LiveSlots { url } => process_live_slots(&url), + CliCommand::LiveSlots => process_live_slots(&config.websocket_url), CliCommand::Ping { lamports, interval, diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index fd4dfdb077..743978feea 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -170,16 +170,7 @@ impl ClusterQuerySubCommands for App<'_, '_> { ) .subcommand( SubCommand::with_name("live-slots") - .about("Show information about the current slot progression") - .arg( - Arg::with_name("websocket_url") - .short("w") - .long("ws") - .value_name("URL") - .takes_value(true) - .default_value("ws://127.0.0.1:8900") - .help("WebSocket URL for PubSub RPC connection"), - ), + .about("Show information about the current slot progression"), ) .subcommand( SubCommand::with_name("block-production") @@ -279,14 +270,6 @@ pub fn parse_cluster_ping( }) } -pub fn parse_live_slots(matches: &ArgMatches<'_>) -> Result { - let url: String = value_t_or_exit!(matches, "websocket_url", String); - Ok(CliCommandInfo { - command: CliCommand::LiveSlots { url }, - signers: vec![], - }) -} - pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result { let slot = value_t_or_exit!(matches, "slot", u64); Ok(CliCommandInfo { @@ -873,6 +856,9 @@ pub fn process_live_slots(url: &str) -> ProcessResult { let (mut client, receiver) = PubsubClient::slot_subscribe(url)?; slot_progress.set_message("Connected."); + let spacer = "|"; + slot_progress.println(spacer); + let mut last_root = std::u64::MAX; let mut last_root_update = Instant::now(); let mut slots_per_second = std::f64::NAN; @@ -918,11 +904,19 @@ pub fn process_live_slots(url: &str) -> ProcessResult { // if slot_delta != root_delta { let prev_root = format!( - "|<- {} <- … <- {} <- {}", + "|<--- {} <- … <- {} <- {} (prev)", previous.root, previous.parent, previous.slot - ) - .to_owned(); + ); slot_progress.println(&prev_root); + + let new_root = format!( + "| '- {} <- … <- {} <- {} (next)", + new_info.root, new_info.parent, new_info.slot + ); + + slot_progress.println(prev_root); + slot_progress.println(new_root); + slot_progress.println(spacer); } } current = Some(new_info); diff --git a/cli/src/display.rs b/cli/src/display.rs index b31d07601c..f8cf1b4690 100644 --- a/cli/src/display.rs +++ b/cli/src/display.rs @@ -1,3 +1,4 @@ +use crate::cli::SettingType; use console::style; use solana_sdk::transaction::Transaction; @@ -11,17 +12,19 @@ pub fn println_name_value(name: &str, value: &str) { println!("{} {}", style(name).bold(), styled_value); } -pub fn println_name_value_or(name: &str, value: &str, default_value: &str) { - if value == "" { - println!( - "{} {} {}", - style(name).bold(), - style(default_value), - style("(default)").italic() - ); - } else { - println!("{} {}", style(name).bold(), style(value)); +pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) { + let description = match setting_type { + SettingType::Explicit => "", + SettingType::Computed => "(computed)", + SettingType::SystemDefault => "(default)", }; + + println!( + "{} {} {}", + style(name).bold(), + style(value), + style(description).italic(), + ); } pub fn println_signers(tx: &Transaction) { diff --git a/cli/src/main.rs b/cli/src/main.rs index 2131b38f3a..2da22d2ecf 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -20,29 +20,31 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result { if let Some(config_file) = matches.value_of("config_file") { let config = Config::load(config_file).unwrap_or_default(); + + let (url_setting_type, json_rpc_url) = + CliConfig::compute_json_rpc_url_setting("", &config.url); + let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting( + "", + &config.websocket_url, + "", + &config.url, + ); + let (keypair_setting_type, keypair_path) = + CliConfig::compute_keypair_path_setting("", &config.keypair_path); + if let Some(field) = subcommand_matches.value_of("specific_setting") { - let (field_name, value, default_value) = match field { - "url" => ("RPC URL", config.url, CliConfig::default_json_rpc_url()), - "keypair" => ( - "Key Path", - config.keypair_path, - CliConfig::default_keypair_path(), - ), + let (field_name, value, setting_type) = match field { + "json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type), + "websocket_url" => ("WS URL", websocket_url, ws_setting_type), + "keypair" => ("Key Path", keypair_path, keypair_setting_type), _ => unreachable!(), }; - println_name_value_or(&format!("{}:", field_name), &value, &default_value); + println_name_value_or(&format!("{}:", field_name), &value, setting_type); } else { println_name_value("Config File:", config_file); - println_name_value_or( - "RPC URL:", - &config.url, - &CliConfig::default_json_rpc_url(), - ); - println_name_value_or( - "Keypair Path:", - &config.keypair_path, - &CliConfig::default_keypair_path(), - ); + println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type); + println_name_value_or("WS URL:", &websocket_url, ws_setting_type); + println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type); } } else { println!( @@ -58,13 +60,29 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result( } else { Config::default() }; - let json_rpc_url = if let Some(url) = matches.value_of("json_rpc_url") { - url.to_string() - } else if config.url != "" { - config.url - } else { - let default = CliConfig::default(); - default.json_rpc_url - }; - - let default_signer_path = if matches.is_present("keypair") { - matches.value_of("keypair").unwrap().to_string() - } else if config.keypair_path != "" { - config.keypair_path - } else { - CliConfig::default_keypair_path() - }; + let (_, json_rpc_url) = CliConfig::compute_json_rpc_url_setting( + matches.value_of("json_rpc_url").unwrap_or(""), + &config.url, + ); + let (_, websocket_url) = CliConfig::compute_websocket_url_setting( + matches.value_of("websocket_url").unwrap_or(""), + &config.websocket_url, + matches.value_of("json_rpc_url").unwrap_or(""), + &config.url, + ); + let (_, default_signer_path) = CliConfig::compute_keypair_path_setting( + matches.value_of("keypair").unwrap_or(""), + &config.keypair_path, + ); let CliCommandInfo { command, signers } = parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?; @@ -113,6 +129,7 @@ pub fn parse_args<'a>( CliConfig { command, json_rpc_url, + websocket_url, signers: vec![], keypair_path: default_signer_path, derivation_path: derivation_of(matches, "derivation_path"), @@ -154,6 +171,15 @@ fn main() -> Result<(), Box> { .validator(is_url) .help("JSON RPC URL for the solana cluster"), ) + .arg( + Arg::with_name("websocket_url") + .long("ws") + .value_name("URL") + .takes_value(true) + .global(true) + .validator(is_url) + .help("WebSocket URL for the solana cluster"), + ) .arg( Arg::with_name("keypair") .short("k") @@ -198,7 +224,7 @@ fn main() -> Result<(), Box> { .index(1) .value_name("CONFIG_FIELD") .takes_value(true) - .possible_values(&["url", "keypair"]) + .possible_values(&["json_rpc_url", "websocket_url", "keypair"]) .help("Return a specific config setting"), ), ) @@ -207,7 +233,7 @@ fn main() -> Result<(), Box> { .about("Set a config setting") .group( ArgGroup::with_name("config_settings") - .args(&["json_rpc_url", "keypair"]) + .args(&["json_rpc_url", "websocket_url", "keypair"]) .multiple(true) .required(true), ),