solana/src/wallet.rs

1594 lines
59 KiB
Rust
Raw Normal View History

use bincode::{deserialize, serialize};
2018-10-23 16:15:22 -07:00
use bpf_loader;
use bs58;
use budget_program::BudgetState;
use budget_transaction::BudgetTransaction;
2018-09-19 12:44:16 -07:00
use chrono::prelude::*;
use clap::ArgMatches;
2018-11-09 14:10:44 -08:00
use drone::{DroneRequest, DRONE_PORT};
2018-10-22 21:21:33 -07:00
use elf;
use fullnode::Config;
2018-09-20 22:27:06 -07:00
use hash::Hash;
2018-10-22 21:21:33 -07:00
use loader_transaction::LoaderTransaction;
use ring::rand::SystemRandom;
use ring::signature::Ed25519KeyPair;
2018-10-24 09:01:19 -07:00
use rpc::RpcSignatureStatus;
use rpc_request::RpcRequest;
use serde_json;
2018-09-26 16:55:36 -07:00
use signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::pubkey::Pubkey;
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;
2018-10-24 09:01:19 -07:00
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
use std::{error, fmt, mem};
use system_transaction::SystemTransaction;
use thin_client::poll_gossip_for_leader;
use transaction::Transaction;
2018-10-22 21:21:33 -07:00
const PLATFORM_SECTION_C: &str = ".text.entrypoint";
const USERDATA_CHUNK_SIZE: usize = 256;
2018-09-14 01:59:09 -07:00
#[derive(Debug, PartialEq)]
pub enum WalletCommand {
Address,
AirDrop(u64),
Balance,
Cancel(Pubkey),
Confirm(Signature),
2018-10-22 21:21:33 -07:00
Deploy(String),
GetTransactionCount,
// Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable)
Pay(
u64,
Pubkey,
Option<DateTime<Utc>>,
Option<Pubkey>,
2018-09-19 12:44:16 -07:00
Option<Vec<Pubkey>>,
Option<Pubkey>,
),
// TimeElapsed(to, process_id, timestamp)
TimeElapsed(Pubkey, Pubkey, DateTime<Utc>),
2018-09-24 09:23:16 -07:00
// Witness(to, process_id)
Witness(Pubkey, Pubkey),
}
#[derive(Debug, Clone)]
pub enum WalletError {
CommandNotRecognized(String),
BadParameter(String),
2018-10-22 21:21:33 -07:00
DynamicProgramError(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 id: Keypair,
pub command: WalletCommand,
2018-11-09 14:10:44 -08:00
pub network: SocketAddr,
pub timeout: Option<u64>,
pub proxy: Option<String>,
}
impl Default for WalletConfig {
fn default() -> WalletConfig {
let default_addr = socketaddr!(0, 8000);
WalletConfig {
id: Keypair::new(),
command: WalletCommand::Balance,
2018-11-09 14:10:44 -08:00
network: default_addr,
timeout: None,
proxy: None,
}
}
}
2018-11-09 14:10:44 -08:00
impl WalletConfig {
pub fn drone_addr(&self, tpu_addr: SocketAddr) -> SocketAddr {
2018-11-09 14:10:44 -08:00
let mut drone_addr = tpu_addr;
drone_addr.set_port(DRONE_PORT);
drone_addr
}
2018-11-09 14:10:44 -08:00
}
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()))
}
}
2018-10-22 21:21:33 -07:00
("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy(
deploy_matches
.value_of("program-location")
.unwrap()
.to_string(),
)),
("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount),
("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
2018-09-21 20:48:46 -07:00
let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') {
2018-09-19 12:44:16 -07:00
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(pubkey)
2018-09-19 12:44:16 -07:00
} else {
None
};
Ok(WalletCommand::Pay(
tokens,
to,
timestamp,
timestamp_pubkey,
witness_vec,
cancelable,
))
}
("send-signature", Some(sig_matches)) => {
2018-09-24 09:23:16 -07:00
let pubkey_vec = bs58::decode(sig_matches.value_of("to").unwrap())
.into_vec()
.expect("base58-encoded public key");
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", sig_matches.usage());
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
}
let to = Pubkey::new(&pubkey_vec);
2018-09-19 12:44:16 -07:00
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);
2018-09-24 09:23:16 -07:00
Ok(WalletCommand::Witness(to, process_id))
2018-09-19 12:44:16 -07:00
}
("send-timestamp", Some(timestamp_matches)) => {
let pubkey_vec = bs58::decode(timestamp_matches.value_of("to").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 to = Pubkey::new(&pubkey_vec);
2018-09-19 12:44:16 -07:00
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 dt = if timestamp_matches.is_present("datetime") {
2018-09-19 12:44:16 -07:00
// Parse input for serde_json
let date_string = if !timestamp_matches
.value_of("datetime")
.unwrap()
2018-09-21 20:48:46 -07:00
.contains('Z')
2018-09-19 12:44:16 -07:00
{
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(to, process_id, dt))
}
("", None) => {
2018-09-24 09:23:16 -07:00
eprintln!("{}", 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>> {
2018-11-09 15:37:34 -08:00
if let WalletCommand::Address = config.command {
// Get address of this client
2018-11-09 15:37:34 -08:00
return Ok(format!("{}", config.id.pubkey()));
}
let leader = poll_gossip_for_leader(config.network, config.timeout)?;
2018-11-09 16:03:53 -08:00
let leader_info = &leader.contact_info;
let drone_addr = config.drone_addr(leader_info.tpu);
let rpc_addr = config
.proxy
.clone()
.unwrap_or_else(|| leader_info.rpc.to_string());
match config.command {
2018-09-20 22:27:06 -07:00
// Get address of this client
WalletCommand::Address => unreachable!(),
// Request an airdrop from Solana Drone;
WalletCommand::AirDrop(tokens) => {
println!(
"Requesting airdrop of {:?} tokens from {}",
tokens, drone_addr
);
let params = json!(format!("{}", config.id.pubkey()));
let previous_balance = match RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))?
.as_u64()
{
Some(tokens) => tokens,
None => Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?,
};
request_airdrop(&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()));
current_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))?
.as_u64()
.unwrap_or(previous_balance);
2018-11-01 09:14:13 -07:00
if previous_balance < current_balance {
break;
}
println!(".");
}
2018-11-01 09:14:13 -07:00
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()));
let balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))?
.as_u64();
2018-09-20 22:27:06 -07:00
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) => {
let last_id = get_last_id(&rpc_addr)?;
let tx =
Transaction::budget_new_signature(&config.id, pubkey, config.id.pubkey(), last_id);
let signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-21 20:48:46 -07:00
Ok(signature_str.to_string())
2018-09-19 12:44:16 -07:00
}
// Confirm the last client transaction by signature
WalletCommand::Confirm(signature) => {
let params = json!(format!("{}", signature));
let confirmation = RpcRequest::ConfirmTransaction
.make_rpc_request(&rpc_addr, 1, Some(params))?
2018-09-20 22:27:06 -07:00
.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(),
))?,
}
}
2018-10-22 21:21:33 -07:00
// Deploy a custom program to the chain
WalletCommand::Deploy(ref program_location) => {
let params = json!(format!("{}", config.id.pubkey()));
let balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))?
.as_u64();
2018-10-22 21:21:33 -07:00
if let Some(tokens) = balance {
2018-10-23 16:15:22 -07:00
if tokens < 1 {
2018-10-22 21:21:33 -07:00
Err(WalletError::DynamicProgramError(
"Insufficient funds".to_string(),
))?
}
}
let last_id = get_last_id(&rpc_addr)?;
2018-10-22 21:21:33 -07:00
let program = Keypair::new();
let program_userdata = elf::File::open_path(program_location)
.map_err(|_| {
WalletError::DynamicProgramError("Could not parse program file".to_string())
})?.get_section(PLATFORM_SECTION_C)
.ok_or_else(|| {
WalletError::DynamicProgramError(
"Could not find entrypoint in program file".to_string(),
)
})?.data
.clone();
let tx = Transaction::system_create(
&config.id,
program.pubkey(),
last_id,
1,
program_userdata.len() as u64,
2018-10-23 16:15:22 -07:00
bpf_loader::id(),
2018-10-22 21:21:33 -07:00
0,
);
send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| {
2018-10-24 09:01:19 -07:00
WalletError::DynamicProgramError("Program allocate space failed".to_string())
})?;
2018-10-22 21:21:33 -07:00
let mut offset = 0;
2018-10-24 09:01:19 -07:00
for chunk in program_userdata.chunks(USERDATA_CHUNK_SIZE) {
let tx = Transaction::loader_write(
2018-10-24 09:01:19 -07:00
&program,
bpf_loader::id(),
offset,
chunk.to_vec(),
last_id,
0,
);
send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| {
2018-10-24 09:01:19 -07:00
WalletError::DynamicProgramError(format!(
"Program write failed at offset {:?}",
offset
))
})?;
offset += USERDATA_CHUNK_SIZE as u32;
2018-10-22 21:21:33 -07:00
}
2018-10-24 09:01:19 -07:00
let last_id = get_last_id(&rpc_addr)?;
let tx = Transaction::loader_finalize(&program, bpf_loader::id(), last_id, 0);
send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| {
2018-10-24 09:01:19 -07:00
WalletError::DynamicProgramError("Program finalize transaction failed".to_string())
})?;
let tx = Transaction::system_spawn(&program, last_id, 0);
send_and_confirm_tx(&rpc_addr, &tx).map_err(|_| {
2018-10-24 09:01:19 -07:00
WalletError::DynamicProgramError("Program spawn failed".to_string())
})?;
Ok(json!({
"programId": format!("{}", program.pubkey()),
}).to_string())
2018-10-22 21:21:33 -07:00
}
WalletCommand::GetTransactionCount => {
let transaction_count = RpcRequest::GetTransactionCount
.make_rpc_request(&rpc_addr, 1, None)?
.as_u64();
match transaction_count {
Some(count) => Ok(count.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, timestamp, timestamp_pubkey, ref witnesses, cancelable) => {
let last_id = get_last_id(&rpc_addr)?;
2018-09-20 22:27:06 -07:00
if timestamp == None && *witnesses == None {
let tx = Transaction::system_new(&config.id, to, tokens, last_id);
let signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-21 20:48:46 -07:00
Ok(signature_str.to_string())
} else if *witnesses == None {
let dt = timestamp.unwrap();
let dt_pubkey = match timestamp_pubkey {
Some(pubkey) => pubkey,
None => config.id.pubkey(),
};
let contract_funds = Keypair::new();
let contract_state = Keypair::new();
let budget_program_id = BudgetState::id();
// Create account for contract funds
let tx = Transaction::system_create(
&config.id,
contract_funds.pubkey(),
last_id,
tokens,
0,
budget_program_id,
2018-09-24 09:23:16 -07:00
0,
);
let _signature_str = send_tx(&rpc_addr, &tx)?;
// Create account for contract state
let tx = Transaction::system_create(
2018-09-21 18:51:42 -07:00
&config.id,
contract_state.pubkey(),
last_id,
1,
196,
budget_program_id,
2018-09-24 09:23:16 -07:00
0,
);
let _signature_str = send_tx(&rpc_addr, &tx)?;
// Initializing contract
let tx = Transaction::budget_new_on_date(
&contract_funds,
2018-09-21 18:51:42 -07:00
to,
contract_state.pubkey(),
2018-09-21 18:51:42 -07:00
dt,
dt_pubkey,
cancelable,
2018-09-21 18:51:42 -07:00
tokens,
last_id,
);
let signature_str = send_tx(&rpc_addr, &tx)?;
Ok(json!({
"signature": signature_str,
"processId": format!("{}", contract_state.pubkey()),
}).to_string())
} else if timestamp == None {
let last_id = get_last_id(&rpc_addr)?;
2018-09-24 09:23:16 -07:00
let witness = if let Some(ref witness_vec) = *witnesses {
witness_vec[0]
} else {
Err(WalletError::BadParameter(
"Could not parse required signature pubkey(s)".to_string(),
))?
};
let contract_funds = Keypair::new();
let contract_state = Keypair::new();
let budget_program_id = BudgetState::id();
// Create account for contract funds
let tx = Transaction::system_create(
&config.id,
contract_funds.pubkey(),
last_id,
tokens,
0,
budget_program_id,
0,
);
let _signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-24 09:23:16 -07:00
// Create account for contract state
let tx = Transaction::system_create(
&config.id,
contract_state.pubkey(),
last_id,
1,
196,
budget_program_id,
0,
);
let _signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-24 09:23:16 -07:00
// Initializing contract
let tx = Transaction::budget_new_when_signed(
&contract_funds,
to,
contract_state.pubkey(),
witness,
cancelable,
tokens,
last_id,
);
let signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-24 09:23:16 -07:00
Ok(json!({
"signature": signature_str,
"processId": format!("{}", contract_state.pubkey()),
}).to_string())
} else {
2018-09-24 09:40:23 -07:00
Ok("Combo transactions not yet handled".to_string())
}
}
// Apply time elapsed to contract
WalletCommand::TimeElapsed(to, pubkey, dt) => {
2018-09-24 09:23:16 -07:00
let params = json!(format!("{}", config.id.pubkey()));
let balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))?
.as_u64();
2018-09-24 09:23:16 -07:00
if let Some(0) = balance {
request_airdrop(&drone_addr, &config.id.pubkey(), 1)?;
2018-09-24 09:23:16 -07:00
}
let last_id = get_last_id(&rpc_addr)?;
2018-09-24 09:23:16 -07:00
let tx = Transaction::budget_new_timestamp(&config.id, pubkey, to, dt, last_id);
let signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-20 22:27:06 -07:00
2018-09-23 14:38:17 -07:00
Ok(signature_str.to_string())
}
// Apply witness signature to contract
2018-09-24 09:23:16 -07:00
WalletCommand::Witness(to, pubkey) => {
let last_id = get_last_id(&rpc_addr)?;
2018-09-24 09:23:16 -07:00
let params = json!(format!("{}", config.id.pubkey()));
let balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))?
.as_u64();
2018-09-24 09:23:16 -07:00
if let Some(0) = balance {
request_airdrop(&drone_addr, &config.id.pubkey(), 1)?;
}
2018-09-24 09:23:16 -07:00
let tx = Transaction::budget_new_signature(&config.id, pubkey, to, last_id);
let signature_str = send_tx(&rpc_addr, &tx)?;
2018-09-21 20:48:46 -07:00
Ok(signature_str.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_timeout(drone_addr, Duration::new(3, 0))?;
stream.set_read_timeout(Some(Duration::new(10, 0)))?;
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| {
info!("request_airdrop: read_exact error: {:?}", err);
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),
))
})?;
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-11-09 15:37:34 -08:00
fn get_last_id(rpc_addr: &str) -> Result<Hash, Box<error::Error>> {
let result = RpcRequest::GetLastId.make_rpc_request(&rpc_addr, 1, None)?;
2018-09-22 10:33:06 -07:00
if result.as_str().is_none() {
Err(WalletError::RpcRequestError(
"Received bad last_id".to_string(),
))?
}
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()))?;
Ok(Hash::new(&last_id_vec))
}
2018-11-09 15:37:34 -08:00
fn send_tx(rpc_addr: &str, tx: &Transaction) -> Result<String, Box<error::Error>> {
let serialized = serialize(tx).unwrap();
let params = json!(serialized);
let signature = RpcRequest::SendTransaction.make_rpc_request(&rpc_addr, 2, Some(params))?;
if signature.as_str().is_none() {
Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?
}
Ok(signature.as_str().unwrap().to_string())
}
2018-11-09 15:37:34 -08:00
fn confirm_tx(rpc_addr: &str, signature: &str) -> Result<RpcSignatureStatus, Box<error::Error>> {
2018-10-22 21:21:33 -07:00
let params = json!(signature.to_string());
2018-10-24 09:01:19 -07:00
let signature_status =
RpcRequest::GetSignatureStatus.make_rpc_request(&rpc_addr, 1, Some(params))?;
2018-10-24 09:01:19 -07:00
if let Some(status) = signature_status.as_str() {
let rpc_status = RpcSignatureStatus::from_str(status).map_err(|_| {
WalletError::RpcRequestError("Unable to parse signature status".to_string())
})?;
Ok(rpc_status)
} else {
2018-10-22 21:21:33 -07:00
Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?
}
2018-10-24 09:01:19 -07:00
}
2018-11-09 15:37:34 -08:00
fn send_and_confirm_tx(rpc_addr: &str, tx: &Transaction) -> Result<(), Box<error::Error>> {
2018-10-24 09:01:19 -07:00
let mut send_retries = 3;
while send_retries > 0 {
let mut status_retries = 4;
let signature_str = send_tx(rpc_addr, tx)?;
2018-10-24 09:01:19 -07:00
let status = loop {
let status = confirm_tx(rpc_addr, &signature_str)?;
2018-10-24 09:01:19 -07:00
if status == RpcSignatureStatus::SignatureNotFound {
status_retries -= 1;
if status_retries == 0 {
break status;
}
} else {
break status;
}
};
match status {
RpcSignatureStatus::AccountInUse => {
send_retries -= 1;
}
RpcSignatureStatus::Confirmed => {
return Ok(());
}
_ => {
return Err(WalletError::RpcRequestError(format!(
"Transaction {:?} failed: {:?}",
signature_str, status
)))?;
}
}
}
Err(WalletError::RpcRequestError(format!(
"AccountInUse after 3 retries: {:?}",
tx.account_keys[0]
)))?
2018-10-22 21:21:33 -07:00
}
#[cfg(test)]
2018-09-14 01:59:09 -07:00
mod tests {
use super::*;
use bank::Bank;
use clap::{App, Arg, SubCommand};
2018-10-08 19:55:54 -07:00
use cluster_info::Node;
2018-09-14 01:59:09 -07:00
use drone::run_local_drone;
use fullnode::Fullnode;
Leader scheduler plumbing (#1440) * Added LeaderScheduler module and tests * plumbing for LeaderScheduler in Fullnode + tests. Add vote processing for active set to ReplicateStage and WriteStage * Add LeaderScheduler plumbing for Tvu, window, and tests * Fix bank and switch tests to use new LeaderScheduler * move leader rotation check from window service to replicate stage * Add replicate_stage leader rotation exit test * removed leader scheduler from the window service and associated modules/tests * Corrected is_leader calculation in repair() function in window.rs * Integrate LeaderScheduler with write_stage for leader to validator transitions * Integrated LeaderScheduler with BroadcastStage * Removed gossip leader rotation from crdt * Add multi validator, leader test * Comments and cleanup * Remove unneeded checks from broadcast stage * Fix case where a validator/leader need to immediately transition on startup after reading ledger and seeing they are not in the correct role * Set new leader in validator -> validator transitions * Clean up for PR comments, refactor LeaderScheduler from process_entry/process_ledger_tail * Cleaned out LeaderScheduler options, implemented LeaderScheduler strategy that only picks the bootstrap leader to support existing tests, drone/airdrops * Ignore test_full_leader_validator_network test due to bug where the next leader in line fails to get the last entry before rotation (b/c it hasn't started up yet). Added a test test_dropped_handoff_recovery go track this bug
2018-10-10 16:49:41 -07:00
use leader_scheduler::LeaderScheduler;
2018-10-17 13:42:54 -07:00
use ledger::create_tmp_genesis;
use serde_json::Value;
use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil};
use std::fs::remove_dir_all;
2018-09-14 01:59:09 -07:00
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
2018-09-14 01:59:09 -07:00
#[test]
fn test_wallet_parse_command() {
2018-09-14 01:59:09 -07:00
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")
.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("cancelable")
.long("cancelable")
.takes_value(false),
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-24 09:23:16 -07:00
Arg::with_name("to")
2018-09-14 01:59:09 -07:00
.index(1)
2018-09-24 09:23:16 -07:00
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
).arg(
Arg::with_name("process-id")
.index(2)
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(
2018-09-24 09:23:16 -07:00
Arg::with_name("to")
2018-09-19 12:44:16 -07:00
.index(1)
2018-09-24 09:23:16 -07:00
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
).arg(
Arg::with_name("process-id")
.index(2)
2018-09-19 12:44:16 -07:00
.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
2018-09-24 09:23:16 -07:00
let test_send_signature = test_commands.clone().get_matches_from(vec![
"test",
"send-signature",
&pubkey_string,
&pubkey_string,
]);
2018-09-19 12:44:16 -07:00
assert_eq!(
parse_command(pubkey, &test_send_signature).unwrap(),
2018-09-24 09:23:16 -07:00
WalletCommand::Witness(pubkey, pubkey)
2018-09-19 12:44:16 -07:00
);
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,
2018-09-24 09:23:16 -07:00
&pubkey_string,
2018-09-19 12:44:16 -07:00
"--date",
"2018-09-19T17:30:59",
]);
assert_eq!(
parse_command(pubkey, &test_send_timestamp).unwrap(),
2018-09-24 09:23:16 -07:00
WalletCommand::TimeElapsed(pubkey, pubkey, dt)
2018-09-19 12:44:16 -07:00
);
let test_bad_timestamp = test_commands.clone().get_matches_from(vec![
"test",
"send-timestamp",
&pubkey_string,
2018-09-24 09:23:16 -07:00
&pubkey_string,
2018-09-19 12:44:16 -07:00
"--date",
"20180919T17:30:59",
]);
assert!(parse_command(pubkey, &test_bad_timestamp).is_err());
2018-09-14 01:59:09 -07:00
}
#[test]
#[ignore]
fn test_wallet_process_command() {
2018-09-14 01:59:09 -07:00
let bob_pubkey = Keypair::new().pubkey();
2018-10-17 13:42:54 -07:00
let leader_keypair = Arc::new(Keypair::new());
2018-10-17 13:42:54 -07:00
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
2018-09-14 01:59:09 -07:00
let leader_data = leader.info.clone();
let (alice, ledger_path) =
create_tmp_genesis("wallet_process_command", 10_000_000, leader_data.id, 1000);
let mut bank = Bank::new(&alice);
2018-09-14 01:59:09 -07:00
let mut config = WalletConfig::default();
2018-09-24 09:23:16 -07:00
let rpc_port = 12345; // Needs to be distinct known number to not conflict with other tests
2018-09-14 01:59:09 -07:00
let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(
leader_data.id,
)));
bank.leader_scheduler = leader_scheduler;
let vote_account_keypair = Arc::new(Keypair::new());
let last_id = bank.last_id();
let server = Fullnode::new_with_bank(
2018-09-14 01:59:09 -07:00
leader_keypair,
vote_account_keypair,
2018-09-14 01:59:09 -07:00
bank,
0,
&last_id,
2018-09-14 01:59:09 -07:00
leader,
None,
&ledger_path,
2018-09-14 01:59:09 -07:00
false,
2018-09-20 22:38:59 -07:00
Some(rpc_port),
2018-09-14 01:59:09 -07:00
);
sleep(Duration::from_millis(900));
2018-09-14 01:59:09 -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());
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)
);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
2018-09-14 01:59:09 -07:00
}
#[test]
fn test_wallet_request_airdrop() {
2018-09-14 01:59:09 -07:00
let bob_pubkey = Keypair::new().pubkey();
2018-10-17 13:42:54 -07:00
let leader_keypair = Arc::new(Keypair::new());
2018-10-17 13:42:54 -07:00
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
2018-09-14 01:59:09 -07:00
let leader_data = leader.info.clone();
let (alice, ledger_path) =
create_tmp_genesis("wallet_request_airdrop", 10_000_000, leader_data.id, 1000);
let mut bank = Bank::new(&alice);
2018-09-14 01:59:09 -07:00
let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(
leader_data.id,
)));
bank.leader_scheduler = leader_scheduler;
let vote_account_keypair = Arc::new(Keypair::new());
let last_id = bank.last_id();
let entry_height = alice.create_entries().len() as u64;
let server = Fullnode::new_with_bank(
2018-09-14 01:59:09 -07:00
leader_keypair,
vote_account_keypair,
2018-09-14 01:59:09 -07:00
bank,
entry_height,
&last_id,
2018-09-14 01:59:09 -07:00
leader,
None,
&ledger_path,
2018-09-14 01:59:09 -07:00
false,
2018-11-05 09:50:58 -08:00
None,
2018-09-14 01:59:09 -07:00
);
sleep(Duration::from_millis(900));
2018-09-14 01:59:09 -07:00
let (sender, receiver) = channel();
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
let drone_addr = receiver.recv().unwrap();
2018-11-05 09:50:58 -08:00
let rpc_addr = format!("http://{}", leader_data.contact_info.rpc.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()));
let confirmation = RpcRequest::ConfirmTransaction
2018-09-20 22:27:06 -07:00
.make_rpc_request(&rpc_addr, 1, Some(params))
.unwrap()
.as_bool()
.unwrap();
assert!(confirmation);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
2018-09-14 01:59:09 -07:00
}
2018-10-17 13:42:54 -07:00
fn tmp_file_path(name: &str) -> String {
use std::env;
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
let keypair = Keypair::new();
format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey()).to_string()
}
#[test]
fn test_wallet_gen_keypair_file() {
let outfile = tmp_file_path("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-24 09:23:16 -07:00
#[test]
#[ignore]
fn test_wallet_timestamp_tx() {
2018-09-24 09:23:16 -07:00
let bob_pubkey = Keypair::new().pubkey();
2018-10-17 13:42:54 -07:00
let leader_keypair = Arc::new(Keypair::new());
2018-10-17 13:42:54 -07:00
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
2018-09-24 09:23:16 -07:00
let leader_data = leader.info.clone();
let (alice, ledger_path) =
create_tmp_genesis("wallet_timestamp_tx", 10_000_000, leader_data.id, 1000);
let mut bank = Bank::new(&alice);
2018-09-24 09:23:16 -07:00
let mut config_payer = WalletConfig::default();
let mut config_witness = WalletConfig::default();
let rpc_port = 13579; // Needs to be distinct known number to not conflict with other tests
let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(
leader_data.id,
)));
bank.leader_scheduler = leader_scheduler;
let vote_account_keypair = Arc::new(Keypair::new());
let last_id = bank.last_id();
let server = Fullnode::new_with_bank(
2018-09-24 09:23:16 -07:00
leader_keypair,
vote_account_keypair,
2018-09-24 09:23:16 -07:00
bank,
0,
&last_id,
2018-09-24 09:23:16 -07:00
leader,
None,
&ledger_path,
false,
Some(rpc_port),
);
sleep(Duration::from_millis(900));
2018-09-24 09:23:16 -07:00
let (sender, receiver) = channel();
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
let drone_addr = receiver.recv().unwrap();
2018-09-24 09:23:16 -07:00
let mut rpc_addr = leader_data.contact_info.ncp;
rpc_addr.set_port(rpc_port);
let rpc_addr = format!("http://{}", rpc_addr.to_string());
2018-09-24 09:23:16 -07:00
assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey());
let _signature = request_airdrop(&drone_addr, &config_payer.id.pubkey(), 50);
2018-09-24 09:23:16 -07:00
// Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness
let date_string = "\"2018-09-19T17:30:59Z\"";
let dt: DateTime<Utc> = serde_json::from_str(&date_string).unwrap();
config_payer.command = WalletCommand::Pay(
10,
bob_pubkey,
Some(dt),
Some(config_witness.id.pubkey()),
None,
None,
);
let sig_response = process_command(&config_payer);
assert!(sig_response.is_ok());
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
let process_id_str = object.get("processId").unwrap().as_str().unwrap();
let process_id_vec = bs58::decode(process_id_str)
.into_vec()
.expect("base58-encoded public key");
let process_id = Pubkey::new(&process_id_vec);
let params = json!(format!("{}", config_payer.id.pubkey()));
let config_payer_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(config_payer_balance, 39);
let params = json!(format!("{}", process_id));
let contract_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(contract_balance, 11);
let params = json!(format!("{}", bob_pubkey));
let recipient_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(recipient_balance, 0);
// Sign transaction by config_witness
config_witness.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt);
let sig_response = process_command(&config_witness);
assert!(sig_response.is_ok());
let params = json!(format!("{}", config_payer.id.pubkey()));
let config_payer_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(config_payer_balance, 39);
let params = json!(format!("{}", process_id));
let contract_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(contract_balance, 1);
let params = json!(format!("{}", bob_pubkey));
let recipient_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(recipient_balance, 10);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
2018-09-24 09:23:16 -07:00
}
#[test]
#[ignore]
fn test_wallet_witness_tx() {
2018-09-24 09:23:16 -07:00
let bob_pubkey = Keypair::new().pubkey();
let leader_keypair = Arc::new(Keypair::new());
2018-10-17 13:42:54 -07:00
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
2018-09-24 09:23:16 -07:00
let leader_data = leader.info.clone();
let (alice, ledger_path) =
create_tmp_genesis("wallet_witness_tx", 10_000_000, leader_data.id, 1000);
let mut bank = Bank::new(&alice);
2018-09-24 09:23:16 -07:00
let mut config_payer = WalletConfig::default();
let mut config_witness = WalletConfig::default();
let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(
leader_data.id,
)));
bank.leader_scheduler = leader_scheduler;
let vote_account_keypair = Arc::new(Keypair::new());
let last_id = bank.last_id();
let server = Fullnode::new_with_bank(
2018-09-24 09:23:16 -07:00
leader_keypair,
vote_account_keypair,
2018-09-24 09:23:16 -07:00
bank,
0,
&last_id,
2018-09-24 09:23:16 -07:00
leader,
None,
&ledger_path,
false,
2018-11-05 09:50:58 -08:00
None,
2018-09-24 09:23:16 -07:00
);
sleep(Duration::from_millis(900));
2018-09-24 09:23:16 -07:00
let (sender, receiver) = channel();
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
let drone_addr = receiver.recv().unwrap();
2018-09-24 09:23:16 -07:00
2018-11-05 09:50:58 -08:00
let rpc_addr = leader_data.contact_info.rpc;
let rpc_addr = format!("http://{}", rpc_addr.to_string());
2018-09-24 09:23:16 -07:00
assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey());
let _signature = request_airdrop(&drone_addr, &config_payer.id.pubkey(), 50);
2018-09-24 09:23:16 -07:00
// Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness
config_payer.command = WalletCommand::Pay(
10,
bob_pubkey,
None,
None,
Some(vec![config_witness.id.pubkey()]),
None,
);
let sig_response = process_command(&config_payer);
assert!(sig_response.is_ok());
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
let process_id_str = object.get("processId").unwrap().as_str().unwrap();
let process_id_vec = bs58::decode(process_id_str)
.into_vec()
.expect("base58-encoded public key");
let process_id = Pubkey::new(&process_id_vec);
let params = json!(format!("{}", config_payer.id.pubkey()));
let config_payer_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(config_payer_balance, 39);
let params = json!(format!("{}", process_id));
let contract_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(contract_balance, 11);
let params = json!(format!("{}", bob_pubkey));
let recipient_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(recipient_balance, 0);
// Sign transaction by config_witness
config_witness.command = WalletCommand::Witness(bob_pubkey, process_id);
let sig_response = process_command(&config_witness);
assert!(sig_response.is_ok());
let params = json!(format!("{}", config_payer.id.pubkey()));
let config_payer_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(config_payer_balance, 39);
let params = json!(format!("{}", process_id));
let contract_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(contract_balance, 1);
let params = json!(format!("{}", bob_pubkey));
let recipient_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(recipient_balance, 10);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
2018-09-24 09:23:16 -07:00
}
#[test]
#[ignore]
fn test_wallet_cancel_tx() {
2018-09-24 09:23:16 -07:00
let bob_pubkey = Keypair::new().pubkey();
let leader_keypair = Arc::new(Keypair::new());
2018-10-17 13:42:54 -07:00
let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey());
2018-09-24 09:23:16 -07:00
let leader_data = leader.info.clone();
let (alice, ledger_path) =
create_tmp_genesis("wallet_cancel_tx", 10_000_000, leader_data.id, 1000);
let mut bank = Bank::new(&alice);
2018-09-24 09:23:16 -07:00
let mut config_payer = WalletConfig::default();
let config_witness = WalletConfig::default();
2018-09-24 09:23:16 -07:00
let rpc_port = 13456; // Needs to be distinct known number to not conflict with other tests
let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader(
leader_data.id,
)));
bank.leader_scheduler = leader_scheduler;
let vote_account_keypair = Arc::new(Keypair::new());
let last_id = bank.last_id();
let server = Fullnode::new_with_bank(
2018-09-24 09:23:16 -07:00
leader_keypair,
vote_account_keypair,
2018-09-24 09:23:16 -07:00
bank,
0,
&last_id,
2018-09-24 09:23:16 -07:00
leader,
None,
&ledger_path,
false,
Some(rpc_port),
);
sleep(Duration::from_millis(900));
2018-09-24 09:23:16 -07:00
let (sender, receiver) = channel();
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
let drone_addr = receiver.recv().unwrap();
2018-09-24 09:23:16 -07:00
let mut rpc_addr = leader_data.contact_info.ncp;
rpc_addr.set_port(rpc_port);
let rpc_addr = format!("http://{}", rpc_addr.to_string());
2018-09-24 09:23:16 -07:00
assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey());
let _signature = request_airdrop(&drone_addr, &config_payer.id.pubkey(), 50);
2018-09-24 09:23:16 -07:00
// Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness
config_payer.command = WalletCommand::Pay(
10,
bob_pubkey,
None,
None,
Some(vec![config_witness.id.pubkey()]),
Some(config_payer.id.pubkey()),
);
let sig_response = process_command(&config_payer);
assert!(sig_response.is_ok());
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
let process_id_str = object.get("processId").unwrap().as_str().unwrap();
let process_id_vec = bs58::decode(process_id_str)
.into_vec()
.expect("base58-encoded public key");
let process_id = Pubkey::new(&process_id_vec);
let params = json!(format!("{}", config_payer.id.pubkey()));
let config_payer_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(config_payer_balance, 39);
let params = json!(format!("{}", process_id));
let contract_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(contract_balance, 11);
let params = json!(format!("{}", bob_pubkey));
let recipient_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(recipient_balance, 0);
// Sign transaction by config_witness
config_payer.command = WalletCommand::Cancel(process_id);
let sig_response = process_command(&config_payer);
assert!(sig_response.is_ok());
let params = json!(format!("{}", config_payer.id.pubkey()));
let config_payer_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(config_payer_balance, 49);
let params = json!(format!("{}", process_id));
let contract_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(contract_balance, 1);
let params = json!(format!("{}", bob_pubkey));
let recipient_balance = RpcRequest::GetBalance
.make_rpc_request(&rpc_addr, 1, Some(params))
2018-09-24 09:23:16 -07:00
.unwrap()
.as_u64()
2018-09-24 09:23:16 -07:00
.unwrap();
assert_eq!(recipient_balance, 0);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
2018-09-24 09:23:16 -07:00
}
2018-09-14 01:59:09 -07:00
}