From 4eae8dda88c448400eb89d6c2e4aa4ac11cc260c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Fri, 29 Jun 2018 11:38:00 -0600 Subject: [PATCH] Remove interactive behavior from wallet --- src/bin/wallet.rs | 255 +++++++++++++++++++++++++++++----------------- 1 file changed, 164 insertions(+), 91 deletions(-) diff --git a/src/bin/wallet.rs b/src/bin/wallet.rs index 1d85ecee2526a6..3ed1e49d844d0e 100644 --- a/src/bin/wallet.rs +++ b/src/bin/wallet.rs @@ -13,6 +13,8 @@ use solana::mint::Mint; use solana::signature::Signature; use solana::thin_client::ThinClient; use std::env; +use std::error; +use std::fmt; use std::fs::File; use std::io; use std::io::prelude::*; @@ -21,6 +23,58 @@ use std::process::exit; use std::thread::sleep; use std::time::Duration; +enum WalletCommand { + Balance, + AirDrop, + Pay, + Confirm, +} + +#[derive(Debug, Clone)] +enum WalletError { + CommandNotRecognized(String), +} + +impl fmt::Display for WalletError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid") + } +} + +impl error::Error for WalletError { + fn description(&self) -> &str { + "invalid" + } + + fn cause(&self) -> Option<&error::Error> { + // Generic error, underlying cause isn't tracked. + None + } +} + +struct WalletConfig { + leader: ReplicatedData, + id: Mint, + client_addr: SocketAddr, + drone_addr: SocketAddr, + command: WalletCommand, + sig: Option, +} + +impl Default for WalletConfig { + fn default() -> WalletConfig { + let default_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); + WalletConfig { + leader: ReplicatedData::new_leader(&default_addr.clone()), + id: Mint::new(0), + client_addr: default_addr.clone(), + drone_addr: default_addr.clone(), + command: WalletCommand::Balance, + sig: None, + } + } +} + fn print_usage(program: &str, opts: Options) { let mut brief = format!("Usage: {} [options]\n\n", program); brief += " solana-wallet allows you to perform basic actions, including\n"; @@ -30,16 +84,24 @@ fn print_usage(program: &str, opts: Options) { print!("{}", opts.usage(&brief)); } -fn main() -> io::Result<()> { - env_logger::init(); +fn parse_command(input: &str) -> Result { + match input { + "balance" => Ok(WalletCommand::Balance), + "airdrop" => Ok(WalletCommand::AirDrop), + "pay" => Ok(WalletCommand::Pay), + "confirm" => Ok(WalletCommand::Confirm), + _ => Err(WalletError::CommandNotRecognized(input.to_string())), + } +} +fn parse_args(args: Vec) -> Result> { let mut opts = Options::new(); opts.optopt("l", "", "leader", "leader.json"); opts.optopt("m", "", "mint", "mint.json"); opts.optopt("c", "", "client port", "port"); opts.optflag("d", "dyn", "detect network address dynamically"); opts.optflag("h", "help", "print help"); - let args: Vec = env::args().collect(); + let matches = match opts.parse(&args[1..]) { Ok(m) => m, Err(e) => { @@ -47,126 +109,129 @@ fn main() -> io::Result<()> { exit(1); } }; + if matches.opt_present("h") { let program = args[0].clone(); print_usage(&program, opts); - return Ok(()); + display_actions(); + return Ok(WalletConfig::default()); } + let mut client_addr: SocketAddr = "0.0.0.0:8100".parse().unwrap(); if matches.opt_present("c") { let port = matches.opt_str("c").unwrap().parse().unwrap(); client_addr.set_port(port); } + if matches.opt_present("d") { client_addr.set_ip(get_ip_addr().unwrap()); } + let leader = if matches.opt_present("l") { read_leader(matches.opt_str("l").unwrap()) } else { let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); ReplicatedData::new_leader(&server_addr) }; + let id = if matches.opt_present("m") { - read_mint(matches.opt_str("m").unwrap()) + read_mint(matches.opt_str("m").unwrap())? } else { eprintln!("No mint found!"); exit(1); }; - let mut client = mk_client(&client_addr, &leader)?; let mut drone_addr = leader.transactions_addr.clone(); drone_addr.set_port(9900); - let mut last_transaction_sig: Option = None; - - // Start the a, generate a random client keypair, and show user possible commands - display_actions(); - - loop { - let mut input = String::new(); - match std::io::stdin().read_line(&mut input) { - Ok(_) => { - match input.trim() { - // Check client balance - "balance" => { - println!("Balance requested..."); - let balance = client.poll_get_balance(&id.pubkey()); - match balance { - Ok(balance) => { - println!("Your balance is: {:?}", balance); - } - Err(ref e) if e.kind() == std::io::ErrorKind::Other => { - println!("No account found! Request an airdrop to get started."); - } - Err(error) => { - println!("An error occurred: {:?}", error); - } - } - } - // Request an airdrop from Solana Drone; - // Request amount is set in request_airdrop function - "airdrop" => { - println!("Airdrop requested..."); - let _airdrop = request_airdrop(&drone_addr, &id); - // TODO: return airdrop Result from Drone - sleep(Duration::from_millis(100)); - println!( - "Your balance is: {:?}", - client.poll_get_balance(&id.pubkey()).unwrap() - ); - } - // If client has positive balance, spend tokens in {balance} number of transactions - "pay" => { - let last_id = client.get_last_id(); - let balance = client.poll_get_balance(&id.pubkey()); - match balance { - Ok(0) => { - println!("You don't have any tokens!"); - } - Ok(balance) => { - println!("Sending {:?} tokens to self...", balance); - let sig = client - .transfer(balance, &id.keypair(), id.pubkey(), &last_id) - .expect("transfer return signature"); - last_transaction_sig = Some(sig); - println!("Transaction sent!"); - println!("Signature: {:?}", sig); - } - Err(ref e) if e.kind() == std::io::ErrorKind::Other => { - println!("No account found! Request an airdrop to get started."); - } - Err(error) => { - println!("An error occurred: {:?}", error); - } - } - } - // Confirm the last client transaction by signature - "confirm" => match last_transaction_sig { - Some(sig) => { - if client.check_signature(&sig) { - println!("Signature found at bank id {:?}", id); - } else { - println!("Uh oh... Signature not found!"); - } - } - None => { - println!("No recent signature. Make a payment to get started."); - } - }, - _ => { - println!("Command {:?} not recognized", input.trim()); - } + let command = parse_command(&matches.free[0])?; + + Ok(WalletConfig { + leader, + id, + client_addr, + drone_addr, // TODO: Add an option for this. + command, + sig: None, // TODO: Add an option for this. + }) +} + +fn process_command( + config: &WalletConfig, + client: &mut ThinClient, +) -> Result<(), Box> { + match config.command { + // Check client balance + WalletCommand::Balance => { + println!("Balance requested..."); + let balance = client.poll_get_balance(&config.id.pubkey()); + match balance { + Ok(balance) => { + println!("Your balance is: {:?}", balance); + } + Err(ref e) if e.kind() == std::io::ErrorKind::Other => { + println!("No account found! Request an airdrop to get started."); + } + Err(error) => { + println!("An error occurred: {:?}", error); } - display_actions(); } - Err(error) => println!("error: {}", error), } + // Request an airdrop from Solana Drone; + // Request amount is set in request_airdrop function + WalletCommand::AirDrop => { + println!("Airdrop requested..."); + let _airdrop = request_airdrop(&config.drone_addr, &config.id); + // TODO: return airdrop Result from Drone + sleep(Duration::from_millis(100)); + println!( + "Your balance is: {:?}", + client.poll_get_balance(&config.id.pubkey()).unwrap() + ); + } + // If client has positive balance, spend tokens in {balance} number of transactions + WalletCommand::Pay => { + let last_id = client.get_last_id(); + let balance = client.poll_get_balance(&config.id.pubkey()); + match balance { + Ok(0) => { + println!("You don't have any tokens!"); + } + Ok(balance) => { + println!("Sending {:?} tokens to self...", balance); + let sig = client + .transfer(balance, &config.id.keypair(), config.id.pubkey(), &last_id) + .expect("transfer return signature"); + println!("Transaction sent!"); + println!("Signature: {:?}", sig); + } + Err(ref e) if e.kind() == std::io::ErrorKind::Other => { + println!("No account found! Request an airdrop to get started."); + } + Err(error) => { + println!("An error occurred: {:?}", error); + } + } + } + // Confirm the last client transaction by signature + WalletCommand::Confirm => match config.sig { + Some(sig) => { + if client.check_signature(&sig) { + println!("Signature found at bank id {:?}", config.id); + } else { + println!("Uh oh... Signature not found!"); + } + } + None => { + println!("No recent signature. Make a payment to get started."); + } + }, } + Ok(()) } fn display_actions() { println!(""); - println!("What would you like to do? Type a command:"); println!(" `balance` - Get your account balance"); println!(" `airdrop` - Request a batch of tokens"); println!(" `pay` - Spend your tokens as fast as possible"); @@ -179,9 +244,10 @@ fn read_leader(path: String) -> ReplicatedData { serde_json::from_reader(file).expect(&format!("failed to parse {}", path)) } -fn read_mint(path: String) -> Mint { - let file = File::open(path.clone()).expect(&format!("file not found: {}", path)); - serde_json::from_reader(file).expect(&format!("failed to parse {}", path)) +fn read_mint(path: String) -> Result> { + let file = File::open(path.clone())?; + let mint = serde_json::from_reader(file)?; + Ok(mint) } fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result { @@ -211,3 +277,10 @@ fn request_airdrop(drone_addr: &SocketAddr, id: &Mint) { stream.write_all(&tx).unwrap(); // TODO: add timeout to this function, in case of unresponsive drone } + +fn main() -> Result<(), Box> { + env_logger::init(); + let config = parse_args(env::args().collect())?; + let mut client = mk_client(&config.client_addr, &config.leader)?; + process_command(&config, &mut client) +}