From 0112a24179141d7d535b35d7171d42b143d9543d Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Thu, 28 Jun 2018 12:58:33 -0600 Subject: [PATCH] Add confirm command to wallet, and update RPU to check bank for a signature --- src/bank.rs | 27 +++++++++++- src/bin/wallet.rs | 63 ++++++++++++++++++---------- src/request.rs | 19 +++++++-- src/request_processor.rs | 6 +++ src/thin_client.rs | 90 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 175 insertions(+), 30 deletions(-) diff --git a/src/bank.rs b/src/bank.rs index 98a821a346..8ea1e7ddac 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -128,7 +128,10 @@ impl Bank { /// Return the last entry ID registered. pub fn last_id(&self) -> Hash { let last_ids = self.last_ids.read().expect("'last_ids' read lock"); - let last_item = last_ids.iter().last().expect("empty 'last_ids' list"); + let last_item = last_ids + .iter() + .last() + .expect("get last item from 'last_ids' list"); *last_item } @@ -430,6 +433,18 @@ impl Bank { pub fn transaction_count(&self) -> usize { self.transaction_count.load(Ordering::Relaxed) } + + pub fn check_signature(&self, signature: &Signature) -> Option<(Hash, Signature)> { + let last_ids_sigs = self.last_ids_sigs + .read() + .expect("'last_ids_sigs' read lock"); + for (hash, signatures) in last_ids_sigs.iter() { + if let Some(sig) = signatures.get(signature) { + return Some((*hash, *sig)); + } + } + return None; + } } #[cfg(test)] @@ -618,6 +633,16 @@ mod tests { ); } + #[test] + fn test_check_signature() { + let mint = Mint::new(1); + let bank = Bank::new(&mint); + let sig = Signature::default(); + bank.reserve_signature_with_last_id(&sig, &mint.last_id()) + .expect("reserve signature"); + assert_eq!(bank.check_signature(&sig), Some((mint.last_id(), sig))); + } + #[test] fn test_reject_old_last_id() { let mint = Mint::new(1); diff --git a/src/bin/wallet.rs b/src/bin/wallet.rs index 9e4f0ca02a..af98b32815 100644 --- a/src/bin/wallet.rs +++ b/src/bin/wallet.rs @@ -5,12 +5,12 @@ extern crate getopts; extern crate serde_json; extern crate solana; -use atty::{is, Stream}; use bincode::serialize; use getopts::Options; use solana::crdt::{get_ip_addr, ReplicatedData}; use solana::drone::DroneRequest; use solana::mint::Mint; +use solana::signature::Signature; use solana::thin_client::ThinClient; use std::env; use std::fs::File; @@ -32,25 +32,10 @@ fn print_usage(program: &str, opts: Options) { fn main() -> io::Result<()> { env_logger::init(); - if is(Stream::Stdin) { - eprintln!("nothing found on stdin, expected a json file"); - exit(1); - } - - let mut buffer = String::new(); - let num_bytes = io::stdin().read_to_string(&mut buffer).unwrap(); - if num_bytes == 0 { - eprintln!("empty file on stdin, expected a json file"); - exit(1); - } - - let id: Mint = serde_json::from_str(&buffer).unwrap_or_else(|e| { - eprintln!("failed to parse json: {}", e); - exit(1); - }); 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"); @@ -75,17 +60,25 @@ fn main() -> io::Result<()> { if matches.opt_present("d") { client_addr.set_ip(get_ip_addr().unwrap()); } - let leader = if matches.opt_present("l") { + let leader: ReplicatedData = 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: Mint = if matches.opt_present("m") { + read_mint(matches.opt_str("m").unwrap()) + } else { + read_mint(matches.opt_str("m").unwrap()) + }; + println!("{:?}", id); 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(); @@ -132,9 +125,12 @@ fn main() -> io::Result<()> { } Ok(balance) => { println!("Sending {:?} tokens to self...", balance); - let sig = - client.transfer(balance, &id.keypair(), id.pubkey(), &last_id); - println!("Sent transaction! Signature: {:?}", sig); + 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."); @@ -144,6 +140,23 @@ fn main() -> io::Result<()> { } } } + // Confirm the last client transaction by signature + "confirm" => match last_transaction_sig { + Some(sig) => { + let check_signature = client.check_signature(&sig); + match check_signature { + Some((id, _sig)) => { + println!("Signature found at bank id {:?}", id); + } + None => { + println!("Uh oh... Signature not found!"); + } + } + } + None => { + println!("No recent signature. Make a payment to get started."); + } + }, _ => { println!("Command {:?} not recognized", input.trim()); } @@ -159,8 +172,9 @@ 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 50 tokens"); + println!(" `airdrop` - Request a batch of tokens"); println!(" `pay` - Spend your tokens as fast as possible"); + println!(" `confirm` - Confirm your last payment by signature"); println!(""); } @@ -169,6 +183,11 @@ 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 mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result { let mut addr = client_addr.clone(); let port = addr.port(); diff --git a/src/request.rs b/src/request.rs index 7c5d23b52b..847c8b54e0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,7 +1,7 @@ //! The `request` module defines the messages for the thin client. use hash::Hash; -use signature::PublicKey; +use signature::{PublicKey, Signature}; #[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] #[derive(Serialize, Deserialize, Debug, Clone)] @@ -9,6 +9,7 @@ pub enum Request { GetBalance { key: PublicKey }, GetLastId, GetTransactionCount, + GetSignature { signature: Signature }, } impl Request { @@ -20,7 +21,17 @@ impl Request { #[derive(Serialize, Deserialize, Debug)] pub enum Response { - Balance { key: PublicKey, val: Option }, - LastId { id: Hash }, - TransactionCount { transaction_count: u64 }, + Balance { + key: PublicKey, + val: Option, + }, + LastId { + id: Hash, + }, + TransactionCount { + transaction_count: u64, + }, + SignatureStatus { + signature_status: Option<(Hash, Signature)>, + }, } diff --git a/src/request_processor.rs b/src/request_processor.rs index 2ce0ecf487..a247840a67 100644 --- a/src/request_processor.rs +++ b/src/request_processor.rs @@ -40,6 +40,12 @@ impl RequestProcessor { info!("Response::TransactionCount {:?}", rsp); Some(rsp) } + Request::GetSignature { signature } => { + let signature_status = self.bank.check_signature(&signature); + let rsp = (Response::SignatureStatus { signature_status }, rsp_addr); + info!("Response::Signature {:?}", rsp); + Some(rsp) + } } } diff --git a/src/thin_client.rs b/src/thin_client.rs index f0a3da38d8..3d9339679a 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -21,6 +21,7 @@ pub struct ThinClient { last_id: Option, transaction_count: u64, balances: HashMap>, + signature_status: Option<(Hash, Signature)>, } impl ThinClient { @@ -41,6 +42,7 @@ impl ThinClient { last_id: None, transaction_count: 0, balances: HashMap::new(), + signature_status: None, }; client } @@ -61,13 +63,24 @@ impl ThinClient { self.balances.insert(key, val); } Response::LastId { id } => { - info!("Response last_id {:?}", id); + trace!("Response last_id {:?}", id); self.last_id = Some(id); } Response::TransactionCount { transaction_count } => { - info!("Response transaction count {:?}", transaction_count); + trace!("Response transaction count {:?}", transaction_count); self.transaction_count = transaction_count; } + Response::SignatureStatus { signature_status } => { + self.signature_status = signature_status; + match signature_status { + Some((_, signature)) => { + trace!("Response found signature: {:?}", signature); + } + None => { + trace!("Response signature not found"); + } + } + } } } @@ -141,7 +154,7 @@ impl ThinClient { /// Request the last Entry ID from the server. This method blocks /// until the server sends a response. pub fn get_last_id(&mut self) -> Hash { - info!("get_last_id"); + trace!("get_last_id"); let req = Request::GetLastId; let data = serialize(&req).expect("serialize GetLastId in pub fn get_last_id"); let mut done = false; @@ -179,6 +192,28 @@ impl ThinClient { balance } + + /// Check a signature in the bank. This method blocks + /// until the server sends a response. + pub fn check_signature(&mut self, sig: &Signature) -> Option<(Hash, Signature)> { + trace!("check_signature"); + let req = Request::GetSignature { signature: *sig }; + let data = serialize(&req).expect("serialize GetSignature in pub fn check_signature"); + let mut done = false; + while !done { + self.requests_socket + .send_to(&data, &self.requests_addr) + .expect("buffer error in pub fn get_last_id"); + + if let Ok(resp) = self.recv_response() { + if let &Response::SignatureStatus { .. } = &resp { + done = true; + } + self.process_response(resp); + } + } + self.signature_status + } } #[cfg(test)] @@ -301,4 +336,53 @@ mod tests { t.join().unwrap(); } } + + #[test] + fn test_client_check_signature() { + logger::setup(); + let leader = TestNode::new(); + let alice = Mint::new(10_000); + let bank = Bank::new(&alice); + let bob_pubkey = KeyPair::new().pubkey(); + let exit = Arc::new(AtomicBool::new(false)); + + let server = Server::new_leader( + bank, + 0, + Some(Duration::from_millis(30)), + leader.data.clone(), + leader.sockets.requests, + leader.sockets.transaction, + leader.sockets.broadcast, + leader.sockets.respond, + leader.sockets.gossip, + exit.clone(), + sink(), + ); + sleep(Duration::from_millis(300)); + + let requests_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + requests_socket + .set_read_timeout(Some(Duration::new(5, 0))) + .unwrap(); + let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + let mut client = ThinClient::new( + leader.data.requests_addr, + requests_socket, + leader.data.transactions_addr, + transactions_socket, + ); + let last_id = client.get_last_id(); + let sig = client + .transfer(500, &alice.keypair(), bob_pubkey, &last_id) + .unwrap(); + sleep(Duration::from_millis(100)); + + assert_eq!(client.check_signature(&sig), Some((last_id, sig))); + + exit.store(true, Ordering::Relaxed); + for t in server.thread_hdls { + t.join().unwrap(); + } + } }