Add confirm command to wallet, and update RPU to check bank for a signature
This commit is contained in:
parent
d680f6b3a5
commit
0112a24179
27
src/bank.rs
27
src/bank.rs
|
@ -128,7 +128,10 @@ impl Bank {
|
||||||
/// Return the last entry ID registered.
|
/// Return the last entry ID registered.
|
||||||
pub fn last_id(&self) -> Hash {
|
pub fn last_id(&self) -> Hash {
|
||||||
let last_ids = self.last_ids.read().expect("'last_ids' read lock");
|
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
|
*last_item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,6 +433,18 @@ impl Bank {
|
||||||
pub fn transaction_count(&self) -> usize {
|
pub fn transaction_count(&self) -> usize {
|
||||||
self.transaction_count.load(Ordering::Relaxed)
|
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)]
|
#[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]
|
#[test]
|
||||||
fn test_reject_old_last_id() {
|
fn test_reject_old_last_id() {
|
||||||
let mint = Mint::new(1);
|
let mint = Mint::new(1);
|
||||||
|
|
|
@ -5,12 +5,12 @@ extern crate getopts;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate solana;
|
extern crate solana;
|
||||||
|
|
||||||
use atty::{is, Stream};
|
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use getopts::Options;
|
use getopts::Options;
|
||||||
use solana::crdt::{get_ip_addr, ReplicatedData};
|
use solana::crdt::{get_ip_addr, ReplicatedData};
|
||||||
use solana::drone::DroneRequest;
|
use solana::drone::DroneRequest;
|
||||||
use solana::mint::Mint;
|
use solana::mint::Mint;
|
||||||
|
use solana::signature::Signature;
|
||||||
use solana::thin_client::ThinClient;
|
use solana::thin_client::ThinClient;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -32,25 +32,10 @@ fn print_usage(program: &str, opts: Options) {
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
env_logger::init();
|
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();
|
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("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");
|
||||||
|
@ -75,17 +60,25 @@ fn main() -> io::Result<()> {
|
||||||
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: ReplicatedData = 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: 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 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;
|
||||||
|
|
||||||
// Start the a, generate a random client keypair, and show user possible commands
|
// Start the a, generate a random client keypair, and show user possible commands
|
||||||
display_actions();
|
display_actions();
|
||||||
|
|
||||||
|
@ -132,9 +125,12 @@ fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
Ok(balance) => {
|
Ok(balance) => {
|
||||||
println!("Sending {:?} tokens to self...", balance);
|
println!("Sending {:?} tokens to self...", balance);
|
||||||
let sig =
|
let sig = client
|
||||||
client.transfer(balance, &id.keypair(), id.pubkey(), &last_id);
|
.transfer(balance, &id.keypair(), id.pubkey(), &last_id)
|
||||||
println!("Sent transaction! Signature: {:?}", sig);
|
.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 => {
|
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.");
|
||||||
|
@ -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());
|
println!("Command {:?} not recognized", input.trim());
|
||||||
}
|
}
|
||||||
|
@ -159,8 +172,9 @@ fn display_actions() {
|
||||||
println!("");
|
println!("");
|
||||||
println!("What would you like to do? Type a command:");
|
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 50 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");
|
||||||
|
println!(" `confirm` - Confirm your last payment by signature");
|
||||||
println!("");
|
println!("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +183,11 @@ 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 {
|
||||||
|
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> {
|
fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result<ThinClient> {
|
||||||
let mut addr = client_addr.clone();
|
let mut addr = client_addr.clone();
|
||||||
let port = addr.port();
|
let port = addr.port();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! The `request` module defines the messages for the thin client.
|
//! The `request` module defines the messages for the thin client.
|
||||||
|
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use signature::PublicKey;
|
use signature::{PublicKey, Signature};
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
|
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -9,6 +9,7 @@ pub enum Request {
|
||||||
GetBalance { key: PublicKey },
|
GetBalance { key: PublicKey },
|
||||||
GetLastId,
|
GetLastId,
|
||||||
GetTransactionCount,
|
GetTransactionCount,
|
||||||
|
GetSignature { signature: Signature },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
|
@ -20,7 +21,17 @@ impl Request {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
Balance { key: PublicKey, val: Option<i64> },
|
Balance {
|
||||||
LastId { id: Hash },
|
key: PublicKey,
|
||||||
TransactionCount { transaction_count: u64 },
|
val: Option<i64>,
|
||||||
|
},
|
||||||
|
LastId {
|
||||||
|
id: Hash,
|
||||||
|
},
|
||||||
|
TransactionCount {
|
||||||
|
transaction_count: u64,
|
||||||
|
},
|
||||||
|
SignatureStatus {
|
||||||
|
signature_status: Option<(Hash, Signature)>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,12 @@ impl RequestProcessor {
|
||||||
info!("Response::TransactionCount {:?}", rsp);
|
info!("Response::TransactionCount {:?}", rsp);
|
||||||
Some(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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ pub struct ThinClient {
|
||||||
last_id: Option<Hash>,
|
last_id: Option<Hash>,
|
||||||
transaction_count: u64,
|
transaction_count: u64,
|
||||||
balances: HashMap<PublicKey, Option<i64>>,
|
balances: HashMap<PublicKey, Option<i64>>,
|
||||||
|
signature_status: Option<(Hash, Signature)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThinClient {
|
impl ThinClient {
|
||||||
|
@ -41,6 +42,7 @@ impl ThinClient {
|
||||||
last_id: None,
|
last_id: None,
|
||||||
transaction_count: 0,
|
transaction_count: 0,
|
||||||
balances: HashMap::new(),
|
balances: HashMap::new(),
|
||||||
|
signature_status: None,
|
||||||
};
|
};
|
||||||
client
|
client
|
||||||
}
|
}
|
||||||
|
@ -61,13 +63,24 @@ impl ThinClient {
|
||||||
self.balances.insert(key, val);
|
self.balances.insert(key, val);
|
||||||
}
|
}
|
||||||
Response::LastId { id } => {
|
Response::LastId { id } => {
|
||||||
info!("Response last_id {:?}", id);
|
trace!("Response last_id {:?}", id);
|
||||||
self.last_id = Some(id);
|
self.last_id = Some(id);
|
||||||
}
|
}
|
||||||
Response::TransactionCount { transaction_count } => {
|
Response::TransactionCount { transaction_count } => {
|
||||||
info!("Response transaction count {:?}", transaction_count);
|
trace!("Response transaction count {:?}", transaction_count);
|
||||||
self.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
|
/// Request the last Entry ID from the server. This method blocks
|
||||||
/// until the server sends a response.
|
/// until the server sends a response.
|
||||||
pub fn get_last_id(&mut self) -> Hash {
|
pub fn get_last_id(&mut self) -> Hash {
|
||||||
info!("get_last_id");
|
trace!("get_last_id");
|
||||||
let req = Request::GetLastId;
|
let req = Request::GetLastId;
|
||||||
let data = serialize(&req).expect("serialize GetLastId in pub fn get_last_id");
|
let data = serialize(&req).expect("serialize GetLastId in pub fn get_last_id");
|
||||||
let mut done = false;
|
let mut done = false;
|
||||||
|
@ -179,6 +192,28 @@ impl ThinClient {
|
||||||
|
|
||||||
balance
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -301,4 +336,53 @@ mod tests {
|
||||||
t.join().unwrap();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue