From a84b6bc7e4346716f3b1e2df8ef1e5bdbcad0c66 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Wed, 16 Jan 2019 20:43:00 -0800 Subject: [PATCH] Overhaul wallet rpc/drone command-line arguments --- book/src/wallet.md | 38 +++---- multinode-demo/fullnode.sh | 2 +- net/remote/remote-sanity.sh | 2 +- scripts/wallet-sanity.sh | 2 +- src/rpc.rs | 4 +- src/rpc_request.rs | 12 ++- wallet/src/main.rs | 185 +++++++++++++++++++++++--------- wallet/src/wallet.rs | 66 +++++++----- wallet/tests/pay.rs | 30 +++--- wallet/tests/request_airdrop.rs | 5 +- 10 files changed, 220 insertions(+), 126 deletions(-) diff --git a/book/src/wallet.md b/book/src/wallet.md index 0b47f14da..422db217f 100644 --- a/book/src/wallet.md +++ b/book/src/wallet.md @@ -168,21 +168,23 @@ $ solana-wallet send-timestamp --date 2018-12-24T23:59:00 ### Usage ```manpage -solana-wallet 0.11.0 +solana-wallet 0.12.0 USAGE: - solana-wallet [OPTIONS] [SUBCOMMAND] + solana-wallet [FLAGS] [OPTIONS] [SUBCOMMAND] FLAGS: -h, --help Prints help information + --rpc-tls Enable TLS for the RPC endpoint -V, --version Prints version information OPTIONS: - -k, --keypair /path/to/id.json - -n, --network Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001 - --proxy Address of TLS proxy - --port Optional rpc-port configuration to connect to non-default nodes - --timeout Max seconds to wait to get necessary gossip from the network + --drone-host Drone host to use [default: same as --host] + --drone-port Drone port to use [default: 9900] + -n, --host Host to use for both RPC and drone [default: 127.0.0.1] + -k, --keypair /path/to/id.json + --rpc-host RPC host to use [default: same as --host] + --rpc-port RPC port to use [default: 8899] SUBCOMMANDS: address Get your public key @@ -199,7 +201,7 @@ SUBCOMMANDS: ``` ```manpage -solana-wallet-address +solana-wallet-address Get your public key USAGE: @@ -211,7 +213,7 @@ FLAGS: ``` ```manpage -solana-wallet-airdrop +solana-wallet-airdrop Request a batch of tokens USAGE: @@ -226,7 +228,7 @@ ARGS: ``` ```manpage -solana-wallet-balance +solana-wallet-balance Get your balance USAGE: @@ -238,7 +240,7 @@ FLAGS: ``` ```manpage -solana-wallet-cancel +solana-wallet-cancel Cancel a transfer USAGE: @@ -253,7 +255,7 @@ ARGS: ``` ```manpage -solana-wallet-confirm +solana-wallet-confirm Confirm transaction by signature USAGE: @@ -268,7 +270,7 @@ ARGS: ``` ```manpage -solana-wallet-deploy +solana-wallet-deploy Deploy a program USAGE: @@ -283,7 +285,7 @@ ARGS: ``` ```manpage -solana-wallet-get-transaction-count +solana-wallet-get-transaction-count Get current transaction count USAGE: @@ -295,14 +297,14 @@ FLAGS: ``` ```manpage -solana-wallet-pay +solana-wallet-pay Send a payment USAGE: solana-wallet pay [FLAGS] [OPTIONS] FLAGS: - --cancelable + --cancelable -h, --help Prints help information -V, --version Prints version information @@ -317,7 +319,7 @@ ARGS: ``` ```manpage -solana-wallet-send-signature +solana-wallet-send-signature Send a signature to authorize a transfer USAGE: @@ -333,7 +335,7 @@ ARGS: ``` ```manpage -solana-wallet-send-timestamp +solana-wallet-send-timestamp Send a timestamp to unlock a transfer USAGE: diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index b4e38fef1..3ebebcf26 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -194,7 +194,7 @@ $solana_wallet --keypair "$fullnode_id_path" address # - one token to keep the node identity public key valid. retries=5 while true; do - if $solana_wallet --keypair "$fullnode_id_path" --network "$leader_address" airdrop 3; then + if $solana_wallet --keypair "$fullnode_id_path" --host "${leader_address%:*}" airdrop 3; then break fi diff --git a/net/remote/remote-sanity.sh b/net/remote/remote-sanity.sh index 2104c81f7..de8ff38ec 100755 --- a/net/remote/remote-sanity.sh +++ b/net/remote/remote-sanity.sh @@ -123,7 +123,7 @@ echo "--- RPC API: getTransactionCount" echo "--- $entrypointIp: wallet sanity" ( set -x - scripts/wallet-sanity.sh "$entrypointIp":8001 + scripts/wallet-sanity.sh --host "$entrypointIp" ) echo "--- $entrypointIp: verify ledger" diff --git a/scripts/wallet-sanity.sh b/scripts/wallet-sanity.sh index 6026cbaee..738c07d45 100755 --- a/scripts/wallet-sanity.sh +++ b/scripts/wallet-sanity.sh @@ -12,7 +12,7 @@ source multinode-demo/common.sh if [[ -z $1 ]]; then # no network argument, use default entrypoint=() else - entrypoint=(-n "$1") + entrypoint=("$@") fi # Tokens transferred to this address are lost forever... diff --git a/src/rpc.rs b/src/rpc.rs index 3c3422b9c..938065bbe 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -507,7 +507,7 @@ mod tests { let thread = rpc_service.thread_hdl.thread(); assert_eq!(thread.name().unwrap(), "solana-jsonrpc"); - let rpc_string = get_rpc_request_str(rpc_addr); + let rpc_string = get_rpc_request_str(rpc_addr, false); let client = reqwest::Client::new(); let request = json!({ "jsonrpc": "2.0", @@ -755,7 +755,7 @@ mod tests { "params": json!([serial_tx]) }); let rpc_addr = leader_data.rpc; - let rpc_string = get_rpc_request_str(rpc_addr); + let rpc_string = get_rpc_request_str(rpc_addr, false); let mut response = client .post(&rpc_string) .header(CONTENT_TYPE, "application/json") diff --git a/src/rpc_request.rs b/src/rpc_request.rs index a3f1dd65e..7a952a30e 100644 --- a/src/rpc_request.rs +++ b/src/rpc_request.rs @@ -21,7 +21,7 @@ impl RpcClient { } pub fn new_with_timeout(addr: SocketAddr, timeout: Duration) -> Self { - let addr = get_rpc_request_str(addr); + let addr = get_rpc_request_str(addr, false); let client = reqwest::Client::builder() .timeout(timeout) .build() @@ -30,7 +30,7 @@ impl RpcClient { } pub fn new_from_socket(addr: SocketAddr) -> Self { - Self::new(get_rpc_request_str(addr)) + Self::new(get_rpc_request_str(addr, false)) } pub fn retry_make_rpc_request( @@ -77,8 +77,12 @@ impl RpcClient { } } -pub fn get_rpc_request_str(rpc_addr: SocketAddr) -> String { - format!("http://{}", rpc_addr) +pub fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String { + if tls { + format!("https://{}", rpc_addr) + } else { + format!("http://{}", rpc_addr) + } } pub trait RpcRequestHandler { diff --git a/wallet/src/main.rs b/wallet/src/main.rs index ecdc753db..7ea44161b 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,22 +1,46 @@ use clap::{crate_version, App, Arg, ArgMatches, SubCommand}; -use solana::socketaddr; use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil}; use solana_wallet::wallet::{parse_command, process_command, WalletConfig, WalletError}; use std::error; -use std::net::SocketAddr; pub fn parse_args(matches: &ArgMatches<'_>) -> Result> { - let network = if let Some(addr) = matches.value_of("network") { - addr.parse().or_else(|_| { - Err(WalletError::BadParameter( - "Invalid network location".to_string(), - )) - })? + let host = matches + .value_of("host") + .unwrap() + .parse() + .or_else(|_| Err(WalletError::BadParameter("Invalid host".to_string())))?; + + let drone_host = if let Some(drone_host) = matches.value_of("drone_host") { + Some( + drone_host + .parse() + .or_else(|_| Err(WalletError::BadParameter("Invalid drone host".to_string())))?, + ) } else { - socketaddr!("127.0.0.1:8001") + None }; - let proxy = matches.value_of("proxy").map(|proxy| proxy.to_string()); + let rpc_host = if let Some(rpc_host) = matches.value_of("rpc_host") { + Some( + rpc_host + .parse() + .or_else(|_| Err(WalletError::BadParameter("Invalid rpc host".to_string())))?, + ) + } else { + None + }; + + let drone_port = matches + .value_of("drone_port") + .unwrap() + .parse() + .or_else(|_| Err(WalletError::BadParameter("Invalid drone port".to_string())))?; + + let rpc_port = matches + .value_of("rpc_port") + .unwrap() + .parse() + .or_else(|_| Err(WalletError::BadParameter("Invalid rpc port".to_string())))?; let mut path = dirs::home_dir().expect("home directory"); let id_path = if matches.is_present("keypair") { @@ -42,40 +66,83 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result Result<(), Box> { solana_logger::setup(); + + let (default_host, default_rpc_port, default_drone_port) = { + let defaults = WalletConfig::default(); + ( + defaults.host.to_string(), + defaults.rpc_port.to_string(), + defaults.drone_port.to_string(), + ) + }; + let matches = App::new("solana-wallet") .version(crate_version!()) .arg( - Arg::with_name("network") + Arg::with_name("host") .short("n") - .long("network") - .value_name("HOST:PORT") + .long("host") + .value_name("IP ADDRESS") .takes_value(true) - .help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"), - ).arg( + .default_value(&default_host) + .help("Host to use for both RPC and drone"), + ) + .arg( + Arg::with_name("rpc_host") + .long("rpc-host") + .value_name("IP ADDRESS") + .takes_value(true) + .help("RPC host to use [default: same as --host]"), + ) + .arg( + Arg::with_name("rpc_port") + .long("rpc-port") + .value_name("PORT") + .takes_value(true) + .default_value(&default_rpc_port) + .help("RPC port to use"), + ) + .arg( + Arg::with_name("rpc_tps") + .long("rpc-tls") + .help("Enable TLS for the RPC endpoint"), + ) + .arg( + Arg::with_name("drone_host") + .long("drone-host") + .value_name("IP ADDRESS") + .takes_value(true) + .help("Drone host to use [default: same as --host]"), + ) + .arg( + Arg::with_name("drone_port") + .long("drone-port") + .value_name("PORT") + .takes_value(true) + .default_value(&default_drone_port) + .help("Drone port to use"), + ) + .arg( Arg::with_name("keypair") .short("k") .long("keypair") .value_name("PATH") .takes_value(true) .help("/path/to/id.json"), - ).arg( - Arg::with_name("proxy") - .long("proxy") - .takes_value(true) - .value_name("URL") - .help("Address of TLS proxy") - .conflicts_with("rpc-port") - ).subcommand(SubCommand::with_name("address").about("Get your public key")) + ) + .subcommand(SubCommand::with_name("address").about("Get your public key")) .subcommand( SubCommand::with_name("airdrop") .about("Request a batch of tokens") @@ -87,7 +154,8 @@ fn main() -> Result<(), Box> { .required(true) .help("The number of tokens to request"), ), - ).subcommand(SubCommand::with_name("balance").about("Get your balance")) + ) + .subcommand(SubCommand::with_name("balance").about("Get your balance")) .subcommand( SubCommand::with_name("cancel") .about("Cancel a transfer") @@ -99,7 +167,8 @@ fn main() -> Result<(), Box> { .required(true) .help("The process id of the transfer to cancel"), ), - ).subcommand( + ) + .subcommand( SubCommand::with_name("confirm") .about("Confirm transaction by signature") .arg( @@ -110,7 +179,8 @@ fn main() -> Result<(), Box> { .required(true) .help("The transaction signature to confirm"), ), - ).subcommand( + ) + .subcommand( SubCommand::with_name("deploy") .about("Deploy a program") .arg( @@ -120,12 +190,12 @@ fn main() -> Result<(), Box> { .takes_value(true) .required(true) .help("/path/to/program.o"), - ) - // TODO: Add "loader" argument; current default is bpf_loader - ).subcommand( - SubCommand::with_name("get-transaction-count") - .about("Get current transaction count") - ).subcommand( + ), // TODO: Add "loader" argument; current default is bpf_loader + ) + .subcommand( + SubCommand::with_name("get-transaction-count").about("Get current transaction count"), + ) + .subcommand( SubCommand::with_name("pay") .about("Send a payment") .arg( @@ -135,27 +205,31 @@ fn main() -> Result<(), Box> { .takes_value(true) .required(true) .help("The pubkey of recipient"), - ).arg( + ) + .arg( Arg::with_name("tokens") .index(2) .value_name("NUM") .takes_value(true) .required(true) .help("The number of tokens to send"), - ).arg( + ) + .arg( Arg::with_name("timestamp") .long("after") .value_name("DATETIME") .takes_value(true) .help("A timestamp after which transaction will execute"), - ).arg( + ) + .arg( Arg::with_name("timestamp-pubkey") .long("require-timestamp-from") .value_name("PUBKEY") .takes_value(true) .requires("timestamp") .help("Require timestamp from this third party"), - ).arg( + ) + .arg( Arg::with_name("witness") .long("require-signature-from") .value_name("PUBKEY") @@ -163,12 +237,14 @@ fn main() -> Result<(), Box> { .multiple(true) .use_delimiter(true) .help("Any third party signatures required to unlock the tokens"), - ).arg( + ) + .arg( Arg::with_name("cancelable") .long("cancelable") .takes_value(false), ), - ).subcommand( + ) + .subcommand( SubCommand::with_name("send-signature") .about("Send a signature to authorize a transfer") .arg( @@ -178,15 +254,17 @@ fn main() -> Result<(), Box> { .takes_value(true) .required(true) .help("The pubkey of recipient"), - ).arg( + ) + .arg( Arg::with_name("process-id") .index(2) .value_name("PROCESS_ID") .takes_value(true) .required(true) - .help("The process id of the transfer to authorize") - ) - ).subcommand( + .help("The process id of the transfer to authorize"), + ), + ) + .subcommand( SubCommand::with_name("send-timestamp") .about("Send a timestamp to unlock a transfer") .arg( @@ -196,21 +274,24 @@ fn main() -> Result<(), Box> { .takes_value(true) .required(true) .help("The pubkey of recipient"), - ).arg( + ) + .arg( Arg::with_name("process-id") .index(2) .value_name("PROCESS_ID") .takes_value(true) .required(true) - .help("The process id of the transfer to unlock") - ).arg( + .help("The process id of the transfer to unlock"), + ) + .arg( Arg::with_name("datetime") .long("date") .value_name("DATETIME") .takes_value(true) - .help("Optional arbitrary timestamp to apply") - ) - ).get_matches(); + .help("Optional arbitrary timestamp to apply"), + ), + ) + .get_matches(); let config = parse_args(&matches)?; let result = process_command(&config)?; diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index a157140f6..ca5912981 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -10,7 +10,6 @@ use solana::rpc_mock::{request_airdrop_transaction, MockRpcClient as RpcClient}; #[cfg(not(test))] use solana::rpc_request::RpcClient; use solana::rpc_request::{get_rpc_request_str, RpcRequest}; -use solana::socketaddr; #[cfg(not(test))] use solana_drone::drone::request_airdrop_transaction; use solana_drone::drone::DRONE_PORT; @@ -25,7 +24,7 @@ use solana_sdk::system_transaction::SystemTransaction; use solana_sdk::transaction::Transaction; use std::fs::File; use std::io::Read; -use std::net::{Ipv4Addr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::str::FromStr; use std::thread::sleep; use std::time::Duration; @@ -85,41 +84,41 @@ impl error::Error for WalletError { pub struct WalletConfig { pub id: Keypair, pub command: WalletCommand, - pub network: SocketAddr, - pub proxy: Option, - pub drone_port: Option, + pub drone_host: Option, + pub drone_port: u16, + pub host: IpAddr, pub rpc_client: Option, - pub rpc_port: Option, + pub rpc_host: Option, + pub rpc_port: u16, + pub rpc_tls: bool, } impl Default for WalletConfig { fn default() -> WalletConfig { - let default_addr = socketaddr!(0, 8000); WalletConfig { - id: Keypair::new(), command: WalletCommand::Balance, - network: default_addr, - proxy: None, - drone_port: None, + drone_host: None, + drone_port: DRONE_PORT, + host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + id: Keypair::new(), rpc_client: None, - rpc_port: None, + rpc_host: None, + rpc_port: RPC_PORT, + rpc_tls: false, } } } impl WalletConfig { pub fn drone_addr(&self) -> SocketAddr { - // Assume drone is running on the provided network entrypoint - let mut drone_addr = self.network; - drone_addr.set_port(self.drone_port.unwrap_or(DRONE_PORT)); - drone_addr + SocketAddr::new(self.drone_host.unwrap_or(self.host), self.drone_port) } pub fn rpc_addr(&self) -> String { - let mut rpc_addr = self.network; - rpc_addr.set_port(self.rpc_port.unwrap_or(RPC_PORT)); - let rpc_addr_str = get_rpc_request_str(rpc_addr); - self.proxy.clone().unwrap_or(rpc_addr_str) + get_rpc_request_str( + SocketAddr::new(self.rpc_host.unwrap_or(self.host), self.rpc_port), + self.rpc_tls, + ) } } @@ -794,6 +793,7 @@ mod tests { use clap::{App, Arg, SubCommand}; use serde_json::Value; use solana::rpc_mock::{PUBKEY, SIGNATURE}; + use solana::socketaddr; use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil}; use std::fs; use std::net::{Ipv4Addr, SocketAddr}; @@ -802,15 +802,29 @@ mod tests { #[test] fn test_wallet_config_drone_addr() { let mut config = WalletConfig::default(); - assert_eq!(config.drone_addr(), socketaddr!(0, DRONE_PORT)); + assert_eq!( + config.drone_addr(), + SocketAddr::new(config.host, config.drone_port) + ); - config.drone_port = Some(1234); - assert_eq!(config.drone_addr(), socketaddr!(0, 1234)); + config.drone_port = 1234; + assert_eq!(config.drone_addr(), SocketAddr::new(config.host, 1234)); - assert_eq!(config.rpc_addr(), "http://0.0.0.0:8899"); + config.drone_host = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2))); + assert_eq!( + config.drone_addr(), + SocketAddr::new(config.drone_host.unwrap(), 1234) + ); + } - config.rpc_port = Some(1234); - assert_eq!(config.rpc_addr(), "http://0.0.0.0:1234"); + #[test] + fn test_wallet_config_rpc_addr() { + let mut config = WalletConfig::default(); + assert_eq!(config.rpc_addr(), "http://127.0.0.1:8899"); + config.rpc_port = 1234; + assert_eq!(config.rpc_addr(), "http://127.0.0.1:1234"); + config.rpc_host = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2))); + assert_eq!(config.rpc_addr(), "http://127.0.0.2:1234"); } #[test] diff --git a/wallet/tests/pay.rs b/wallet/tests/pay.rs index 3923c550c..534a0c34f 100644 --- a/wallet/tests/pay.rs +++ b/wallet/tests/pay.rs @@ -72,14 +72,12 @@ fn test_wallet_timestamp_tx() { let rpc_client = RpcClient::new_from_socket(leader_data.rpc); let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.gossip; - config_payer.drone_port = Some(drone_addr.port()); - config_payer.rpc_port = Some(leader_data.rpc.port()); + config_payer.drone_port = drone_addr.port(); + config_payer.rpc_port = leader_data.rpc.port(); let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.gossip; - config_witness.drone_port = Some(drone_addr.port()); - config_witness.rpc_port = Some(leader_data.rpc.port()); + config_witness.drone_port = drone_addr.port(); + config_witness.rpc_port = leader_data.rpc.port(); assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); @@ -173,14 +171,12 @@ fn test_wallet_witness_tx() { let rpc_client = RpcClient::new_from_socket(leader_data.rpc); let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.gossip; - config_payer.drone_port = Some(drone_addr.port()); - config_payer.rpc_port = Some(leader_data.rpc.port()); + config_payer.drone_port = drone_addr.port(); + config_payer.rpc_port = leader_data.rpc.port(); let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.gossip; - config_witness.drone_port = Some(drone_addr.port()); - config_witness.rpc_port = Some(leader_data.rpc.port()); + config_witness.drone_port = drone_addr.port(); + config_witness.rpc_port = leader_data.rpc.port(); assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); @@ -270,14 +266,12 @@ fn test_wallet_cancel_tx() { let rpc_client = RpcClient::new_from_socket(leader_data.rpc); let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.gossip; - config_payer.drone_port = Some(drone_addr.port()); - config_payer.rpc_port = Some(leader_data.rpc.port()); + config_payer.drone_port = drone_addr.port(); + config_payer.rpc_port = leader_data.rpc.port(); let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.gossip; - config_witness.drone_port = Some(drone_addr.port()); - config_witness.rpc_port = Some(leader_data.rpc.port()); + config_witness.drone_port = drone_addr.port(); + config_witness.rpc_port = leader_data.rpc.port(); assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); diff --git a/wallet/tests/request_airdrop.rs b/wallet/tests/request_airdrop.rs index ce74cceaf..feb91aad7 100644 --- a/wallet/tests/request_airdrop.rs +++ b/wallet/tests/request_airdrop.rs @@ -56,9 +56,8 @@ fn test_wallet_request_airdrop() { let drone_addr = receiver.recv().unwrap(); let mut bob_config = WalletConfig::default(); - bob_config.network = leader_data.gossip; - bob_config.drone_port = Some(drone_addr.port()); - bob_config.rpc_port = Some(leader_data.rpc.port()); + bob_config.drone_port = drone_addr.port(); + bob_config.rpc_port = leader_data.rpc.port(); bob_config.command = WalletCommand::Airdrop(50); let sig_response = process_command(&bob_config);