solana/src/wallet.rs

927 lines
34 KiB
Rust
Raw Normal View History

use bincode::{deserialize, serialize};
use bs58;
2018-09-19 12:44:16 -07:00
use chrono::prelude::*;
use clap::ArgMatches;
use crdt::NodeInfo;
use drone::DroneRequest;
use fullnode::Config;
2018-09-20 22:27:06 -07:00
use hash::Hash;
use reqwest;
use reqwest::header::CONTENT_TYPE;
use ring::rand::SystemRandom;
use ring::signature::Ed25519KeyPair;
2018-09-20 22:27:06 -07:00
use serde_json::{self, Value};
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{Error, ErrorKind, Write};
use std::mem::size_of;
use std::net::{Ipv4Addr, SocketAddr, TcpStream};
use std::path::Path;
use std::thread::sleep;
use std::time::Duration;
use std::{error, fmt, mem};
2018-09-20 22:27:06 -07:00
use transaction::Transaction;
2018-09-14 01:59:09 -07:00
#[derive(Debug, PartialEq)]
pub enum WalletCommand {
Address,
AirDrop(i64),
Balance,
Cancel(Pubkey),
Confirm(Signature),
2018-09-19 12:44:16 -07:00
// Pay(to, tokens, timestamp, timestamp_pubkey, witness(es), cancelable)
Pay(
i64,
Pubkey,
Option<DateTime<Utc>>,
Option<Pubkey>,
2018-09-19 12:44:16 -07:00
Option<Vec<Pubkey>>,
Option<bool>,
),
TimeElapsed(Pubkey, DateTime<Utc>),
Witness(Pubkey),
}
#[derive(Debug, Clone)]
pub enum WalletError {
CommandNotRecognized(String),
BadParameter(String),
2018-09-20 22:27:06 -07:00
RpcRequestError(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
}
}
pub struct WalletConfig {
pub leader: NodeInfo,
pub id: Keypair,
pub drone_addr: SocketAddr,
pub rpc_addr: String,
pub command: WalletCommand,
}
impl Default for WalletConfig {
fn default() -> WalletConfig {
let default_addr = socketaddr!(0, 8000);
WalletConfig {
leader: NodeInfo::new_with_socketaddr(&default_addr),
id: Keypair::new(),
drone_addr: default_addr,
rpc_addr: default_addr.to_string(),
command: WalletCommand::Balance,
}
}
}
pub fn parse_command(
pubkey: Pubkey,
matches: &ArgMatches,
) -> Result<WalletCommand, Box<error::Error>> {
let response = match matches.subcommand() {
2018-09-20 22:27:06 -07:00
("address", Some(_address_matches)) => Ok(WalletCommand::Address),
("airdrop", Some(airdrop_matches)) => {
let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?;
Ok(WalletCommand::AirDrop(tokens))
}
2018-09-20 22:27:06 -07:00
("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance),
2018-09-19 12:44:16 -07:00
("cancel", Some(cancel_matches)) => {
let pubkey_vec = bs58::decode(cancel_matches.value_of("process-id").unwrap())
.into_vec()
.expect("base58-encoded public key");
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", cancel_matches.usage());
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
}
let process_id = Pubkey::new(&pubkey_vec);
Ok(WalletCommand::Cancel(process_id))
}
2018-09-20 22:27:06 -07:00
("confirm", Some(confirm_matches)) => {
let signatures = bs58::decode(confirm_matches.value_of("signature").unwrap())
.into_vec()
.expect("base58-encoded signature");
if signatures.len() == mem::size_of::<Signature>() {
let signature = Signature::new(&signatures);
Ok(WalletCommand::Confirm(signature))
} else {
eprintln!("{}", confirm_matches.usage());
Err(WalletError::BadParameter("Invalid signature".to_string()))
}
}
("pay", Some(pay_matches)) => {
2018-09-19 12:44:16 -07:00
let tokens = pay_matches.value_of("tokens").unwrap().parse()?;
let to = if pay_matches.is_present("to") {
let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap())
.into_vec()
.expect("base58-encoded public key");
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", pay_matches.usage());
2018-09-19 12:44:16 -07:00
Err(WalletError::BadParameter(
"Invalid to public key".to_string(),
))?;
}
Pubkey::new(&pubkey_vec)
} else {
pubkey
};
2018-09-19 12:44:16 -07:00
let timestamp = if pay_matches.is_present("timestamp") {
// Parse input for serde_json
let date_string = if !pay_matches.value_of("timestamp").unwrap().contains("Z") {
format!("\"{}Z\"", pay_matches.value_of("timestamp").unwrap())
} else {
format!("\"{}\"", pay_matches.value_of("timestamp").unwrap())
};
Some(serde_json::from_str(&date_string)?)
} else {
None
};
let timestamp_pubkey = if pay_matches.is_present("timestamp-pubkey") {
let pubkey_vec = bs58::decode(pay_matches.value_of("timestamp-pubkey").unwrap())
.into_vec()
.expect("base58-encoded public key");
2018-09-19 12:44:16 -07:00
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", pay_matches.usage());
Err(WalletError::BadParameter(
"Invalid timestamp public key".to_string(),
))?;
}
Some(Pubkey::new(&pubkey_vec))
} else {
None
};
let witness_vec = if pay_matches.is_present("witness") {
let witnesses = pay_matches.values_of("witness").unwrap();
let mut collection = Vec::new();
for witness in witnesses {
let pubkey_vec = bs58::decode(witness)
.into_vec()
.expect("base58-encoded public key");
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", pay_matches.usage());
Err(WalletError::BadParameter(
"Invalid witness public key".to_string(),
))?;
}
collection.push(Pubkey::new(&pubkey_vec));
}
Some(collection)
} else {
None
};
let cancelable = if pay_matches.is_present("cancelable") {
Some(true)
} else {
None
};
Ok(WalletCommand::Pay(
tokens,
to,
timestamp,
timestamp_pubkey,
witness_vec,
cancelable,
))
}
("send-signature", Some(sig_matches)) => {
let pubkey_vec = bs58::decode(sig_matches.value_of("process-id").unwrap())
.into_vec()
.expect("base58-encoded public key");
2018-09-19 12:44:16 -07:00
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", sig_matches.usage());
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
}
let process_id = Pubkey::new(&pubkey_vec);
Ok(WalletCommand::Witness(process_id))
}
("send-timestamp", Some(timestamp_matches)) => {
let pubkey_vec = bs58::decode(timestamp_matches.value_of("process-id").unwrap())
.into_vec()
.expect("base58-encoded public key");
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", timestamp_matches.usage());
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
}
let process_id = Pubkey::new(&pubkey_vec);
let datetime = if timestamp_matches.is_present("datetime") {
// Parse input for serde_json
let date_string = if !timestamp_matches
.value_of("datetime")
.unwrap()
.contains("Z")
{
format!("\"{}Z\"", timestamp_matches.value_of("datetime").unwrap())
} else {
format!("\"{}\"", timestamp_matches.value_of("datetime").unwrap())
};
serde_json::from_str(&date_string)?
} else {
Utc::now()
};
Ok(WalletCommand::TimeElapsed(process_id, datetime))
}
("", None) => {
println!("{}", matches.usage());
Err(WalletError::CommandNotRecognized(
"no subcommand given".to_string(),
))
}
_ => unreachable!(),
}?;
Ok(response)
}
2018-09-20 22:27:06 -07:00
pub fn process_command(config: &WalletConfig) -> Result<String, Box<error::Error>> {
match config.command {
2018-09-20 22:27:06 -07:00
// Get address of this client
WalletCommand::Address => Ok(format!("{}", config.id.pubkey())),
// Request an airdrop from Solana Drone;
WalletCommand::AirDrop(tokens) => {
println!(
"Requesting airdrop of {:?} tokens from {}",
tokens, config.drone_addr
);
let params = json!(format!("{}", config.id.pubkey()));
let previous_balance = match WalletRpcRequest::GetBalance
2018-09-20 22:27:06 -07:00
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
.as_i64()
{
Some(tokens) => tokens,
None => Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?,
};
request_airdrop(&config.drone_addr, &config.id.pubkey(), tokens as u64)?;
// TODO: return airdrop Result from Drone instead of polling the
// network
let mut current_balance = previous_balance;
for _ in 0..20 {
sleep(Duration::from_millis(500));
let params = json!(format!("{}", config.id.pubkey()));
2018-09-20 22:27:06 -07:00
current_balance = WalletRpcRequest::GetBalance
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
.as_i64()
.unwrap_or(previous_balance);
if previous_balance != current_balance {
break;
}
println!(".");
}
if current_balance - previous_balance != tokens {
Err("Airdrop failed!")?;
}
Ok(format!("Your balance is: {:?}", current_balance))
}
// Check client balance
2018-09-20 22:27:06 -07:00
WalletCommand::Balance => {
println!("Balance requested...");
let params = json!(format!("{}", config.id.pubkey()));
2018-09-20 22:27:06 -07:00
let balance = WalletRpcRequest::GetBalance
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
.as_i64();
match balance {
Some(0) => Ok("No account found! Request an airdrop to get started.".to_string()),
Some(tokens) => Ok(format!("Your balance is: {:?}", tokens)),
None => Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?,
}
}
// Cancel a contract by contract Pubkey
2018-09-19 12:44:16 -07:00
WalletCommand::Cancel(pubkey) => {
println!("{:?}", pubkey);
Err(WalletError::BadParameter(
"Cancel not built yet".to_string(),
))?
}
// Confirm the last client transaction by signature
WalletCommand::Confirm(signature) => {
let params = json!(format!("{}", signature));
2018-09-20 22:27:06 -07:00
let confirmation = WalletRpcRequest::ConfirmTransaction
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
.as_bool();
match confirmation {
Some(b) => {
if b {
Ok("Confirmed".to_string())
} else {
Ok("Not found".to_string())
}
}
None => Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?,
}
}
// If client has positive balance, pay tokens to another address
WalletCommand::Pay(tokens, to, _, _, _, _) => {
2018-09-20 22:27:06 -07:00
let result = WalletRpcRequest::GetLastId.make_rpc_request(&config.rpc_addr, 1, None)?;
if result.as_str().is_none() {
Err(WalletError::RpcRequestError(
"Received bad last_id".to_string(),
))?
}
2018-09-20 22:27:06 -07:00
let last_id_str = result.as_str().unwrap();
let last_id_vec = bs58::decode(last_id_str)
.into_vec()
.map_err(|_| WalletError::RpcRequestError("Received bad last_id".to_string()))?;
let last_id = Hash::new(&last_id_vec);
let tx = Transaction::new(&config.id, to, tokens, last_id);
let serialized = serialize(&tx).unwrap();
let params = json!(serialized);
2018-09-20 22:27:06 -07:00
let signature = WalletRpcRequest::SendTransaction.make_rpc_request(
&config.rpc_addr,
2,
Some(params),
2018-09-20 22:27:06 -07:00
)?;
if signature.as_str().is_none() {
Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?
}
let signature_str = signature.as_str().unwrap();
2018-09-23 14:38:17 -07:00
Ok(signature_str.to_string())
}
// Apply time elapsed to contract
WalletCommand::TimeElapsed(pubkey, timestamp) => Err(WalletError::BadParameter(
"TimeElapsed not built yet".to_string(),
))?,
// Apply witness signature to contract
WalletCommand::Witness(pubkey) => Err(WalletError::BadParameter(
"Witness not built yet".to_string(),
))?,
}
}
pub fn read_leader(path: &str) -> Result<Config, WalletError> {
let file = File::open(path.to_string()).or_else(|err| {
Err(WalletError::BadParameter(format!(
"{}: Unable to open leader file: {}",
err, path
)))
})?;
serde_json::from_reader(file).or_else(|err| {
Err(WalletError::BadParameter(format!(
"{}: Failed to parse leader file: {}",
err, path
)))
})
}
pub fn request_airdrop(
drone_addr: &SocketAddr,
id: &Pubkey,
tokens: u64,
) -> Result<Signature, Error> {
// TODO: make this async tokio client
let mut stream = TcpStream::connect(drone_addr)?;
let req = DroneRequest::GetAirdrop {
airdrop_request_amount: tokens,
2018-08-09 08:03:08 -07:00
client_pubkey: *id,
};
let tx = serialize(&req).expect("serialize drone request");
stream.write_all(&tx)?;
let mut buffer = [0; size_of::<Signature>()];
stream
.read_exact(&mut buffer)
.or_else(|_| Err(Error::new(ErrorKind::Other, "Airdrop failed")))?;
let signature: Signature = deserialize(&buffer).or_else(|err| {
Err(Error::new(
ErrorKind::Other,
format!("deserialize signature in request_airdrop: {:?}", err),
))
})?;
// TODO: add timeout to this function, in case of unresponsive drone
Ok(signature)
}
pub fn gen_keypair_file(outfile: String) -> Result<String, Box<error::Error>> {
let rnd = SystemRandom::new();
let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rnd)?;
let serialized = serde_json::to_string(&pkcs8_bytes.to_vec())?;
if outfile != "-" {
if let Some(outdir) = Path::new(&outfile).parent() {
fs::create_dir_all(outdir)?;
}
let mut f = File::create(outfile)?;
f.write_all(&serialized.clone().into_bytes())?;
}
Ok(serialized)
}
2018-09-20 22:27:06 -07:00
pub enum WalletRpcRequest {
ConfirmTransaction,
GetAccountInfo,
GetBalance,
GetFinality,
GetLastId,
GetTransactionCount,
RequestAirdrop,
SendTransaction,
}
impl WalletRpcRequest {
fn make_rpc_request(
&self,
2018-09-23 14:38:17 -07:00
rpc_addr: &str,
2018-09-20 22:27:06 -07:00
id: u64,
params: Option<Value>,
2018-09-20 22:27:06 -07:00
) -> Result<Value, Box<error::Error>> {
let jsonrpc = "2.0";
let method = match self {
WalletRpcRequest::ConfirmTransaction => "confirmTransaction",
WalletRpcRequest::GetAccountInfo => "getAccountInfo",
WalletRpcRequest::GetBalance => "getBalance",
WalletRpcRequest::GetFinality => "getFinality",
WalletRpcRequest::GetLastId => "getLastId",
WalletRpcRequest::GetTransactionCount => "getTransactionCount",
WalletRpcRequest::RequestAirdrop => "requestAirdrop",
WalletRpcRequest::SendTransaction => "sendTransaction",
};
let client = reqwest::Client::new();
let mut request = json!({
"jsonrpc": jsonrpc,
"id": id,
"method": method,
});
2018-09-20 22:27:06 -07:00
if let Some(param_string) = params {
request["params"] = json!(vec![param_string]);
2018-09-20 22:27:06 -07:00
}
let mut response = client
.post(rpc_addr)
2018-09-20 22:27:06 -07:00
.header(CONTENT_TYPE, "application/json")
.body(request.to_string())
2018-09-20 22:27:06 -07:00
.send()?;
let json: Value = serde_json::from_str(&response.text()?)?;
if json["error"].is_object() {
Err(WalletError::RpcRequestError(format!(
"RPC Error response: {}",
serde_json::to_string(&json["error"]).unwrap()
)))?
}
Ok(json["result"].clone())
}
}
#[cfg(test)]
2018-09-14 01:59:09 -07:00
mod tests {
use super::*;
use bank::Bank;
use clap::{App, Arg, SubCommand};
use crdt::Node;
use drone::run_local_drone;
use fullnode::Fullnode;
use ledger::LedgerWriter;
use mint::Mint;
use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil};
2018-09-14 01:59:09 -07:00
use std::sync::mpsc::channel;
fn tmp_ledger(name: &str, mint: &Mint) -> String {
use std::env;
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
let keypair = Keypair::new();
let path = format!("{}/tmp-ledger-{}-{}", out_dir, name, keypair.pubkey());
let mut writer = LedgerWriter::open(&path, true).unwrap();
writer.write_entries(mint.create_entries()).unwrap();
path
}
#[test]
fn test_parse_command() {
let test_commands = App::new("test")
2018-09-19 12:44:16 -07:00
.subcommand(SubCommand::with_name("address").about("Get your public key"))
2018-09-14 01:59:09 -07:00
.subcommand(
SubCommand::with_name("airdrop")
.about("Request a batch of tokens")
.arg(
Arg::with_name("tokens")
2018-09-19 12:44:16 -07:00
.index(1)
.value_name("NUM")
2018-09-14 01:59:09 -07:00
.takes_value(true)
.required(true)
.help("The number of tokens to request"),
),
2018-09-19 12:44:16 -07:00
).subcommand(SubCommand::with_name("balance").about("Get your balance"))
.subcommand(
SubCommand::with_name("cancel")
.about("Cancel a transfer")
.arg(
Arg::with_name("process-id")
.index(1)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to cancel"),
),
).subcommand(
SubCommand::with_name("confirm")
.about("Confirm transaction by signature")
.arg(
Arg::with_name("signature")
.index(1)
.value_name("SIGNATURE")
.takes_value(true)
.required(true)
.help("The transaction signature to confirm"),
),
2018-09-14 16:25:14 -07:00
).subcommand(
2018-09-14 01:59:09 -07:00
SubCommand::with_name("pay")
.about("Send a payment")
.arg(
2018-09-19 12:44:16 -07:00
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
).arg(
2018-09-14 01:59:09 -07:00
Arg::with_name("tokens")
2018-09-19 12:44:16 -07:00
.index(2)
.value_name("NUM")
2018-09-14 01:59:09 -07:00
.takes_value(true)
.required(true)
.help("The number of tokens to send"),
2018-09-14 16:25:14 -07:00
).arg(
2018-09-19 12:44:16 -07:00
Arg::with_name("timestamp")
.long("after")
.value_name("DATETIME")
.allow_hyphen_values(true)
.takes_value(true)
.help("A timestamp after which transaction will execute"),
).arg(
Arg::with_name("timestamp-pubkey")
.long("require-timestamp-from")
2018-09-14 01:59:09 -07:00
.value_name("PUBKEY")
.takes_value(true)
2018-09-19 12:44:16 -07:00
.requires("timestamp")
.help("Require timestamp from this third party"),
).arg(
Arg::with_name("witness")
.long("require-signature-from")
.value_name("PUBKEY")
.takes_value(true)
.multiple(true)
.use_delimiter(true)
.help("Any third party signatures required to unlock the tokens"),
).arg(
Arg::with_name("cancellable")
.long("cancellable")
.takes_value(false)
.requires("witness"),
2018-09-14 01:59:09 -07:00
),
2018-09-14 16:25:14 -07:00
).subcommand(
2018-09-19 12:44:16 -07:00
SubCommand::with_name("send-signature")
.about("Send a signature to authorize a transfer")
2018-09-14 01:59:09 -07:00
.arg(
2018-09-19 12:44:16 -07:00
Arg::with_name("process-id")
2018-09-14 01:59:09 -07:00
.index(1)
2018-09-19 12:44:16 -07:00
.value_name("PROCESS_ID")
.takes_value(true)
2018-09-14 01:59:09 -07:00
.required(true)
2018-09-19 12:44:16 -07:00
.help("The process id of the transfer to authorize"),
2018-09-14 01:59:09 -07:00
),
2018-09-19 12:44:16 -07:00
).subcommand(
SubCommand::with_name("send-timestamp")
.about("Send a timestamp to unlock a transfer")
.arg(
Arg::with_name("process-id")
.index(1)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to unlock"),
).arg(
Arg::with_name("datetime")
.long("date")
.value_name("DATETIME")
.takes_value(true)
.help("Optional arbitrary timestamp to apply"),
),
);
let pubkey = Keypair::new().pubkey();
let pubkey_string = format!("{}", pubkey);
let witness0 = Keypair::new().pubkey();
let witness0_string = format!("{}", witness0);
let witness1 = Keypair::new().pubkey();
let witness1_string = format!("{}", witness1);
let dt = Utc.ymd(2018, 9, 19).and_hms(17, 30, 59);
2018-09-14 01:59:09 -07:00
2018-09-19 12:44:16 -07:00
// Test Airdrop Subcommand
2018-09-14 01:59:09 -07:00
let test_airdrop = test_commands
.clone()
2018-09-19 12:44:16 -07:00
.get_matches_from(vec!["test", "airdrop", "50"]);
2018-09-14 01:59:09 -07:00
assert_eq!(
parse_command(pubkey, &test_airdrop).unwrap(),
WalletCommand::AirDrop(50)
);
let test_bad_airdrop = test_commands
.clone()
2018-09-19 12:44:16 -07:00
.get_matches_from(vec!["test", "airdrop", "notint"]);
2018-09-14 01:59:09 -07:00
assert!(parse_command(pubkey, &test_bad_airdrop).is_err());
2018-09-19 12:44:16 -07:00
// Test Cancel Subcommand
let test_cancel =
test_commands
.clone()
.get_matches_from(vec!["test", "cancel", &pubkey_string]);
2018-09-14 01:59:09 -07:00
assert_eq!(
2018-09-19 12:44:16 -07:00
parse_command(pubkey, &test_cancel).unwrap(),
WalletCommand::Cancel(pubkey)
2018-09-14 01:59:09 -07:00
);
2018-09-19 12:44:16 -07:00
// Test Confirm Subcommand
2018-09-14 01:59:09 -07:00
let signature = Signature::new(&vec![1; 64]);
let signature_string = format!("{:?}", signature);
let test_confirm =
test_commands
.clone()
.get_matches_from(vec!["test", "confirm", &signature_string]);
assert_eq!(
parse_command(pubkey, &test_confirm).unwrap(),
WalletCommand::Confirm(signature)
);
2018-09-19 12:44:16 -07:00
let test_bad_signature = test_commands
.clone()
.get_matches_from(vec!["test", "confirm", "deadbeef"]);
2018-09-14 01:59:09 -07:00
assert!(parse_command(pubkey, &test_bad_signature).is_err());
2018-09-19 12:44:16 -07:00
// Test Simple Pay Subcommand
let test_pay =
test_commands
.clone()
.get_matches_from(vec!["test", "pay", &pubkey_string, "50"]);
assert_eq!(
parse_command(pubkey, &test_pay).unwrap(),
WalletCommand::Pay(50, pubkey, None, None, None, None)
);
let test_bad_pubkey = test_commands
.clone()
.get_matches_from(vec!["test", "pay", "deadbeef", "50"]);
assert!(parse_command(pubkey, &test_bad_pubkey).is_err());
// Test Pay Subcommand w/ Witness
let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![
"test",
"pay",
&pubkey_string,
"50",
"--require-signature-from",
&witness0_string,
"--require-signature-from",
&witness1_string,
]);
assert_eq!(
parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(),
WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0, witness1]), None)
);
let test_pay_single_witness = test_commands.clone().get_matches_from(vec![
"test",
"pay",
&pubkey_string,
"50",
"--require-signature-from",
&witness0_string,
]);
assert_eq!(
parse_command(pubkey, &test_pay_single_witness).unwrap(),
WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0]), None)
);
// Test Pay Subcommand w/ Timestamp
let test_pay_timestamp = test_commands.clone().get_matches_from(vec![
"test",
"pay",
&pubkey_string,
"50",
"--after",
"2018-09-19T17:30:59",
"--require-timestamp-from",
&witness0_string,
]);
assert_eq!(
parse_command(pubkey, &test_pay_timestamp).unwrap(),
WalletCommand::Pay(50, pubkey, Some(dt), Some(witness0), None, None)
);
// Test Send-Signature Subcommand
let test_send_signature =
test_commands
.clone()
.get_matches_from(vec!["test", "send-signature", &pubkey_string]);
assert_eq!(
parse_command(pubkey, &test_send_signature).unwrap(),
WalletCommand::Witness(pubkey)
);
let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![
"test",
"pay",
&pubkey_string,
"50",
"--after",
"2018-09-19T17:30:59",
"--require-signature-from",
&witness0_string,
"--require-timestamp-from",
&witness0_string,
"--require-signature-from",
&witness1_string,
]);
assert_eq!(
parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(),
WalletCommand::Pay(
50,
pubkey,
Some(dt),
Some(witness0),
Some(vec![witness0, witness1]),
None
)
);
// Test Send-Timestamp Subcommand
let test_send_timestamp = test_commands.clone().get_matches_from(vec![
"test",
"send-timestamp",
&pubkey_string,
"--date",
"2018-09-19T17:30:59",
]);
assert_eq!(
parse_command(pubkey, &test_send_timestamp).unwrap(),
WalletCommand::TimeElapsed(pubkey, dt)
);
let test_bad_timestamp = test_commands.clone().get_matches_from(vec![
"test",
"send-timestamp",
&pubkey_string,
"--date",
"20180919T17:30:59",
]);
assert!(parse_command(pubkey, &test_bad_timestamp).is_err());
2018-09-14 01:59:09 -07:00
}
#[test]
fn test_process_command() {
let leader_keypair = Keypair::new();
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
let alice = Mint::new(10_000_000);
let bank = Bank::new(&alice);
let bob_pubkey = Keypair::new().pubkey();
let leader_data = leader.info.clone();
let leader_data1 = leader.info.clone();
let ledger_path = tmp_ledger("thin_client", &alice);
let mut config = WalletConfig::default();
2018-09-20 22:38:59 -07:00
let rpc_port = 12345; // Needs to be distinct known number to not conflict with following test
2018-09-14 01:59:09 -07:00
let _server = Fullnode::new_with_bank(
leader_keypair,
bank,
0,
&[],
leader,
None,
&ledger_path,
2018-09-14 01:59:09 -07:00
false,
None,
2018-09-20 22:38:59 -07:00
Some(rpc_port),
2018-09-14 01:59:09 -07:00
);
sleep(Duration::from_millis(200));
let (sender, receiver) = channel();
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
config.drone_addr = receiver.recv().unwrap();
config.leader = leader_data1;
2018-09-20 22:27:06 -07:00
let mut rpc_addr = leader_data.contact_info.ncp;
2018-09-20 22:38:59 -07:00
rpc_addr.set_port(rpc_port);
config.rpc_addr = format!("http://{}", rpc_addr.to_string());
2018-09-20 22:27:06 -07:00
let tokens = 50;
config.command = WalletCommand::AirDrop(tokens);
assert_eq!(
2018-09-20 22:27:06 -07:00
process_command(&config).unwrap(),
format!("Your balance is: {:?}", tokens)
);
2018-09-14 01:59:09 -07:00
config.command = WalletCommand::Balance;
assert_eq!(
2018-09-20 22:27:06 -07:00
process_command(&config).unwrap(),
format!("Your balance is: {:?}", tokens)
);
2018-09-14 01:59:09 -07:00
config.command = WalletCommand::Address;
assert_eq!(
2018-09-20 22:27:06 -07:00
process_command(&config).unwrap(),
format!("{}", config.id.pubkey())
);
2018-09-14 01:59:09 -07:00
2018-09-19 12:44:16 -07:00
config.command = WalletCommand::Pay(10, bob_pubkey, None, None, None, None);
2018-09-20 22:27:06 -07:00
let sig_response = process_command(&config);
assert!(sig_response.is_ok());
sleep(Duration::from_millis(100));
let signatures = bs58::decode(sig_response.unwrap())
.into_vec()
.expect("base58-encoded signature");
let signature = Signature::new(&signatures);
config.command = WalletCommand::Confirm(signature);
2018-09-20 22:27:06 -07:00
assert_eq!(process_command(&config).unwrap(), "Confirmed");
2018-09-14 01:59:09 -07:00
config.command = WalletCommand::Balance;
assert_eq!(
2018-09-20 22:27:06 -07:00
process_command(&config).unwrap(),
format!("Your balance is: {:?}", tokens - 10)
);
2018-09-14 01:59:09 -07:00
}
#[test]
fn test_request_airdrop() {
let leader_keypair = Keypair::new();
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
let alice = Mint::new(10_000_000);
let bank = Bank::new(&alice);
let bob_pubkey = Keypair::new().pubkey();
let leader_data = leader.info.clone();
let ledger_path = tmp_ledger("thin_client", &alice);
2018-09-20 22:38:59 -07:00
let rpc_port = 11111; // Needs to be distinct known number to not conflict with previous test
2018-09-14 01:59:09 -07:00
let _server = Fullnode::new_with_bank(
leader_keypair,
bank,
0,
&[],
leader,
None,
&ledger_path,
2018-09-14 01:59:09 -07:00
false,
None,
2018-09-20 22:38:59 -07:00
Some(rpc_port),
2018-09-14 01:59:09 -07:00
);
sleep(Duration::from_millis(200));
let (sender, receiver) = channel();
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
let drone_addr = receiver.recv().unwrap();
let mut addr = leader_data.contact_info.ncp;
addr.set_port(rpc_port);
let rpc_addr = format!("http://{}", addr.to_string());
2018-09-20 22:27:06 -07:00
2018-09-14 01:59:09 -07:00
let signature = request_airdrop(&drone_addr, &bob_pubkey, 50);
assert!(signature.is_ok());
let params = json!(format!("{}", signature.unwrap()));
2018-09-20 22:27:06 -07:00
let confirmation = WalletRpcRequest::ConfirmTransaction
.make_rpc_request(&rpc_addr, 1, Some(params))
.unwrap()
.as_bool()
.unwrap();
assert!(confirmation);
2018-09-14 01:59:09 -07:00
}
#[test]
fn test_gen_keypair_file() {
let outfile = "test_gen_keypair_file.json";
let serialized_keypair = gen_keypair_file(outfile.to_string()).unwrap();
let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
assert!(Path::new(outfile).exists());
assert_eq!(keypair_vec, read_pkcs8(&outfile).unwrap());
assert!(read_keypair(&outfile).is_ok());
assert_eq!(
read_keypair(&outfile).unwrap().pubkey().as_ref().len(),
mem::size_of::<Pubkey>()
);
fs::remove_file(outfile).unwrap();
assert!(!Path::new(outfile).exists());
}
2018-09-14 01:59:09 -07:00
}