Port Wallet to jsonrpc and fix tests
This commit is contained in:
committed by
Tyera Eulberg
parent
5ab38afa51
commit
9228fe11c9
@@ -5,9 +5,9 @@ extern crate dirs;
|
|||||||
extern crate solana;
|
extern crate solana;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use solana::client::mk_client;
|
|
||||||
use solana::drone::DRONE_PORT;
|
use solana::drone::DRONE_PORT;
|
||||||
use solana::logger;
|
use solana::logger;
|
||||||
|
use solana::rpc::RPC_PORT;
|
||||||
use solana::signature::{read_keypair, KeypairUtil};
|
use solana::signature::{read_keypair, KeypairUtil};
|
||||||
use solana::thin_client::poll_gossip_for_leader;
|
use solana::thin_client::poll_gossip_for_leader;
|
||||||
use solana::wallet::{gen_keypair_file, parse_command, process_command, WalletConfig, WalletError};
|
use solana::wallet::{gen_keypair_file, parse_command, process_command, WalletConfig, WalletError};
|
||||||
@@ -54,12 +54,16 @@ pub fn parse_args(matches: &ArgMatches) -> Result<WalletConfig, Box<error::Error
|
|||||||
let mut drone_addr = leader.contact_info.tpu;
|
let mut drone_addr = leader.contact_info.tpu;
|
||||||
drone_addr.set_port(DRONE_PORT);
|
drone_addr.set_port(DRONE_PORT);
|
||||||
|
|
||||||
|
let mut rpc_addr = leader.contact_info.tpu;
|
||||||
|
rpc_addr.set_port(RPC_PORT);
|
||||||
|
|
||||||
let command = parse_command(id.pubkey(), &matches)?;
|
let command = parse_command(id.pubkey(), &matches)?;
|
||||||
|
|
||||||
Ok(WalletConfig {
|
Ok(WalletConfig {
|
||||||
leader,
|
leader,
|
||||||
id,
|
id,
|
||||||
drone_addr, // TODO: Add an option for this.
|
drone_addr, // TODO: Add an option for this.
|
||||||
|
rpc_addr, // TODO: Add an option for this.
|
||||||
command,
|
command,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -75,16 +79,14 @@ fn main() -> Result<(), Box<error::Error>> {
|
|||||||
.value_name("HOST:PORT")
|
.value_name("HOST:PORT")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"),
|
.help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"),
|
||||||
)
|
).arg(
|
||||||
.arg(
|
|
||||||
Arg::with_name("keypair")
|
Arg::with_name("keypair")
|
||||||
.short("k")
|
.short("k")
|
||||||
.long("keypair")
|
.long("keypair")
|
||||||
.value_name("PATH")
|
.value_name("PATH")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("/path/to/id.json"),
|
.help("/path/to/id.json"),
|
||||||
)
|
).arg(
|
||||||
.arg(
|
|
||||||
Arg::with_name("timeout")
|
Arg::with_name("timeout")
|
||||||
.long("timeout")
|
.long("timeout")
|
||||||
.value_name("SECS")
|
.value_name("SECS")
|
||||||
@@ -133,8 +135,7 @@ fn main() -> Result<(), Box<error::Error>> {
|
|||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let config = parse_args(&matches)?;
|
let config = parse_args(&matches)?;
|
||||||
let mut client = mk_client(&config.leader);
|
let result = process_command(&config)?;
|
||||||
let result = process_command(&config, &mut client)?;
|
|
||||||
println!("{}", result);
|
println!("{}", result);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,11 @@ impl fmt::Display for Hash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash {
|
||||||
|
pub fn new(hash_slice: &[u8]) -> Self {
|
||||||
|
Hash(GenericArray::clone_from_slice(&hash_slice))
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Return a Sha256 hash for the given data.
|
/// Return a Sha256 hash for the given data.
|
||||||
pub fn hashv(vals: &[&[u8]]) -> Hash {
|
pub fn hashv(vals: &[&[u8]]) -> Hash {
|
||||||
let mut hasher = Sha256::default();
|
let mut hasher = Sha256::default();
|
||||||
|
36
src/rpc.rs
36
src/rpc.rs
@@ -90,6 +90,9 @@ build_rpc_trait! {
|
|||||||
#[rpc(meta, name = "confirmTransaction")]
|
#[rpc(meta, name = "confirmTransaction")]
|
||||||
fn confirm_transaction(&self, Self::Metadata, String) -> Result<bool>;
|
fn confirm_transaction(&self, Self::Metadata, String) -> Result<bool>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getAccountInfo")]
|
||||||
|
fn get_account_info(&self, Self::Metadata, String) -> Result<Account>;
|
||||||
|
|
||||||
#[rpc(meta, name = "getBalance")]
|
#[rpc(meta, name = "getBalance")]
|
||||||
fn get_balance(&self, Self::Metadata, String) -> Result<i64>;
|
fn get_balance(&self, Self::Metadata, String) -> Result<i64>;
|
||||||
|
|
||||||
@@ -102,9 +105,6 @@ build_rpc_trait! {
|
|||||||
#[rpc(meta, name = "getTransactionCount")]
|
#[rpc(meta, name = "getTransactionCount")]
|
||||||
fn get_transaction_count(&self, Self::Metadata) -> Result<u64>;
|
fn get_transaction_count(&self, Self::Metadata) -> Result<u64>;
|
||||||
|
|
||||||
#[rpc(meta, name = "getAccountInfo")]
|
|
||||||
fn get_account_info(&self, Self::Metadata, String) -> Result<Account>;
|
|
||||||
|
|
||||||
#[rpc(meta, name= "requestAirdrop")]
|
#[rpc(meta, name= "requestAirdrop")]
|
||||||
fn request_airdrop(&self, Self::Metadata, String, u64) -> Result<String>;
|
fn request_airdrop(&self, Self::Metadata, String, u64) -> Result<String>;
|
||||||
|
|
||||||
@@ -127,6 +127,16 @@ impl RpcSol for RpcSolImpl {
|
|||||||
let signature = Signature::new(&signature_vec);
|
let signature = Signature::new(&signature_vec);
|
||||||
meta.request_processor.get_signature_status(signature)
|
meta.request_processor.get_signature_status(signature)
|
||||||
}
|
}
|
||||||
|
fn get_account_info(&self, meta: Self::Metadata, id: String) -> Result<Account> {
|
||||||
|
let pubkey_vec = bs58::decode(id)
|
||||||
|
.into_vec()
|
||||||
|
.map_err(|_| Error::invalid_request())?;
|
||||||
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
||||||
|
return Err(Error::invalid_request());
|
||||||
|
}
|
||||||
|
let pubkey = Pubkey::new(&pubkey_vec);
|
||||||
|
meta.request_processor.get_account_info(pubkey)
|
||||||
|
}
|
||||||
fn get_balance(&self, meta: Self::Metadata, id: String) -> Result<i64> {
|
fn get_balance(&self, meta: Self::Metadata, id: String) -> Result<i64> {
|
||||||
let pubkey_vec = bs58::decode(id)
|
let pubkey_vec = bs58::decode(id)
|
||||||
.into_vec()
|
.into_vec()
|
||||||
@@ -146,16 +156,6 @@ impl RpcSol for RpcSolImpl {
|
|||||||
fn get_transaction_count(&self, meta: Self::Metadata) -> Result<u64> {
|
fn get_transaction_count(&self, meta: Self::Metadata) -> Result<u64> {
|
||||||
meta.request_processor.get_transaction_count()
|
meta.request_processor.get_transaction_count()
|
||||||
}
|
}
|
||||||
fn get_account_info(&self, meta: Self::Metadata, id: String) -> Result<Account> {
|
|
||||||
let pubkey_vec = bs58::decode(id)
|
|
||||||
.into_vec()
|
|
||||||
.map_err(|_| Error::invalid_request())?;
|
|
||||||
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
||||||
return Err(Error::invalid_request());
|
|
||||||
}
|
|
||||||
let pubkey = Pubkey::new(&pubkey_vec);
|
|
||||||
meta.request_processor.get_account_info(pubkey)
|
|
||||||
}
|
|
||||||
fn request_airdrop(&self, meta: Self::Metadata, id: String, tokens: u64) -> Result<String> {
|
fn request_airdrop(&self, meta: Self::Metadata, id: String, tokens: u64) -> Result<String> {
|
||||||
let pubkey_vec = bs58::decode(id)
|
let pubkey_vec = bs58::decode(id)
|
||||||
.into_vec()
|
.into_vec()
|
||||||
@@ -208,6 +208,11 @@ impl JsonRpcRequestProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process JSON-RPC request items sent via JSON-RPC.
|
/// Process JSON-RPC request items sent via JSON-RPC.
|
||||||
|
fn get_account_info(&self, pubkey: Pubkey) -> Result<Account> {
|
||||||
|
self.bank
|
||||||
|
.get_account(&pubkey)
|
||||||
|
.ok_or(Error::invalid_request())
|
||||||
|
}
|
||||||
fn get_balance(&self, pubkey: Pubkey) -> Result<i64> {
|
fn get_balance(&self, pubkey: Pubkey) -> Result<i64> {
|
||||||
let val = self.bank.get_balance(&pubkey);
|
let val = self.bank.get_balance(&pubkey);
|
||||||
Ok(val)
|
Ok(val)
|
||||||
@@ -225,11 +230,6 @@ impl JsonRpcRequestProcessor {
|
|||||||
fn get_transaction_count(&self) -> Result<u64> {
|
fn get_transaction_count(&self) -> Result<u64> {
|
||||||
Ok(self.bank.transaction_count() as u64)
|
Ok(self.bank.transaction_count() as u64)
|
||||||
}
|
}
|
||||||
fn get_account_info(&self, pubkey: Pubkey) -> Result<Account> {
|
|
||||||
self.bank
|
|
||||||
.get_account(&pubkey)
|
|
||||||
.ok_or(Error::invalid_request())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
259
src/wallet.rs
259
src/wallet.rs
@@ -4,9 +4,12 @@ use clap::ArgMatches;
|
|||||||
use crdt::NodeInfo;
|
use crdt::NodeInfo;
|
||||||
use drone::DroneRequest;
|
use drone::DroneRequest;
|
||||||
use fullnode::Config;
|
use fullnode::Config;
|
||||||
|
use hash::Hash;
|
||||||
|
use reqwest;
|
||||||
|
use reqwest::header::CONTENT_TYPE;
|
||||||
use ring::rand::SystemRandom;
|
use ring::rand::SystemRandom;
|
||||||
use ring::signature::Ed25519KeyPair;
|
use ring::signature::Ed25519KeyPair;
|
||||||
use serde_json;
|
use serde_json::{self, Value};
|
||||||
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
|
use signature::{Keypair, KeypairUtil, Pubkey, Signature};
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
@@ -17,7 +20,7 @@ use std::path::Path;
|
|||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{error, fmt, mem};
|
use std::{error, fmt, mem};
|
||||||
use thin_client::ThinClient;
|
use transaction::Transaction;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum WalletCommand {
|
pub enum WalletCommand {
|
||||||
@@ -32,6 +35,7 @@ pub enum WalletCommand {
|
|||||||
pub enum WalletError {
|
pub enum WalletError {
|
||||||
CommandNotRecognized(String),
|
CommandNotRecognized(String),
|
||||||
BadParameter(String),
|
BadParameter(String),
|
||||||
|
RpcRequestError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for WalletError {
|
impl fmt::Display for WalletError {
|
||||||
@@ -55,6 +59,7 @@ pub struct WalletConfig {
|
|||||||
pub leader: NodeInfo,
|
pub leader: NodeInfo,
|
||||||
pub id: Keypair,
|
pub id: Keypair,
|
||||||
pub drone_addr: SocketAddr,
|
pub drone_addr: SocketAddr,
|
||||||
|
pub rpc_addr: SocketAddr,
|
||||||
pub command: WalletCommand,
|
pub command: WalletCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +70,7 @@ impl Default for WalletConfig {
|
|||||||
leader: NodeInfo::new_with_socketaddr(&default_addr),
|
leader: NodeInfo::new_with_socketaddr(&default_addr),
|
||||||
id: Keypair::new(),
|
id: Keypair::new(),
|
||||||
drone_addr: default_addr,
|
drone_addr: default_addr,
|
||||||
|
rpc_addr: default_addr,
|
||||||
command: WalletCommand::Balance,
|
command: WalletCommand::Balance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,10 +81,25 @@ pub fn parse_command(
|
|||||||
matches: &ArgMatches,
|
matches: &ArgMatches,
|
||||||
) -> Result<WalletCommand, Box<error::Error>> {
|
) -> Result<WalletCommand, Box<error::Error>> {
|
||||||
let response = match matches.subcommand() {
|
let response = match matches.subcommand() {
|
||||||
|
("address", Some(_address_matches)) => Ok(WalletCommand::Address),
|
||||||
("airdrop", Some(airdrop_matches)) => {
|
("airdrop", Some(airdrop_matches)) => {
|
||||||
let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?;
|
let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?;
|
||||||
Ok(WalletCommand::AirDrop(tokens))
|
Ok(WalletCommand::AirDrop(tokens))
|
||||||
}
|
}
|
||||||
|
("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance),
|
||||||
|
("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)) => {
|
("pay", Some(pay_matches)) => {
|
||||||
let to = if pay_matches.is_present("to") {
|
let to = if pay_matches.is_present("to") {
|
||||||
let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap())
|
let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap())
|
||||||
@@ -98,22 +119,6 @@ pub fn parse_command(
|
|||||||
|
|
||||||
Ok(WalletCommand::Pay(tokens, to))
|
Ok(WalletCommand::Pay(tokens, to))
|
||||||
}
|
}
|
||||||
("confirm", Some(confirm_matches)) => {
|
|
||||||
println!("{:?}", confirm_matches.value_of("signature").unwrap());
|
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance),
|
|
||||||
("address", Some(_address_matches)) => Ok(WalletCommand::Address),
|
|
||||||
("", None) => {
|
("", None) => {
|
||||||
println!("{}", matches.usage());
|
println!("{}", matches.usage());
|
||||||
Err(WalletError::CommandNotRecognized(
|
Err(WalletError::CommandNotRecognized(
|
||||||
@@ -125,24 +130,10 @@ pub fn parse_command(
|
|||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command(
|
pub fn process_command(config: &WalletConfig) -> Result<String, Box<error::Error>> {
|
||||||
config: &WalletConfig,
|
|
||||||
client: &mut ThinClient,
|
|
||||||
) -> Result<String, Box<error::Error>> {
|
|
||||||
match config.command {
|
match config.command {
|
||||||
// Check client balance
|
// Get address of this client
|
||||||
WalletCommand::Address => Ok(format!("{}", config.id.pubkey())),
|
WalletCommand::Address => Ok(format!("{}", config.id.pubkey())),
|
||||||
WalletCommand::Balance => {
|
|
||||||
println!("Balance requested...");
|
|
||||||
let balance = client.poll_get_balance(&config.id.pubkey());
|
|
||||||
match balance {
|
|
||||||
Ok(balance) => Ok(format!("Your balance is: {:?}", balance)),
|
|
||||||
Err(ref e) if e.kind() == ErrorKind::Other => {
|
|
||||||
Ok("No account found! Request an airdrop to get started.".to_string())
|
|
||||||
}
|
|
||||||
Err(error) => Err(error)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Request an airdrop from Solana Drone;
|
// Request an airdrop from Solana Drone;
|
||||||
// Request amount is set in request_airdrop function
|
// Request amount is set in request_airdrop function
|
||||||
WalletCommand::AirDrop(tokens) => {
|
WalletCommand::AirDrop(tokens) => {
|
||||||
@@ -150,7 +141,11 @@ pub fn process_command(
|
|||||||
"Requesting airdrop of {:?} tokens from {}",
|
"Requesting airdrop of {:?} tokens from {}",
|
||||||
tokens, config.drone_addr
|
tokens, config.drone_addr
|
||||||
);
|
);
|
||||||
let previous_balance = client.poll_get_balance(&config.id.pubkey()).unwrap_or(0);
|
let params = format!("[\"{}\"]", config.id.pubkey());
|
||||||
|
let previous_balance = WalletRpcRequest::GetBalance
|
||||||
|
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
|
||||||
|
.as_i64()
|
||||||
|
.unwrap_or(0);
|
||||||
request_airdrop(&config.drone_addr, &config.id.pubkey(), tokens as u64)?;
|
request_airdrop(&config.drone_addr, &config.id.pubkey(), tokens as u64)?;
|
||||||
|
|
||||||
// TODO: return airdrop Result from Drone instead of polling the
|
// TODO: return airdrop Result from Drone instead of polling the
|
||||||
@@ -158,8 +153,10 @@ pub fn process_command(
|
|||||||
let mut current_balance = previous_balance;
|
let mut current_balance = previous_balance;
|
||||||
for _ in 0..20 {
|
for _ in 0..20 {
|
||||||
sleep(Duration::from_millis(500));
|
sleep(Duration::from_millis(500));
|
||||||
current_balance = client
|
let params = format!("[\"{}\"]", config.id.pubkey());
|
||||||
.poll_get_balance(&config.id.pubkey())
|
current_balance = WalletRpcRequest::GetBalance
|
||||||
|
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
|
||||||
|
.as_i64()
|
||||||
.unwrap_or(previous_balance);
|
.unwrap_or(previous_balance);
|
||||||
|
|
||||||
if previous_balance != current_balance {
|
if previous_balance != current_balance {
|
||||||
@@ -172,20 +169,70 @@ pub fn process_command(
|
|||||||
}
|
}
|
||||||
Ok(format!("Your balance is: {:?}", current_balance))
|
Ok(format!("Your balance is: {:?}", current_balance))
|
||||||
}
|
}
|
||||||
// If client has positive balance, spend tokens in {balance} number of transactions
|
WalletCommand::Balance => {
|
||||||
WalletCommand::Pay(tokens, to) => {
|
println!("Balance requested...");
|
||||||
let last_id = client.get_last_id();
|
let params = format!("[\"{}\"]", config.id.pubkey());
|
||||||
let signature = client.transfer(tokens, &config.id, to, &last_id)?;
|
let balance = WalletRpcRequest::GetBalance
|
||||||
Ok(format!("{}", signature))
|
.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(),
|
||||||
|
))?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Confirm the last client transaction by signature
|
// Confirm the last client transaction by signature
|
||||||
WalletCommand::Confirm(signature) => {
|
WalletCommand::Confirm(signature) => {
|
||||||
if client.check_signature(&signature) {
|
let params = format!("[\"{}\"]", signature);
|
||||||
Ok("Confirmed".to_string())
|
let confirmation = WalletRpcRequest::ConfirmTransaction
|
||||||
} else {
|
.make_rpc_request(&config.rpc_addr, 1, Some(params))?
|
||||||
Ok("Not found".to_string())
|
.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, spend tokens in {balance} number of transactions
|
||||||
|
WalletCommand::Pay(tokens, to) => {
|
||||||
|
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(),
|
||||||
|
))?
|
||||||
|
}
|
||||||
|
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 = format!("[{}]", serde_json::to_string(&serialized)?);
|
||||||
|
let signature = WalletRpcRequest::SendTransaction.make_rpc_request(
|
||||||
|
&config.rpc_addr,
|
||||||
|
2,
|
||||||
|
Some(params.to_string()),
|
||||||
|
)?;
|
||||||
|
if signature.as_str().is_none() {
|
||||||
|
Err(WalletError::RpcRequestError(
|
||||||
|
"Received result of an unexpected type".to_string(),
|
||||||
|
))?
|
||||||
|
}
|
||||||
|
let signature_str = signature.as_str().unwrap();
|
||||||
|
|
||||||
|
Ok(format!("{}", signature_str))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,17 +294,71 @@ pub fn gen_keypair_file(outfile: String) -> Result<String, Box<error::Error>> {
|
|||||||
Ok(serialized)
|
Ok(serialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum WalletRpcRequest {
|
||||||
|
ConfirmTransaction,
|
||||||
|
GetAccountInfo,
|
||||||
|
GetBalance,
|
||||||
|
GetFinality,
|
||||||
|
GetLastId,
|
||||||
|
GetTransactionCount,
|
||||||
|
RequestAirdrop,
|
||||||
|
SendTransaction,
|
||||||
|
}
|
||||||
|
impl WalletRpcRequest {
|
||||||
|
fn make_rpc_request(
|
||||||
|
&self,
|
||||||
|
rpc_addr: &SocketAddr,
|
||||||
|
id: u64,
|
||||||
|
params: Option<String>,
|
||||||
|
) -> Result<Value, Box<error::Error>> {
|
||||||
|
let rpc_string = format!("http://{}", rpc_addr.to_string());
|
||||||
|
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: String = format!(
|
||||||
|
"{{\"jsonrpc\":\"{}\",\"id\":{},\"method\":\"{}\"",
|
||||||
|
jsonrpc, id, method
|
||||||
|
);
|
||||||
|
if let Some(param_string) = params {
|
||||||
|
request.push_str(&format!(",\"params\":{}", param_string));
|
||||||
|
}
|
||||||
|
request.push_str(&"}".to_string());
|
||||||
|
let mut response = client
|
||||||
|
.post(&rpc_string)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.body(request)
|
||||||
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use bank::Bank;
|
use bank::Bank;
|
||||||
use clap::{App, Arg, SubCommand};
|
use clap::{App, Arg, SubCommand};
|
||||||
use client::mk_client;
|
|
||||||
use crdt::Node;
|
use crdt::Node;
|
||||||
use drone::run_local_drone;
|
use drone::run_local_drone;
|
||||||
use fullnode::Fullnode;
|
use fullnode::Fullnode;
|
||||||
use ledger::LedgerWriter;
|
use ledger::LedgerWriter;
|
||||||
use mint::Mint;
|
use mint::Mint;
|
||||||
|
use rpc::RPC_PORT;
|
||||||
use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil};
|
use signature::{read_keypair, read_pkcs8, Keypair, KeypairUtil};
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
@@ -396,28 +497,31 @@ mod tests {
|
|||||||
config.drone_addr = receiver.recv().unwrap();
|
config.drone_addr = receiver.recv().unwrap();
|
||||||
config.leader = leader_data1;
|
config.leader = leader_data1;
|
||||||
|
|
||||||
|
let mut rpc_addr = leader_data.contact_info.ncp;
|
||||||
|
rpc_addr.set_port(RPC_PORT);
|
||||||
|
config.rpc_addr = rpc_addr;
|
||||||
|
|
||||||
let tokens = 50;
|
let tokens = 50;
|
||||||
config.command = WalletCommand::AirDrop(tokens);
|
config.command = WalletCommand::AirDrop(tokens);
|
||||||
let mut client = mk_client(&config.leader);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
process_command(&config, &mut client).unwrap(),
|
process_command(&config).unwrap(),
|
||||||
format!("Your balance is: {:?}", tokens)
|
format!("Your balance is: {:?}", tokens)
|
||||||
);
|
);
|
||||||
|
|
||||||
config.command = WalletCommand::Balance;
|
config.command = WalletCommand::Balance;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
process_command(&config, &mut client).unwrap(),
|
process_command(&config).unwrap(),
|
||||||
format!("Your balance is: {:?}", tokens)
|
format!("Your balance is: {:?}", tokens)
|
||||||
);
|
);
|
||||||
|
|
||||||
config.command = WalletCommand::Address;
|
config.command = WalletCommand::Address;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
process_command(&config, &mut client).unwrap(),
|
process_command(&config).unwrap(),
|
||||||
format!("{}", config.id.pubkey())
|
format!("{}", config.id.pubkey())
|
||||||
);
|
);
|
||||||
|
|
||||||
config.command = WalletCommand::Pay(10, bob_pubkey);
|
config.command = WalletCommand::Pay(10, bob_pubkey);
|
||||||
let sig_response = process_command(&config, &mut client);
|
let sig_response = process_command(&config);
|
||||||
assert!(sig_response.is_ok());
|
assert!(sig_response.is_ok());
|
||||||
sleep(Duration::from_millis(100));
|
sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
@@ -426,11 +530,11 @@ mod tests {
|
|||||||
.expect("base58-encoded signature");
|
.expect("base58-encoded signature");
|
||||||
let signature = Signature::new(&signatures);
|
let signature = Signature::new(&signatures);
|
||||||
config.command = WalletCommand::Confirm(signature);
|
config.command = WalletCommand::Confirm(signature);
|
||||||
assert_eq!(process_command(&config, &mut client).unwrap(), "Confirmed");
|
assert_eq!(process_command(&config).unwrap(), "Confirmed");
|
||||||
|
|
||||||
config.command = WalletCommand::Balance;
|
config.command = WalletCommand::Balance;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
process_command(&config, &mut client).unwrap(),
|
process_command(&config).unwrap(),
|
||||||
format!("Your balance is: {:?}", tokens - 10)
|
format!("Your balance is: {:?}", tokens - 10)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -461,20 +565,22 @@ mod tests {
|
|||||||
let requests_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
let requests_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||||
let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||||
|
|
||||||
let mut client = ThinClient::new(
|
|
||||||
leader_data.contact_info.rpu,
|
|
||||||
requests_socket,
|
|
||||||
leader_data.contact_info.tpu,
|
|
||||||
transactions_socket,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (sender, receiver) = channel();
|
let (sender, receiver) = channel();
|
||||||
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
|
run_local_drone(alice.keypair(), leader_data.contact_info.ncp, sender);
|
||||||
let drone_addr = receiver.recv().unwrap();
|
let drone_addr = receiver.recv().unwrap();
|
||||||
|
|
||||||
|
let mut rpc_addr = leader_data.contact_info.ncp;
|
||||||
|
rpc_addr.set_port(RPC_PORT);
|
||||||
|
|
||||||
let signature = request_airdrop(&drone_addr, &bob_pubkey, 50);
|
let signature = request_airdrop(&drone_addr, &bob_pubkey, 50);
|
||||||
assert!(signature.is_ok());
|
assert!(signature.is_ok());
|
||||||
assert!(client.check_signature(&signature.unwrap()));
|
let params = format!("[\"{}\"]", signature.unwrap());
|
||||||
|
let confirmation = WalletRpcRequest::ConfirmTransaction
|
||||||
|
.make_rpc_request(&rpc_addr, 1, Some(params))
|
||||||
|
.unwrap()
|
||||||
|
.as_bool()
|
||||||
|
.unwrap();
|
||||||
|
assert!(confirmation);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gen_keypair_file() {
|
fn test_gen_keypair_file() {
|
||||||
@@ -491,4 +597,33 @@ mod tests {
|
|||||||
fs::remove_file(outfile).unwrap();
|
fs::remove_file(outfile).unwrap();
|
||||||
assert!(!Path::new(outfile).exists());
|
assert!(!Path::new(outfile).exists());
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_make_rpc_request() {
|
||||||
|
// 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();
|
||||||
|
//
|
||||||
|
// let _server = Fullnode::new_with_bank(
|
||||||
|
// leader_keypair,
|
||||||
|
// bank,
|
||||||
|
// 0,
|
||||||
|
// &[],
|
||||||
|
// leader,
|
||||||
|
// None,
|
||||||
|
// &ledger_path,
|
||||||
|
// false,
|
||||||
|
// None,
|
||||||
|
// );
|
||||||
|
// sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user