Remove interactive behavior from wallet

This commit is contained in:
Greg Fitzgerald
2018-06-29 11:38:00 -06:00
committed by Greg Fitzgerald
parent f3df5df52c
commit 9a4733bde7

View File

@ -13,6 +13,8 @@ use solana::mint::Mint;
use solana::signature::Signature; use solana::signature::Signature;
use solana::thin_client::ThinClient; use solana::thin_client::ThinClient;
use std::env; use std::env;
use std::error;
use std::fmt;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
@ -21,6 +23,58 @@ use std::process::exit;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; 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<Signature>,
}
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) { fn print_usage(program: &str, opts: Options) {
let mut brief = format!("Usage: {} [options]\n\n", program); let mut brief = format!("Usage: {} [options]\n\n", program);
brief += " solana-wallet allows you to perform basic actions, including\n"; 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)); print!("{}", opts.usage(&brief));
} }
fn main() -> io::Result<()> { fn parse_command(input: &str) -> Result<WalletCommand, WalletError> {
env_logger::init(); 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<String>) -> Result<WalletConfig, Box<error::Error>> {
let mut opts = Options::new(); let mut opts = Options::new();
opts.optopt("l", "", "leader", "leader.json"); opts.optopt("l", "", "leader", "leader.json");
opts.optopt("m", "", "mint", "mint.json"); opts.optopt("m", "", "mint", "mint.json");
opts.optopt("c", "", "client port", "port"); opts.optopt("c", "", "client port", "port");
opts.optflag("d", "dyn", "detect network address dynamically"); opts.optflag("d", "dyn", "detect network address dynamically");
opts.optflag("h", "help", "print help"); opts.optflag("h", "help", "print help");
let args: Vec<String> = env::args().collect();
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
@ -47,126 +109,129 @@ fn main() -> io::Result<()> {
exit(1); exit(1);
} }
}; };
if matches.opt_present("h") { if matches.opt_present("h") {
let program = args[0].clone(); let program = args[0].clone();
print_usage(&program, opts); print_usage(&program, opts);
return Ok(()); display_actions();
return Ok(WalletConfig::default());
} }
let mut client_addr: SocketAddr = "0.0.0.0:8100".parse().unwrap(); let mut client_addr: SocketAddr = "0.0.0.0:8100".parse().unwrap();
if matches.opt_present("c") { if matches.opt_present("c") {
let port = matches.opt_str("c").unwrap().parse().unwrap(); let port = matches.opt_str("c").unwrap().parse().unwrap();
client_addr.set_port(port); client_addr.set_port(port);
} }
if matches.opt_present("d") { if matches.opt_present("d") {
client_addr.set_ip(get_ip_addr().unwrap()); client_addr.set_ip(get_ip_addr().unwrap());
} }
let leader = if matches.opt_present("l") { let leader = if matches.opt_present("l") {
read_leader(matches.opt_str("l").unwrap()) read_leader(matches.opt_str("l").unwrap())
} else { } else {
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000);
ReplicatedData::new_leader(&server_addr) ReplicatedData::new_leader(&server_addr)
}; };
let id = if matches.opt_present("m") { let id = if matches.opt_present("m") {
read_mint(matches.opt_str("m").unwrap()) read_mint(matches.opt_str("m").unwrap())?
} else { } else {
eprintln!("No mint found!"); eprintln!("No mint found!");
exit(1); exit(1);
}; };
let mut client = mk_client(&client_addr, &leader)?;
let mut drone_addr = leader.transactions_addr.clone(); let mut drone_addr = leader.transactions_addr.clone();
drone_addr.set_port(9900); drone_addr.set_port(9900);
let mut last_transaction_sig: Option<Signature> = None; let command = parse_command(&matches.free[0])?;
// Start the a, generate a random client keypair, and show user possible commands Ok(WalletConfig {
display_actions(); leader,
id,
client_addr,
drone_addr, // TODO: Add an option for this.
command,
sig: None, // TODO: Add an option for this.
})
}
loop { fn process_command(
let mut input = String::new(); config: &WalletConfig,
match std::io::stdin().read_line(&mut input) { client: &mut ThinClient,
Ok(_) => { ) -> Result<(), Box<error::Error>> {
match input.trim() { match config.command {
// Check client balance // Check client balance
"balance" => { WalletCommand::Balance => {
println!("Balance requested..."); println!("Balance requested...");
let balance = client.poll_get_balance(&id.pubkey()); let balance = client.poll_get_balance(&config.id.pubkey());
match balance { match balance {
Ok(balance) => { Ok(balance) => {
println!("Your balance is: {:?}", balance); println!("Your balance is: {:?}", balance);
} }
Err(ref e) if e.kind() == std::io::ErrorKind::Other => { Err(ref e) if e.kind() == std::io::ErrorKind::Other => {
println!("No account found! Request an airdrop to get started."); println!("No account found! Request an airdrop to get started.");
} }
Err(error) => { Err(error) => {
println!("An error occurred: {:?}", 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());
}
} }
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() { fn display_actions() {
println!(""); println!("");
println!("What would you like to do? Type a command:");
println!(" `balance` - Get your account balance"); println!(" `balance` - Get your account balance");
println!(" `airdrop` - Request a batch of tokens"); println!(" `airdrop` - Request a batch of tokens");
println!(" `pay` - Spend your tokens as fast as possible"); 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)) serde_json::from_reader(file).expect(&format!("failed to parse {}", path))
} }
fn read_mint(path: String) -> Mint { fn read_mint(path: String) -> Result<Mint, Box<error::Error>> {
let file = File::open(path.clone()).expect(&format!("file not found: {}", path)); let file = File::open(path.clone())?;
serde_json::from_reader(file).expect(&format!("failed to parse {}", path)) let mint = serde_json::from_reader(file)?;
Ok(mint)
} }
fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result<ThinClient> { fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result<ThinClient> {
@ -211,3 +277,10 @@ fn request_airdrop(drone_addr: &SocketAddr, id: &Mint) {
stream.write_all(&tx).unwrap(); stream.write_all(&tx).unwrap();
// TODO: add timeout to this function, in case of unresponsive drone // TODO: add timeout to this function, in case of unresponsive drone
} }
fn main() -> Result<(), Box<error::Error>> {
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)
}