feat: implement websocket_url as a get/set-able global parameter w/ value computation

This commit is contained in:
Sunny Gleason
2020-02-29 11:39:07 -05:00
committed by Michael Vines
parent 2a5605db24
commit db291234ed
5 changed files with 193 additions and 77 deletions

View File

@ -18,13 +18,15 @@ lazy_static! {
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)] #[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct Config { pub struct Config {
pub url: String, pub url: String,
pub websocket_url: String,
pub keypair_path: String, pub keypair_path: String,
} }
impl Config { impl Config {
pub fn new(url: &str, keypair_path: &str) -> Self { pub fn new(url: &str, websocket_url: &str, keypair_path: &str) -> Self {
Self { Self {
url: url.to_string(), url: url.to_string(),
websocket_url: websocket_url.to_string(),
keypair_path: keypair_path.to_string(), keypair_path: keypair_path.to_string(),
} }
} }

View File

@ -52,6 +52,7 @@ use std::{
time::Duration, time::Duration,
{error, fmt}, {error, fmt},
}; };
use url::Url;
pub type CliSigners = Vec<Box<dyn Signer>>; pub type CliSigners = Vec<Box<dyn Signer>>;
pub type SignerIndex = usize; pub type SignerIndex = usize;
@ -185,9 +186,7 @@ pub enum CliCommand {
commitment_config: CommitmentConfig, commitment_config: CommitmentConfig,
}, },
LeaderSchedule, LeaderSchedule,
LiveSlots { LiveSlots,
url: String,
},
Ping { Ping {
lamports: u64, lamports: u64,
interval: Duration, interval: Duration,
@ -435,9 +434,16 @@ impl From<Box<dyn error::Error>> for CliError {
} }
} }
pub enum SettingType {
Explicit,
Computed,
SystemDefault,
}
pub struct CliConfig<'a> { pub struct CliConfig<'a> {
pub command: CliCommand, pub command: CliCommand,
pub json_rpc_url: String, pub json_rpc_url: String,
pub websocket_url: String,
pub signers: Vec<&'a dyn Signer>, pub signers: Vec<&'a dyn Signer>,
pub keypair_path: String, pub keypair_path: String,
pub derivation_path: Option<DerivationPath>, pub derivation_path: Option<DerivationPath>,
@ -446,16 +452,97 @@ pub struct CliConfig<'a> {
} }
impl CliConfig<'_> { impl CliConfig<'_> {
pub fn default_keypair_path() -> String { fn default_keypair_path() -> String {
let mut keypair_path = dirs::home_dir().expect("home directory"); let mut keypair_path = dirs::home_dir().expect("home directory");
keypair_path.extend(&[".config", "solana", "id.json"]); keypair_path.extend(&[".config", "solana", "id.json"]);
keypair_path.to_str().unwrap().to_string() 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() "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<Url> = 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<Pubkey, SignerError> { pub(crate) fn pubkey(&self) -> Result<Pubkey, SignerError> {
if !self.signers.is_empty() { if !self.signers.is_empty() {
self.signers[0].try_pubkey() self.signers[0].try_pubkey()
@ -475,6 +562,7 @@ impl Default for CliConfig<'_> {
use_lamports_unit: false, use_lamports_unit: false,
}, },
json_rpc_url: Self::default_json_rpc_url(), json_rpc_url: Self::default_json_rpc_url(),
websocket_url: Self::default_websocket_url(),
signers: Vec::new(), signers: Vec::new(),
keypair_path: Self::default_keypair_path(), keypair_path: Self::default_keypair_path(),
derivation_path: None, derivation_path: None,
@ -514,7 +602,10 @@ pub fn parse_command(
signers: vec![], signers: vec![],
}), }),
("ping", Some(matches)) => parse_cluster_ping(matches, default_signer_path, wallet_manager), ("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), ("block-production", Some(matches)) => parse_show_block_production(matches),
("gossip", Some(_matches)) => Ok(CliCommandInfo { ("gossip", Some(_matches)) => Ok(CliCommandInfo {
command: CliCommand::ShowGossip, command: CliCommand::ShowGossip,
@ -1474,7 +1565,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
process_get_transaction_count(&rpc_client, commitment_config) process_get_transaction_count(&rpc_client, commitment_config)
} }
CliCommand::LeaderSchedule => process_leader_schedule(&rpc_client), CliCommand::LeaderSchedule => process_leader_schedule(&rpc_client),
CliCommand::LiveSlots { url } => process_live_slots(&url), CliCommand::LiveSlots => process_live_slots(&config.websocket_url),
CliCommand::Ping { CliCommand::Ping {
lamports, lamports,
interval, interval,

View File

@ -170,16 +170,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
) )
.subcommand( .subcommand(
SubCommand::with_name("live-slots") SubCommand::with_name("live-slots")
.about("Show information about the current slot progression") .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"),
),
) )
.subcommand( .subcommand(
SubCommand::with_name("block-production") SubCommand::with_name("block-production")
@ -279,14 +270,6 @@ pub fn parse_cluster_ping(
}) })
} }
pub fn parse_live_slots(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
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<CliCommandInfo, CliError> { pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let slot = value_t_or_exit!(matches, "slot", u64); let slot = value_t_or_exit!(matches, "slot", u64);
Ok(CliCommandInfo { Ok(CliCommandInfo {
@ -873,6 +856,9 @@ pub fn process_live_slots(url: &str) -> ProcessResult {
let (mut client, receiver) = PubsubClient::slot_subscribe(url)?; let (mut client, receiver) = PubsubClient::slot_subscribe(url)?;
slot_progress.set_message("Connected."); slot_progress.set_message("Connected.");
let spacer = "|";
slot_progress.println(spacer);
let mut last_root = std::u64::MAX; let mut last_root = std::u64::MAX;
let mut last_root_update = Instant::now(); let mut last_root_update = Instant::now();
let mut slots_per_second = std::f64::NAN; 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 { if slot_delta != root_delta {
let prev_root = format!( let prev_root = format!(
"|<- {} <- … <- {} <- {}", "|<--- {} <- … <- {} <- {} (prev)",
previous.root, previous.parent, previous.slot previous.root, previous.parent, previous.slot
) );
.to_owned();
slot_progress.println(&prev_root); 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); current = Some(new_info);

View File

@ -1,3 +1,4 @@
use crate::cli::SettingType;
use console::style; use console::style;
use solana_sdk::transaction::Transaction; use solana_sdk::transaction::Transaction;
@ -11,17 +12,19 @@ pub fn println_name_value(name: &str, value: &str) {
println!("{} {}", style(name).bold(), styled_value); println!("{} {}", style(name).bold(), styled_value);
} }
pub fn println_name_value_or(name: &str, value: &str, default_value: &str) { pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
if value == "" { let description = match setting_type {
SettingType::Explicit => "",
SettingType::Computed => "(computed)",
SettingType::SystemDefault => "(default)",
};
println!( println!(
"{} {} {}", "{} {} {}",
style(name).bold(), style(name).bold(),
style(default_value), style(value),
style("(default)").italic() style(description).italic(),
); );
} else {
println!("{} {}", style(name).bold(), style(value));
};
} }
pub fn println_signers(tx: &Transaction) { pub fn println_signers(tx: &Transaction) {

View File

@ -20,29 +20,31 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
("get", Some(subcommand_matches)) => { ("get", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") { if let Some(config_file) = matches.value_of("config_file") {
let config = Config::load(config_file).unwrap_or_default(); 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") { if let Some(field) = subcommand_matches.value_of("specific_setting") {
let (field_name, value, default_value) = match field { let (field_name, value, setting_type) = match field {
"url" => ("RPC URL", config.url, CliConfig::default_json_rpc_url()), "json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type),
"keypair" => ( "websocket_url" => ("WS URL", websocket_url, ws_setting_type),
"Key Path", "keypair" => ("Key Path", keypair_path, keypair_setting_type),
config.keypair_path,
CliConfig::default_keypair_path(),
),
_ => unreachable!(), _ => unreachable!(),
}; };
println_name_value_or(&format!("{}:", field_name), &value, &default_value); println_name_value_or(&format!("{}:", field_name), &value, setting_type);
} else { } else {
println_name_value("Config File:", config_file); println_name_value("Config File:", config_file);
println_name_value_or( println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
"RPC URL:", println_name_value_or("WS URL:", &websocket_url, ws_setting_type);
&config.url, println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
&CliConfig::default_json_rpc_url(),
);
println_name_value_or(
"Keypair Path:",
&config.keypair_path,
&CliConfig::default_keypair_path(),
);
} }
} else { } else {
println!( println!(
@ -58,13 +60,29 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
if let Some(url) = subcommand_matches.value_of("json_rpc_url") { if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
config.url = url.to_string(); config.url = url.to_string();
} }
if let Some(url) = subcommand_matches.value_of("websocket_url") {
config.websocket_url = url.to_string();
}
if let Some(keypair) = subcommand_matches.value_of("keypair") { if let Some(keypair) = subcommand_matches.value_of("keypair") {
config.keypair_path = keypair.to_string(); config.keypair_path = keypair.to_string();
} }
config.save(config_file)?; config.save(config_file)?;
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);
println_name_value("Config File:", config_file); println_name_value("Config File:", config_file);
println_name_value("RPC URL:", &config.url); println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
println_name_value("Keypair Path:", &config.keypair_path); println_name_value_or("WS URL:", &websocket_url, ws_setting_type);
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
} else { } else {
println!( println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location", "{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
@ -89,22 +107,20 @@ pub fn parse_args<'a>(
} else { } else {
Config::default() Config::default()
}; };
let json_rpc_url = if let Some(url) = matches.value_of("json_rpc_url") { let (_, json_rpc_url) = CliConfig::compute_json_rpc_url_setting(
url.to_string() matches.value_of("json_rpc_url").unwrap_or(""),
} else if config.url != "" { &config.url,
config.url );
} else { let (_, websocket_url) = CliConfig::compute_websocket_url_setting(
let default = CliConfig::default(); matches.value_of("websocket_url").unwrap_or(""),
default.json_rpc_url &config.websocket_url,
}; matches.value_of("json_rpc_url").unwrap_or(""),
&config.url,
let default_signer_path = if matches.is_present("keypair") { );
matches.value_of("keypair").unwrap().to_string() let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
} else if config.keypair_path != "" { matches.value_of("keypair").unwrap_or(""),
config.keypair_path &config.keypair_path,
} else { );
CliConfig::default_keypair_path()
};
let CliCommandInfo { command, signers } = let CliCommandInfo { command, signers } =
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?; parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
@ -113,6 +129,7 @@ pub fn parse_args<'a>(
CliConfig { CliConfig {
command, command,
json_rpc_url, json_rpc_url,
websocket_url,
signers: vec![], signers: vec![],
keypair_path: default_signer_path, keypair_path: default_signer_path,
derivation_path: derivation_of(matches, "derivation_path"), derivation_path: derivation_of(matches, "derivation_path"),
@ -154,6 +171,15 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.validator(is_url) .validator(is_url)
.help("JSON RPC URL for the solana cluster"), .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(
Arg::with_name("keypair") Arg::with_name("keypair")
.short("k") .short("k")
@ -198,7 +224,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.index(1) .index(1)
.value_name("CONFIG_FIELD") .value_name("CONFIG_FIELD")
.takes_value(true) .takes_value(true)
.possible_values(&["url", "keypair"]) .possible_values(&["json_rpc_url", "websocket_url", "keypair"])
.help("Return a specific config setting"), .help("Return a specific config setting"),
), ),
) )
@ -207,7 +233,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.about("Set a config setting") .about("Set a config setting")
.group( .group(
ArgGroup::with_name("config_settings") ArgGroup::with_name("config_settings")
.args(&["json_rpc_url", "keypair"]) .args(&["json_rpc_url", "websocket_url", "keypair"])
.multiple(true) .multiple(true)
.required(true), .required(true),
), ),