Add confirm command to wallet, and update RPU to check bank for a signature

This commit is contained in:
Tyera Eulberg 2018-06-28 12:58:33 -06:00 committed by Grimes
parent d680f6b3a5
commit 0112a24179
5 changed files with 175 additions and 30 deletions

View File

@ -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);

View File

@ -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<Signature> = 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<ThinClient> {
let mut addr = client_addr.clone();
let port = addr.port();

View File

@ -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<i64> },
LastId { id: Hash },
TransactionCount { transaction_count: u64 },
Balance {
key: PublicKey,
val: Option<i64>,
},
LastId {
id: Hash,
},
TransactionCount {
transaction_count: u64,
},
SignatureStatus {
signature_status: Option<(Hash, Signature)>,
},
}

View File

@ -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)
}
}
}

View File

@ -21,6 +21,7 @@ pub struct ThinClient {
last_id: Option<Hash>,
transaction_count: u64,
balances: HashMap<PublicKey, Option<i64>>,
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();
}
}
}