mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-08 12:21:09 +03:00
Wallet API Test Client (#1242)
* beginnings to testclient implementation for more complete wallet API testing * rework TestWalletClient to be message-based proxy * test fix * wallet tests now exercising the API directly as much as possible, capable of instantiating multiple wallets * test in place to run both file and db wallets * ensure all wallet api functions lock wallet as needed. Split up transaction creation from posting to chain
This commit is contained in:
parent
25ca2de487
commit
bacadfb5ab
13 changed files with 917 additions and 475 deletions
|
@ -27,7 +27,7 @@ use std::default::Default;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{fs, thread, time};
|
use std::{fs, thread, time};
|
||||||
|
|
||||||
use wallet::{HTTPWalletClient, FileWallet, WalletConfig};
|
use wallet::{FileWallet, HTTPWalletClient, WalletConfig};
|
||||||
|
|
||||||
/// Just removes all results from previous runs
|
/// Just removes all results from previous runs
|
||||||
pub fn clean_all_output(test_name_dir: &str) {
|
pub fn clean_all_output(test_name_dir: &str) {
|
||||||
|
@ -276,13 +276,15 @@ impl LocalServerContainer {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet::controller::foreign_listener(Box::new(wallet), &self.wallet_config.api_listen_addr())
|
wallet::controller::foreign_listener(
|
||||||
.unwrap_or_else(|e| {
|
Box::new(wallet),
|
||||||
panic!(
|
&self.wallet_config.api_listen_addr(),
|
||||||
"Error creating wallet listener: {:?} Config: {:?}",
|
).unwrap_or_else(|e| {
|
||||||
e, self.wallet_config
|
panic!(
|
||||||
)
|
"Error creating wallet listener: {:?} Config: {:?}",
|
||||||
});
|
e, self.wallet_config
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
self.wallet_is_running = true;
|
self.wallet_is_running = true;
|
||||||
}
|
}
|
||||||
|
@ -335,28 +337,30 @@ impl LocalServerContainer {
|
||||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||||
wallet.keychain = Some(keychain);
|
wallet.keychain = Some(keychain);
|
||||||
let _ =
|
let _ =
|
||||||
wallet::controller::owner_single_use(Box::new(wallet), |api| {
|
wallet::controller::owner_single_use(
|
||||||
let result = api.issue_send_tx(
|
Arc::new(Mutex::new(Box::new(wallet))),
|
||||||
amount,
|
|api| {
|
||||||
minimum_confirmations,
|
let result = api.issue_send_tx(
|
||||||
dest,
|
amount,
|
||||||
max_outputs,
|
minimum_confirmations,
|
||||||
selection_strategy == "all",
|
|
||||||
fluff,
|
|
||||||
);
|
|
||||||
match result {
|
|
||||||
Ok(_) => println!(
|
|
||||||
"Tx sent: {} grin to {} (strategy '{}')",
|
|
||||||
core::core::amount_to_hr_string(amount),
|
|
||||||
dest,
|
dest,
|
||||||
selection_strategy,
|
max_outputs,
|
||||||
),
|
selection_strategy == "all",
|
||||||
Err(e) => {
|
);
|
||||||
println!("Tx not sent to {}: {:?}", dest, e);
|
match result {
|
||||||
}
|
Ok(_) => println!(
|
||||||
};
|
"Tx sent: {} grin to {} (strategy '{}')",
|
||||||
Ok(())
|
core::core::amount_to_hr_string(amount),
|
||||||
}).unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
dest,
|
||||||
|
selection_strategy,
|
||||||
|
),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Tx not sent to {}: {:?}", dest, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
).unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the running wallet server
|
/// Stops the running wallet server
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub mod tui;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -53,8 +53,10 @@ use core::core::amount_to_hr_string;
|
||||||
use core::global;
|
use core::global;
|
||||||
use tui::ui;
|
use tui::ui;
|
||||||
use util::{init_logger, LoggingConfig, LOGGER};
|
use util::{init_logger, LoggingConfig, LOGGER};
|
||||||
use wallet::{libwallet, wallet_db_exists, FileWallet,
|
use wallet::{
|
||||||
HTTPWalletClient, LMDBBackend, WalletConfig, WalletInst};
|
libwallet, wallet_db_exists, FileWallet, HTTPWalletClient, LMDBBackend, WalletConfig,
|
||||||
|
WalletInst,
|
||||||
|
};
|
||||||
|
|
||||||
// include build information
|
// include build information
|
||||||
pub mod built_info {
|
pub mod built_info {
|
||||||
|
@ -606,8 +608,8 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
info!(LOGGER, "Wallet seed file created");
|
info!(LOGGER, "Wallet seed file created");
|
||||||
if use_db {
|
if use_db {
|
||||||
let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr);
|
let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr);
|
||||||
let _: LMDBBackend<HTTPWalletClient, keychain::ExtKeychain> = LMDBBackend::new(wallet_config.clone(), "", client)
|
let _: LMDBBackend<HTTPWalletClient, keychain::ExtKeychain> =
|
||||||
.unwrap_or_else(|e| {
|
LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| {
|
||||||
panic!(
|
panic!(
|
||||||
"Error creating DB wallet: {} Config: {:?}",
|
"Error creating DB wallet: {} Config: {:?}",
|
||||||
e, wallet_config
|
e, wallet_config
|
||||||
|
@ -658,7 +660,11 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
|
|
||||||
// Handle single-use (command line) owner commands
|
// Handle single-use (command line) owner commands
|
||||||
{
|
{
|
||||||
let wallet = instantiate_wallet(wallet_config.clone(), passphrase, use_db);
|
let wallet = Arc::new(Mutex::new(instantiate_wallet(
|
||||||
|
wallet_config.clone(),
|
||||||
|
passphrase,
|
||||||
|
use_db,
|
||||||
|
)));
|
||||||
let _res = wallet::controller::owner_single_use(wallet, |api| {
|
let _res = wallet::controller::owner_single_use(wallet, |api| {
|
||||||
match wallet_args.subcommand() {
|
match wallet_args.subcommand() {
|
||||||
("send", Some(send_args)) => {
|
("send", Some(send_args)) => {
|
||||||
|
@ -689,21 +695,20 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
dest,
|
dest,
|
||||||
max_outputs,
|
max_outputs,
|
||||||
selection_strategy == "all",
|
selection_strategy == "all",
|
||||||
fluff,
|
|
||||||
);
|
);
|
||||||
match result {
|
let slate = match result {
|
||||||
Ok(_) => {
|
Ok(s) => {
|
||||||
info!(
|
info!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Tx sent: {} grin to {} (strategy '{}')",
|
"Tx created: {} grin to {} (strategy '{}')",
|
||||||
amount_to_hr_string(amount),
|
amount_to_hr_string(amount),
|
||||||
dest,
|
dest,
|
||||||
selection_strategy,
|
selection_strategy,
|
||||||
);
|
);
|
||||||
Ok(())
|
s
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(LOGGER, "Tx not sent: {:?}", e);
|
error!(LOGGER, "Tx not created: {:?}", e);
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
// user errors, don't backtrace
|
// user errors, don't backtrace
|
||||||
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
||||||
|
@ -714,6 +719,20 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let result = api.post_tx(&slate, fluff);
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
info!(
|
||||||
|
LOGGER,
|
||||||
|
"Tx sent",
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "Tx not sent: {:?}", e);
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ use tokio_core::reactor;
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
use error::{Error, ErrorKind};
|
use error::{Error, ErrorKind};
|
||||||
use libwallet;
|
|
||||||
use libtx::slate::Slate;
|
use libtx::slate::Slate;
|
||||||
|
use libwallet;
|
||||||
use util::secp::pedersen;
|
use util::secp::pedersen;
|
||||||
use util::{self, LOGGER};
|
use util::{self, LOGGER};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ pub struct HTTPWalletClient {
|
||||||
|
|
||||||
impl HTTPWalletClient {
|
impl HTTPWalletClient {
|
||||||
/// Create a new client that will communicate with the given grin node
|
/// Create a new client that will communicate with the given grin node
|
||||||
pub fn new(node_url:&str) -> HTTPWalletClient {
|
pub fn new(node_url: &str) -> HTTPWalletClient {
|
||||||
HTTPWalletClient {
|
HTTPWalletClient {
|
||||||
node_url: node_url.to_owned(),
|
node_url: node_url.to_owned(),
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,14 @@ impl WalletClient for HTTPWalletClient {
|
||||||
&self.node_url
|
&self.node_url
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
/// Call the wallet API to create a coinbase output for the given
|
||||||
/// Will retry based on default "retry forever with backoff" behavior.
|
/// block_fees. Will retry based on default "retry forever with backoff"
|
||||||
fn create_coinbase(&self, dest: &str, block_fees: &BlockFees) -> Result<CbData, libwallet::Error> {
|
/// behavior.
|
||||||
|
fn create_coinbase(
|
||||||
|
&self,
|
||||||
|
dest: &str,
|
||||||
|
block_fees: &BlockFees,
|
||||||
|
) -> Result<CbData, libwallet::Error> {
|
||||||
let url = format!("{}/v1/wallet/foreign/build_coinbase", dest);
|
let url = format!("{}/v1/wallet/foreign/build_coinbase", dest);
|
||||||
match single_create_coinbase(&url, &block_fees) {
|
match single_create_coinbase(&url, &block_fees) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -64,7 +69,9 @@ impl WalletClient for HTTPWalletClient {
|
||||||
);
|
);
|
||||||
error!(LOGGER, "Underlying Error: {}", e.cause().unwrap());
|
error!(LOGGER, "Underlying Error: {}", e.cause().unwrap());
|
||||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||||
Err(libwallet::ErrorKind::ClientCallback("Failed to get coinbase"))?
|
Err(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Failed to get coinbase",
|
||||||
|
))?
|
||||||
}
|
}
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
}
|
}
|
||||||
|
@ -83,35 +90,41 @@ impl WalletClient for HTTPWalletClient {
|
||||||
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
||||||
debug!(LOGGER, "Posting transaction slate to {}", url);
|
debug!(LOGGER, "Posting transaction slate to {}", url);
|
||||||
|
|
||||||
let mut core = reactor::Core::new()
|
let mut core = reactor::Core::new().context(libwallet::ErrorKind::ClientCallback(
|
||||||
.context(libwallet::ErrorKind::ClientCallback("Sending transaction: Initialise API"))?;
|
"Sending transaction: Initialise API",
|
||||||
|
))?;
|
||||||
let client = hyper::Client::new(&core.handle());
|
let client = hyper::Client::new(&core.handle());
|
||||||
|
|
||||||
let url_pool = url.to_owned();
|
let url_pool = url.to_owned();
|
||||||
|
|
||||||
let mut req = Request::new(
|
let mut req = Request::new(
|
||||||
Method::Post,
|
Method::Post,
|
||||||
url_pool.parse::<hyper::Uri>()
|
url_pool
|
||||||
.context(libwallet::ErrorKind::ClientCallback("Sending transaction: parsing URL"))?
|
.parse::<hyper::Uri>()
|
||||||
|
.context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Sending transaction: parsing URL",
|
||||||
|
))?,
|
||||||
);
|
);
|
||||||
req.headers_mut().set(ContentType::json());
|
req.headers_mut().set(ContentType::json());
|
||||||
let json = serde_json::to_string(&slate)
|
let json = serde_json::to_string(&slate).context(libwallet::ErrorKind::ClientCallback(
|
||||||
.context(libwallet::ErrorKind::ClientCallback("Sending transaction: parsing response"))?;
|
"Sending transaction: parsing response",
|
||||||
|
))?;
|
||||||
req.set_body(json);
|
req.set_body(json);
|
||||||
|
|
||||||
let work = client.request(req).and_then(|res| {
|
let work = client.request(req).and_then(|res| {
|
||||||
res.body().concat2().and_then(move |body| {
|
res.body().concat2().and_then(move |body| {
|
||||||
let slate: Slate =
|
let slate: Slate = serde_json::from_slice(&body)
|
||||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
Ok(slate)
|
Ok(slate)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let res = core.run(work)
|
let res = core.run(work)
|
||||||
.context(libwallet::ErrorKind::ClientCallback("Sending transaction: posting request"))?;
|
.context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Sending transaction: posting request",
|
||||||
|
))?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Posts a transaction to a grin node
|
/// Posts a transaction to a grin node
|
||||||
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> {
|
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> {
|
||||||
let url;
|
let url;
|
||||||
|
@ -121,8 +134,9 @@ impl WalletClient for HTTPWalletClient {
|
||||||
} else {
|
} else {
|
||||||
url = format!("{}/v1/pool/push", dest);
|
url = format!("{}/v1/pool/push", dest);
|
||||||
}
|
}
|
||||||
api::client::post(url.as_str(), tx)
|
api::client::post(url.as_str(), tx).context(libwallet::ErrorKind::ClientCallback(
|
||||||
.context(libwallet::ErrorKind::ClientCallback("Posting transaction to node"))?;
|
"Posting transaction to node",
|
||||||
|
))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,8 +144,9 @@ impl WalletClient for HTTPWalletClient {
|
||||||
fn get_chain_height(&self) -> Result<u64, libwallet::Error> {
|
fn get_chain_height(&self) -> Result<u64, libwallet::Error> {
|
||||||
let addr = self.node_url();
|
let addr = self.node_url();
|
||||||
let url = format!("{}/v1/chain", addr);
|
let url = format!("{}/v1/chain", addr);
|
||||||
let res = api::client::get::<api::Tip>(url.as_str())
|
let res = api::client::get::<api::Tip>(url.as_str()).context(
|
||||||
.context(libwallet::ErrorKind::ClientCallback("Getting chain height from node"))?;
|
libwallet::ErrorKind::ClientCallback("Getting chain height from node"),
|
||||||
|
)?;
|
||||||
Ok(res.height)
|
Ok(res.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +220,9 @@ impl WalletClient for HTTPWalletClient {
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"get_outputs_by_pmmr_index: unable to contact API {}. Error: {}", addr, e
|
"get_outputs_by_pmmr_index: unable to contact API {}. Error: {}", addr, e
|
||||||
);
|
);
|
||||||
Err(libwallet::ErrorKind::ClientCallback("unable to contact api"))?
|
Err(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"unable to contact api",
|
||||||
|
))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,8 +247,9 @@ pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result<CbData, Err
|
||||||
|
|
||||||
/// Makes a single request to the wallet API to create a new coinbase output.
|
/// Makes a single request to the wallet API to create a new coinbase output.
|
||||||
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||||
let mut core =
|
let mut core = reactor::Core::new().context(ErrorKind::GenericError(
|
||||||
reactor::Core::new().context(ErrorKind::GenericError("Could not create reactor"))?;
|
"Could not create reactor".to_owned(),
|
||||||
|
))?;
|
||||||
let client = hyper::Client::new(&core.handle());
|
let client = hyper::Client::new(&core.handle());
|
||||||
|
|
||||||
let mut req = Request::new(
|
let mut req = Request::new(
|
||||||
|
@ -253,7 +271,6 @@ fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, E
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = core.run(work)
|
let res = core.run(work)
|
||||||
.context(ErrorKind::GenericError("Could not run core"))?;
|
.context(ErrorKind::GenericError("Could not run core".to_owned()))?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ pub enum ErrorKind {
|
||||||
|
|
||||||
/// Other
|
/// Other
|
||||||
#[fail(display = "Generic error: {}", _0)]
|
#[fail(display = "Generic error: {}", _0)]
|
||||||
GenericError(&'static str),
|
GenericError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fail for Error {
|
impl Fail for Error {
|
||||||
|
|
|
@ -177,13 +177,12 @@ where
|
||||||
self.keychain = Some(keychain.context(libwallet::ErrorKind::CallbackImpl(
|
self.keychain = Some(keychain.context(libwallet::ErrorKind::CallbackImpl(
|
||||||
"Error deriving keychain",
|
"Error deriving keychain",
|
||||||
))?);
|
))?);
|
||||||
// Just blow up password for now after it's been used
|
|
||||||
self.passphrase = String::from("");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close wallet and remove any stored credentials (TBD)
|
/// Close wallet and remove any stored credentials (TBD)
|
||||||
fn close(&mut self) -> Result<(), libwallet::Error> {
|
fn close(&mut self) -> Result<(), libwallet::Error> {
|
||||||
|
debug!(LOGGER, "Closing wallet keychain");
|
||||||
self.keychain = None;
|
self.keychain = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
//! Still experimental, not sure this is the best way to do this
|
//! Still experimental, not sure this is the best way to do this
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use core::ser;
|
use core::ser;
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
|
@ -31,27 +32,27 @@ use util::{self, LOGGER};
|
||||||
|
|
||||||
/// Wrapper around internal API functions, containing a reference to
|
/// Wrapper around internal API functions, containing a reference to
|
||||||
/// the wallet/keychain that they're acting upon
|
/// the wallet/keychain that they're acting upon
|
||||||
pub struct APIOwner<'a, W: ?Sized, C, K>
|
pub struct APIOwner<W: ?Sized, C, K>
|
||||||
where
|
where
|
||||||
W: 'a + WalletBackend<C, K>,
|
W: WalletBackend<C, K>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||||
/// perhaps)
|
/// perhaps)
|
||||||
pub wallet: &'a mut Box<W>,
|
pub wallet: Arc<Mutex<Box<W>>>,
|
||||||
phantom: PhantomData<K>,
|
phantom: PhantomData<K>,
|
||||||
phantom_c: PhantomData<C>,
|
phantom_c: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, W: ?Sized, C, K> APIOwner<'a, W, C, K>
|
impl<W: ?Sized, C, K> APIOwner<W, C, K>
|
||||||
where
|
where
|
||||||
W: 'a + WalletBackend<C, K>,
|
W: WalletBackend<C, K>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
/// Create new API instance
|
/// Create new API instance
|
||||||
pub fn new(wallet_in: &'a mut Box<W>) -> APIOwner<'a, W, C, K> {
|
pub fn new(wallet_in: Arc<Mutex<Box<W>>>) -> Self {
|
||||||
APIOwner {
|
APIOwner {
|
||||||
wallet: wallet_in,
|
wallet: wallet_in,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
|
@ -62,18 +63,26 @@ where
|
||||||
/// Attempt to update and retrieve outputs
|
/// Attempt to update and retrieve outputs
|
||||||
/// Return (whether the outputs were validated against a node, OutputData)
|
/// Return (whether the outputs were validated against a node, OutputData)
|
||||||
pub fn retrieve_outputs(
|
pub fn retrieve_outputs(
|
||||||
&mut self,
|
&self,
|
||||||
include_spent: bool,
|
include_spent: bool,
|
||||||
refresh_from_node: bool,
|
refresh_from_node: bool,
|
||||||
) -> Result<(bool, Vec<OutputData>), Error> {
|
) -> Result<(bool, Vec<OutputData>), Error> {
|
||||||
|
|
||||||
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = self.update_outputs();
|
validated = self.update_outputs(&mut w);
|
||||||
}
|
}
|
||||||
Ok((
|
|
||||||
|
let res = Ok((
|
||||||
validated,
|
validated,
|
||||||
updater::retrieve_outputs(&mut **self.wallet, include_spent)?,
|
updater::retrieve_outputs(&mut **w, include_spent)?,
|
||||||
))
|
));
|
||||||
|
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve summary info for wallet
|
/// Retrieve summary info for wallet
|
||||||
|
@ -81,12 +90,19 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
refresh_from_node: bool,
|
refresh_from_node: bool,
|
||||||
) -> Result<(bool, WalletInfo), Error> {
|
) -> Result<(bool, WalletInfo), Error> {
|
||||||
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = self.update_outputs();
|
validated = self.update_outputs(&mut w);
|
||||||
}
|
}
|
||||||
let wallet_info = updater::retrieve_info(&mut **self.wallet)?;
|
|
||||||
Ok((validated, wallet_info))
|
let wallet_info = updater::retrieve_info(&mut **w)?;
|
||||||
|
let res = Ok((validated, wallet_info));
|
||||||
|
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Issues a send transaction and sends to recipient
|
/// Issues a send transaction and sends to recipient
|
||||||
|
@ -97,37 +113,42 @@ where
|
||||||
dest: &str,
|
dest: &str,
|
||||||
max_outputs: usize,
|
max_outputs: usize,
|
||||||
selection_strategy_is_use_all: bool,
|
selection_strategy_is_use_all: bool,
|
||||||
fluff: bool,
|
) -> Result<Slate, Error> {
|
||||||
) -> Result<(), Error> {
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
|
||||||
|
let client;
|
||||||
|
let mut slate_out: Slate;
|
||||||
|
let lock_fn_out;
|
||||||
|
|
||||||
|
client = w.client().clone();
|
||||||
let (slate, context, lock_fn) = tx::create_send_tx(
|
let (slate, context, lock_fn) = tx::create_send_tx(
|
||||||
&mut **self.wallet,
|
&mut **w,
|
||||||
amount,
|
amount,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
max_outputs,
|
max_outputs,
|
||||||
selection_strategy_is_use_all,
|
selection_strategy_is_use_all,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut slate = match self.wallet.client().send_tx_slate(dest, &slate) {
|
lock_fn_out = lock_fn;
|
||||||
|
slate_out = match w.client().send_tx_slate(dest, &slate) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Communication with receiver failed on SenderInitiation send. Aborting transaction {:?}",
|
"Communication with receiver failed on SenderInitiation send. Aborting transaction {:?}",
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
return Err(e)?;
|
return Err(e)?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tx::complete_tx(&mut **self.wallet, &mut slate, &context)?;
|
tx::complete_tx(&mut **w, &mut slate_out, &context)?;
|
||||||
|
|
||||||
// All good here, so let's post it
|
// lock our inputs
|
||||||
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
|
lock_fn_out(&mut **w)?;
|
||||||
self.wallet.client().post_tx(&TxWrapper { tx_hex: tx_hex }, fluff)?;
|
w.close()?;
|
||||||
|
Ok(slate_out)
|
||||||
// All good here, lock our inputs
|
|
||||||
lock_fn(self.wallet)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Issue a burn TX
|
/// Issue a burn TX
|
||||||
|
@ -137,40 +158,60 @@ where
|
||||||
minimum_confirmations: u64,
|
minimum_confirmations: u64,
|
||||||
max_outputs: usize,
|
max_outputs: usize,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let tx_burn = tx::issue_burn_tx(
|
let mut w = self.wallet.lock().unwrap();
|
||||||
&mut **self.wallet,
|
w.open_with_credentials()?;
|
||||||
amount,
|
let tx_burn = tx::issue_burn_tx(&mut **w, amount, minimum_confirmations, max_outputs)?;
|
||||||
minimum_confirmations,
|
|
||||||
max_outputs,
|
|
||||||
)?;
|
|
||||||
let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap());
|
let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap());
|
||||||
self.wallet.client().post_tx(&TxWrapper { tx_hex: tx_hex }, false)?;
|
w.client().post_tx(&TxWrapper { tx_hex: tx_hex }, false)?;
|
||||||
|
w.close()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Posts a transaction to the chain
|
||||||
|
pub fn post_tx(&self, slate: &Slate, fluff: bool) -> Result<(), Error> {
|
||||||
|
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
|
||||||
|
let client = {
|
||||||
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.client().clone()
|
||||||
|
};
|
||||||
|
client.post_tx(&TxWrapper { tx_hex: tx_hex }, fluff)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to restore contents of wallet
|
/// Attempt to restore contents of wallet
|
||||||
pub fn restore(&mut self) -> Result<(), Error> {
|
pub fn restore(&mut self) -> Result<(), Error> {
|
||||||
self.wallet.restore()
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
let res = w.restore();
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve current height from node
|
/// Retrieve current height from node
|
||||||
pub fn node_height(&mut self) -> Result<(u64, bool), Error> {
|
pub fn node_height(&mut self) -> Result<(u64, bool), Error> {
|
||||||
match self.wallet.client().get_chain_height() {
|
let mut w = self.wallet.lock().unwrap();
|
||||||
Ok(height) => Ok((height, true)),
|
w.open_with_credentials()?;
|
||||||
|
let res = w.client().get_chain_height();
|
||||||
|
match res {
|
||||||
|
Ok(height) => {
|
||||||
|
w.close()?;
|
||||||
|
Ok((height, true))
|
||||||
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let outputs = self.retrieve_outputs(true, false)?;
|
let outputs = self.retrieve_outputs(true, false)?;
|
||||||
let height = match outputs.1.iter().map(|out| out.height).max() {
|
let height = match outputs.1.iter().map(|out| out.height).max() {
|
||||||
Some(height) => height,
|
Some(height) => height,
|
||||||
None => 0,
|
None => 0,
|
||||||
};
|
};
|
||||||
|
w.close()?;
|
||||||
Ok((height, false))
|
Ok((height, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to update outputs in wallet, return whether it was successful
|
/// Attempt to update outputs in wallet, return whether it was successful
|
||||||
fn update_outputs(&mut self) -> bool {
|
fn update_outputs(&self, w: &mut W ) -> bool {
|
||||||
match updater::refresh_outputs(&mut **self.wallet) {
|
match updater::refresh_outputs(&mut *w) {
|
||||||
Ok(_) => true,
|
Ok(_) => true,
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
}
|
}
|
||||||
|
@ -179,41 +220,50 @@ where
|
||||||
|
|
||||||
/// Wrapper around external API functions, intended to communicate
|
/// Wrapper around external API functions, intended to communicate
|
||||||
/// with other parties
|
/// with other parties
|
||||||
pub struct APIForeign<'a, W: ?Sized, C, K>
|
pub struct APIForeign<W: ?Sized, C, K>
|
||||||
where
|
where
|
||||||
W: 'a + WalletBackend<C, K>,
|
W: WalletBackend<C, K>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||||
/// perhaps)
|
/// perhaps)
|
||||||
pub wallet: &'a mut Box<W>,
|
pub wallet: Arc<Mutex<Box<W>>>,
|
||||||
phantom: PhantomData<K>,
|
phantom: PhantomData<K>,
|
||||||
phantom_c: PhantomData<C>,
|
phantom_c: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, W: ?Sized, C, K> APIForeign<'a, W, C, K>
|
impl<'a, W: ?Sized, C, K> APIForeign<W, C, K>
|
||||||
where
|
where
|
||||||
W: WalletBackend<C, K>,
|
W: WalletBackend<C, K>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
/// Create new API instance
|
/// Create new API instance
|
||||||
pub fn new(wallet_in: &'a mut Box<W>) -> APIForeign<W, C, K> {
|
pub fn new(wallet_in: Arc<Mutex<Box<W>>>) -> Box<Self> {
|
||||||
APIForeign {
|
Box::new(APIForeign {
|
||||||
wallet: wallet_in,
|
wallet: wallet_in,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
phantom_c: PhantomData,
|
phantom_c: PhantomData,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a new (potential) coinbase transaction in the wallet
|
/// Build a new (potential) coinbase transaction in the wallet
|
||||||
pub fn build_coinbase(&mut self, block_fees: &BlockFees) -> Result<CbData, Error> {
|
pub fn build_coinbase(&mut self, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||||
updater::build_coinbase(&mut **self.wallet, block_fees)
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
let res = updater::build_coinbase(&mut **w, block_fees);
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive a transaction from a sender
|
/// Receive a transaction from a sender
|
||||||
pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||||
tx::receive_tx(&mut **self.wallet, slate)
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
let res = tx::receive_tx(&mut **w, slate);
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ use keychain::Keychain;
|
||||||
use libtx::slate::Slate;
|
use libtx::slate::Slate;
|
||||||
use libwallet::api::{APIForeign, APIOwner};
|
use libwallet::api::{APIForeign, APIOwner};
|
||||||
use libwallet::types::{
|
use libwallet::types::{
|
||||||
BlockFees, CbData, OutputData, SendTXArgs, WalletBackend, WalletClient, WalletInfo
|
BlockFees, CbData, OutputData, SendTXArgs, WalletBackend, WalletClient, WalletInfo,
|
||||||
};
|
};
|
||||||
use libwallet::{Error, ErrorKind};
|
use libwallet::{Error, ErrorKind};
|
||||||
|
|
||||||
|
@ -40,32 +40,33 @@ use util::LOGGER;
|
||||||
|
|
||||||
/// Instantiate wallet Owner API for a single-use (command line) call
|
/// Instantiate wallet Owner API for a single-use (command line) call
|
||||||
/// Return a function containing a loaded API context to call
|
/// Return a function containing a loaded API context to call
|
||||||
pub fn owner_single_use<F, T: ?Sized, C, K>(wallet: Box<T>, f: F) -> Result<(), Error>
|
pub fn owner_single_use<F, T: ?Sized, C, K>(
|
||||||
|
wallet: Arc<Mutex<Box<T>>>,
|
||||||
|
f: F,
|
||||||
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K>,
|
||||||
F: FnOnce(&mut APIOwner<T, C, K>) -> Result<(), Error>,
|
F: FnOnce(&mut APIOwner<T, C, K>) -> Result<(), Error>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
let mut w = wallet;
|
f(&mut APIOwner::new(wallet.clone()))?;
|
||||||
w.open_with_credentials()?;
|
|
||||||
f(&mut APIOwner::new(&mut w))?;
|
|
||||||
w.close()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instantiate wallet Foreign API for a single-use (command line) call
|
/// Instantiate wallet Foreign API for a single-use (command line) call
|
||||||
/// Return a function containing a loaded API context to call
|
/// Return a function containing a loaded API context to call
|
||||||
pub fn foreign_single_use<F, T: ?Sized, C, K>(wallet: &mut Box<T>, f: F) -> Result<(), Error>
|
pub fn foreign_single_use<F, T: ?Sized, C, K>(
|
||||||
|
wallet: Arc<Mutex<Box<T>>>,
|
||||||
|
f: F,
|
||||||
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K>,
|
||||||
F: FnOnce(&mut APIForeign<T, C, K>) -> Result<(), Error>,
|
F: FnOnce(&mut APIForeign<T, C, K>) -> Result<(), Error>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
wallet.open_with_credentials()?;
|
f(&mut APIForeign::new(wallet.clone()))?;
|
||||||
f(&mut APIForeign::new(wallet))?;
|
|
||||||
wallet.close()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +194,11 @@ where
|
||||||
api.node_height()
|
api.node_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> IronResult<Response> {
|
fn handle_request(
|
||||||
|
&self,
|
||||||
|
req: &mut Request,
|
||||||
|
api: &mut APIOwner<T, C, K>,
|
||||||
|
) -> IronResult<Response> {
|
||||||
let url = req.url.clone();
|
let url = req.url.clone();
|
||||||
let path_elems = url.path();
|
let path_elems = url.path();
|
||||||
match *path_elems.last().unwrap() {
|
match *path_elems.last().unwrap() {
|
||||||
|
@ -218,16 +223,7 @@ where
|
||||||
K: Keychain + 'static,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||||
// every request should open with stored credentials,
|
let mut api = APIOwner::new(self.wallet.clone());
|
||||||
// do its thing and then de-init whatever secrets have been
|
|
||||||
// stored
|
|
||||||
let mut wallet = self.wallet.lock().unwrap();
|
|
||||||
wallet.open_with_credentials().map_err(|e| {
|
|
||||||
error!(LOGGER, "Error opening wallet: {:?}", e);
|
|
||||||
IronError::new(Fail::compat(e), status::BadRequest)
|
|
||||||
})?;
|
|
||||||
let mut w = wallet;
|
|
||||||
let mut api = APIOwner::new(&mut w);
|
|
||||||
let mut resp_json = self.handle_request(req, &mut api);
|
let mut resp_json = self.handle_request(req, &mut api);
|
||||||
if !resp_json.is_err() {
|
if !resp_json.is_err() {
|
||||||
resp_json
|
resp_json
|
||||||
|
@ -236,9 +232,6 @@ where
|
||||||
.headers
|
.headers
|
||||||
.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
|
.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
|
||||||
}
|
}
|
||||||
api.wallet
|
|
||||||
.close()
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
|
||||||
resp_json
|
resp_json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +264,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<(), Error> {
|
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<Slate, Error> {
|
||||||
let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>();
|
let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>();
|
||||||
match struct_body {
|
match struct_body {
|
||||||
Ok(Some(args)) => api.issue_send_tx(
|
Ok(Some(args)) => api.issue_send_tx(
|
||||||
|
@ -280,18 +273,17 @@ where
|
||||||
&args.dest,
|
&args.dest,
|
||||||
args.max_outputs,
|
args.max_outputs,
|
||||||
args.selection_strategy_is_use_all,
|
args.selection_strategy_is_use_all,
|
||||||
args.fluff,
|
|
||||||
),
|
),
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
error!(LOGGER, "Missing request body: issue_send_tx");
|
error!(LOGGER, "Missing request body: issue_send_tx");
|
||||||
Err(ErrorKind::GenericError(
|
Err(ErrorKind::GenericError(
|
||||||
"Invalid request body: issue_send_tx",
|
"Invalid request body: issue_send_tx".to_owned(),
|
||||||
))?
|
))?
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(LOGGER, "Invalid request body: issue_send_tx {:?}", e);
|
error!(LOGGER, "Invalid request body: issue_send_tx {:?}", e);
|
||||||
Err(ErrorKind::GenericError(
|
Err(ErrorKind::GenericError(
|
||||||
"Invalid request body: issue_send_tx",
|
"Invalid request body: issue_send_tx".to_owned(),
|
||||||
))?
|
))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,14 +294,18 @@ where
|
||||||
api.issue_burn_tx(60, 10, 1000)
|
api.issue_burn_tx(60, 10, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<String, Error> {
|
fn handle_request(
|
||||||
|
&self,
|
||||||
|
req: &mut Request,
|
||||||
|
api: &mut APIOwner<T, C, K>,
|
||||||
|
) -> Result<String, Error> {
|
||||||
let url = req.url.clone();
|
let url = req.url.clone();
|
||||||
let path_elems = url.path();
|
let path_elems = url.path();
|
||||||
match *path_elems.last().unwrap() {
|
match *path_elems.last().unwrap() {
|
||||||
"issue_send_tx" => json_response_pretty(&self.issue_send_tx(req, api)?),
|
"issue_send_tx" => json_response_pretty(&self.issue_send_tx(req, api)?),
|
||||||
"issue_burn_tx" => json_response_pretty(&self.issue_burn_tx(req, api)?),
|
"issue_burn_tx" => json_response_pretty(&self.issue_burn_tx(req, api)?),
|
||||||
_ => Err(ErrorKind::GenericError(
|
_ => Err(ErrorKind::GenericError(
|
||||||
"Unknown error handling post request",
|
"Unknown error handling post request".to_owned(),
|
||||||
))?,
|
))?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,18 +339,7 @@ where
|
||||||
K: Keychain + 'static,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||||
// every request should open with stored credentials,
|
let mut api = APIOwner::new(self.wallet.clone());
|
||||||
// do its thing and then de-init whatever secrets have been
|
|
||||||
// stored
|
|
||||||
{
|
|
||||||
let mut wallet = self.wallet.lock().unwrap();
|
|
||||||
wallet.open_with_credentials().map_err(|e| {
|
|
||||||
error!(LOGGER, "Error opening wallet: {:?}", e);
|
|
||||||
IronError::new(Fail::compat(e), status::BadRequest)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
let mut wallet = self.wallet.lock().unwrap();
|
|
||||||
let mut api = APIOwner::new(&mut wallet);
|
|
||||||
let resp = match self.handle_request(req, &mut api) {
|
let resp = match self.handle_request(req, &mut api) {
|
||||||
Ok(r) => self.create_ok_response(&r),
|
Ok(r) => self.create_ok_response(&r),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -362,9 +347,6 @@ where
|
||||||
self.create_error_response(e)
|
self.create_error_response(e)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
api.wallet
|
|
||||||
.close()
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,13 +407,13 @@ where
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
error!(LOGGER, "Missing request body: build_coinbase");
|
error!(LOGGER, "Missing request body: build_coinbase");
|
||||||
Err(ErrorKind::GenericError(
|
Err(ErrorKind::GenericError(
|
||||||
"Invalid request body: build_coinbase",
|
"Invalid request body: build_coinbase".to_owned(),
|
||||||
))?
|
))?
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(LOGGER, "Invalid request body: build_coinbase: {:?}", e);
|
error!(LOGGER, "Invalid request body: build_coinbase: {:?}", e);
|
||||||
Err(ErrorKind::GenericError(
|
Err(ErrorKind::GenericError(
|
||||||
"Invalid request body: build_coinbase",
|
"Invalid request body: build_coinbase".to_owned(),
|
||||||
))?
|
))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,7 +425,9 @@ where
|
||||||
api.receive_tx(&mut slate)?;
|
api.receive_tx(&mut slate)?;
|
||||||
Ok(slate.clone())
|
Ok(slate.clone())
|
||||||
} else {
|
} else {
|
||||||
Err(ErrorKind::GenericError("Invalid request body: receive_tx"))?
|
Err(ErrorKind::GenericError(
|
||||||
|
"Invalid request body: receive_tx".to_owned(),
|
||||||
|
))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,22 +457,8 @@ where
|
||||||
K: Keychain + 'static,
|
K: Keychain + 'static,
|
||||||
{
|
{
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||||
// every request should open with stored credentials,
|
let mut api = APIForeign::new(self.wallet.clone());
|
||||||
// do its thing and then de-init whatever secrets have been
|
let resp_json = self.handle_request(req, &mut *api);
|
||||||
// stored
|
|
||||||
{
|
|
||||||
let mut wallet = self.wallet.lock().unwrap();
|
|
||||||
wallet.open_with_credentials().map_err(|e| {
|
|
||||||
error!(LOGGER, "Error opening wallet: {:?}", e);
|
|
||||||
IronError::new(Fail::compat(e), status::BadRequest)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
let mut wallet = self.wallet.lock().unwrap();
|
|
||||||
let mut api = APIForeign::new(&mut wallet);
|
|
||||||
let resp_json = self.handle_request(req, &mut api);
|
|
||||||
api.wallet
|
|
||||||
.close()
|
|
||||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
|
||||||
resp_json
|
resp_json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ pub enum ErrorKind {
|
||||||
|
|
||||||
/// Other
|
/// Other
|
||||||
#[fail(display = "Generic error: {}", _0)]
|
#[fail(display = "Generic error: {}", _0)]
|
||||||
GenericError(&'static str),
|
GenericError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
|
|
|
@ -306,7 +306,7 @@ impl BlockIdentifier {
|
||||||
|
|
||||||
/// convert to hex string
|
/// convert to hex string
|
||||||
pub fn from_hex(hex: &str) -> Result<BlockIdentifier, Error> {
|
pub fn from_hex(hex: &str) -> Result<BlockIdentifier, Error> {
|
||||||
let hash = Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex"))?;
|
let hash = Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex".to_owned()))?;
|
||||||
Ok(BlockIdentifier(hash))
|
Ok(BlockIdentifier(hash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,8 +78,8 @@ impl WalletSeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_hex(hex: &str) -> Result<WalletSeed, Error> {
|
fn from_hex(hex: &str) -> Result<WalletSeed, Error> {
|
||||||
let bytes =
|
let bytes = util::from_hex(hex.to_string())
|
||||||
util::from_hex(hex.to_string()).context(ErrorKind::GenericError("Invalid hex"))?;
|
.context(ErrorKind::GenericError("Invalid hex".to_owned()))?;
|
||||||
Ok(WalletSeed::from_bytes(&bytes))
|
Ok(WalletSeed::from_bytes(&bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,112 +12,42 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Common functions to facilitate wallet, walletlib and transaction testing
|
extern crate failure;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
|
|
||||||
extern crate grin_api as api;
|
extern crate grin_api as api;
|
||||||
extern crate grin_chain as chain;
|
extern crate grin_chain as chain;
|
||||||
extern crate grin_core as core;
|
extern crate grin_core as core;
|
||||||
extern crate grin_keychain as keychain;
|
extern crate grin_keychain as keychain;
|
||||||
extern crate grin_wallet as wallet;
|
extern crate grin_wallet as wallet;
|
||||||
|
extern crate serde_json;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use chain::Chain;
|
use chain::Chain;
|
||||||
use core::core::hash::Hashed;
|
use core::core::{OutputFeatures, OutputIdentifier, Transaction};
|
||||||
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
|
use core::{consensus, global, pow, ser};
|
||||||
use core::{consensus, global, pow};
|
|
||||||
use keychain::ExtKeychain;
|
|
||||||
use wallet::{HTTPWalletClient, WalletConfig};
|
|
||||||
use wallet::file_wallet::FileWallet;
|
use wallet::file_wallet::FileWallet;
|
||||||
use wallet::libwallet::internal::updater;
|
use wallet::libwallet;
|
||||||
use wallet::libwallet::types::{BlockFees, BlockIdentifier, OutputStatus,
|
use wallet::libwallet::types::{BlockFees, CbData, WalletClient, WalletInst};
|
||||||
WalletBackend, WalletClient};
|
use wallet::lmdb_wallet::LMDBBackend;
|
||||||
use wallet::libwallet::{Error, ErrorKind};
|
use wallet::WalletConfig;
|
||||||
|
|
||||||
use util;
|
use util;
|
||||||
use util::secp::pedersen;
|
use util::secp::pedersen;
|
||||||
|
|
||||||
/// Mostly for testing, refreshes output state against a local chain instance
|
pub mod testclient;
|
||||||
/// instead of via an http API call
|
|
||||||
pub fn refresh_output_state_local<T, C, K>(wallet: &mut T, chain: &chain::Chain) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
T: WalletBackend<C, K>,
|
|
||||||
C: WalletClient,
|
|
||||||
K: keychain::Keychain,
|
|
||||||
{
|
|
||||||
let wallet_outputs = updater::map_wallet_outputs(wallet)?;
|
|
||||||
let chain_outputs: Vec<Option<api::Output>> = wallet_outputs
|
|
||||||
.keys()
|
|
||||||
.map(|k| match get_output_local(chain, &k) {
|
|
||||||
Err(_) => None,
|
|
||||||
Ok(k) => Some(k),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let mut api_outputs: HashMap<pedersen::Commitment, String> = HashMap::new();
|
|
||||||
for out in chain_outputs {
|
|
||||||
match out {
|
|
||||||
Some(o) => {
|
|
||||||
api_outputs.insert(o.commit.commit(), util::to_hex(o.commit.to_vec()));
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let height = chain.head().unwrap().height;
|
|
||||||
updater::apply_api_outputs(wallet, &wallet_outputs, &api_outputs, height)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the spendable wallet balance from the local chain
|
/// types of backends tests should iterate through
|
||||||
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked,
|
#[derive(Clone)]
|
||||||
/// 3:currently_spendable, 4:locked total) TODO: Should be a wallet lib
|
pub enum BackendType {
|
||||||
/// function with nicer return values
|
/// File
|
||||||
pub fn get_wallet_balances<T, C, K>(
|
FileBackend,
|
||||||
wallet: &mut T,
|
/// LMDB
|
||||||
height: u64,
|
LMDBBackend,
|
||||||
) -> Result<(u64, u64, u64, u64, u64), Error>
|
|
||||||
where
|
|
||||||
T: WalletBackend<C, K>,
|
|
||||||
C: WalletClient,
|
|
||||||
K: keychain::Keychain,
|
|
||||||
{
|
|
||||||
let mut unspent_total = 0;
|
|
||||||
let mut unspent_but_locked_total = 0;
|
|
||||||
let mut unconfirmed_total = 0;
|
|
||||||
let mut locked_total = 0;
|
|
||||||
let keychain = wallet.keychain().clone();
|
|
||||||
for out in wallet
|
|
||||||
.iter()
|
|
||||||
.filter(|out| out.root_key_id == keychain.root_key_id())
|
|
||||||
{
|
|
||||||
if out.status == OutputStatus::Unspent {
|
|
||||||
unspent_total += out.value;
|
|
||||||
if out.lock_height > height {
|
|
||||||
unspent_but_locked_total += out.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if out.status == OutputStatus::Unconfirmed && !out.is_coinbase {
|
|
||||||
unconfirmed_total += out.value;
|
|
||||||
}
|
|
||||||
if out.status == OutputStatus::Locked {
|
|
||||||
locked_total += out.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
unspent_total + unconfirmed_total, //total
|
|
||||||
unconfirmed_total, //amount_awaiting_confirmation
|
|
||||||
unspent_but_locked_total, // confirmed but locked
|
|
||||||
unspent_total - unspent_but_locked_total, // currently spendable
|
|
||||||
locked_total, // locked total
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an output from the chain locally and present it back as an API output
|
/// Get an output from the chain locally and present it back as an API output
|
||||||
fn get_output_local(
|
fn get_output_local(chain: &chain::Chain, commit: &pedersen::Commitment) -> Option<api::Output> {
|
||||||
chain: &chain::Chain,
|
|
||||||
commit: &pedersen::Commitment,
|
|
||||||
) -> Result<api::Output, Error> {
|
|
||||||
let outputs = [
|
let outputs = [
|
||||||
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, commit),
|
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, commit),
|
||||||
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, commit),
|
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, commit),
|
||||||
|
@ -125,23 +55,25 @@ fn get_output_local(
|
||||||
|
|
||||||
for x in outputs.iter() {
|
for x in outputs.iter() {
|
||||||
if let Ok(_) = chain.is_unspent(&x) {
|
if let Ok(_) = chain.is_unspent(&x) {
|
||||||
return Ok(api::Output::new(&commit));
|
return Some(api::Output::new(&commit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ErrorKind::GenericError(
|
None
|
||||||
"Can't get output from local instance of chain",
|
|
||||||
))?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a block with a given reward to the chain and mines it
|
/// Adds a block with a given reward to the chain and mines it
|
||||||
pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Output, TxKernel)) {
|
pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: CbData) {
|
||||||
let prev = chain.head_header().unwrap();
|
let prev = chain.head_header().unwrap();
|
||||||
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
|
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
|
||||||
|
let out_bin = util::from_hex(reward.output).unwrap();
|
||||||
|
let kern_bin = util::from_hex(reward.kernel).unwrap();
|
||||||
|
let output = ser::deserialize(&mut &out_bin[..]).unwrap();
|
||||||
|
let kernel = ser::deserialize(&mut &kern_bin[..]).unwrap();
|
||||||
let mut b = core::core::Block::new(
|
let mut b = core::core::Block::new(
|
||||||
&prev,
|
&prev,
|
||||||
txs.into_iter().cloned().collect(),
|
txs.into_iter().cloned().collect(),
|
||||||
difficulty.clone(),
|
difficulty.clone(),
|
||||||
reward,
|
(output, kernel),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
|
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
|
||||||
chain.set_txhashset_roots(&mut b, false).unwrap();
|
chain.set_txhashset_roots(&mut b, false).unwrap();
|
||||||
|
@ -158,57 +90,82 @@ pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Out
|
||||||
/// adds a reward output to a wallet, includes that reward in a block, mines
|
/// adds a reward output to a wallet, includes that reward in a block, mines
|
||||||
/// the block and adds it to the chain, with option transactions included.
|
/// the block and adds it to the chain, with option transactions included.
|
||||||
/// Helpful for building up precise wallet balances for testing.
|
/// Helpful for building up precise wallet balances for testing.
|
||||||
pub fn award_block_to_wallet<T, C, K>(chain: &Chain, txs: Vec<&Transaction>, wallet: &mut T)
|
pub fn award_block_to_wallet<C, K>(
|
||||||
|
chain: &Chain,
|
||||||
|
txs: Vec<&Transaction>,
|
||||||
|
wallet: Arc<Mutex<Box<WalletInst<C, K>>>>,
|
||||||
|
) -> Result<(), libwallet::Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: keychain::Keychain,
|
K: keychain::Keychain,
|
||||||
{
|
{
|
||||||
|
// build block fees
|
||||||
let prev = chain.head_header().unwrap();
|
let prev = chain.head_header().unwrap();
|
||||||
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
|
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
|
||||||
let fees = BlockFees {
|
let block_fees = BlockFees {
|
||||||
fees: fee_amt,
|
fees: fee_amt,
|
||||||
key_id: None,
|
key_id: None,
|
||||||
height: prev.height + 1,
|
height: prev.height + 1,
|
||||||
};
|
};
|
||||||
let coinbase_tx = wallet::libwallet::internal::updater::receive_coinbase(wallet, &fees);
|
// build coinbase (via api) and add block
|
||||||
let (coinbase_tx, fees) = match coinbase_tx {
|
libwallet::controller::foreign_single_use(wallet.clone(), |api| {
|
||||||
Ok(t) => ((t.0, t.1), t.2),
|
let coinbase_tx = api.build_coinbase(&block_fees)?;
|
||||||
Err(e) => {
|
add_block_with_reward(chain, txs, coinbase_tx.clone());
|
||||||
panic!("Unable to create block reward: {:?}", e);
|
Ok(())
|
||||||
}
|
})?;
|
||||||
};
|
Ok(())
|
||||||
add_block_with_reward(chain, txs, coinbase_tx.clone());
|
|
||||||
let output = wallet.get(&fees.key_id.unwrap()).unwrap();
|
|
||||||
let mut batch = wallet.batch().unwrap();
|
|
||||||
batch.save(output).unwrap();
|
|
||||||
batch.commit().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// adds many block rewards to a wallet, no transactions
|
/// Award a blocks to a wallet directly
|
||||||
pub fn award_blocks_to_wallet<T, C, K>(chain: &Chain, wallet: &mut T, num_rewards: usize)
|
pub fn award_blocks_to_wallet<C, K>(
|
||||||
|
chain: &Chain,
|
||||||
|
wallet: Arc<Mutex<Box<WalletInst<C, K>>>>,
|
||||||
|
number: usize,
|
||||||
|
) -> Result<(), libwallet::Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
K: keychain::Keychain,
|
K: keychain::Keychain,
|
||||||
{
|
{
|
||||||
for _ in 0..num_rewards {
|
for _ in 0..number {
|
||||||
award_block_to_wallet(chain, vec![], wallet);
|
award_block_to_wallet(chain, vec![], wallet.clone())?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new wallet in a particular directory
|
/// dispatch a wallet (extend later to optionally dispatch a db wallet)
|
||||||
pub fn create_wallet(dir: &str, client: HTTPWalletClient) -> FileWallet<HTTPWalletClient, ExtKeychain> {
|
pub fn create_wallet<C, K>(
|
||||||
|
dir: &str,
|
||||||
|
client: C,
|
||||||
|
backend_type: BackendType,
|
||||||
|
) -> Arc<Mutex<Box<WalletInst<C, K>>>>
|
||||||
|
where
|
||||||
|
C: WalletClient + 'static,
|
||||||
|
K: keychain::Keychain + 'static,
|
||||||
|
{
|
||||||
let mut wallet_config = WalletConfig::default();
|
let mut wallet_config = WalletConfig::default();
|
||||||
wallet_config.data_file_dir = String::from(dir);
|
wallet_config.data_file_dir = String::from(dir);
|
||||||
wallet::WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file.");
|
let _ = wallet::WalletSeed::init_file(&wallet_config);
|
||||||
let mut wallet = FileWallet::new(wallet_config.clone(), "", client)
|
let mut wallet: Box<WalletInst<C, K>> = match backend_type {
|
||||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
|
BackendType::FileBackend => {
|
||||||
|
let mut wallet: FileWallet<C, K> = FileWallet::new(wallet_config.clone(), "", client)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
|
||||||
|
});
|
||||||
|
Box::new(wallet)
|
||||||
|
}
|
||||||
|
BackendType::LMDBBackend => {
|
||||||
|
let mut wallet: LMDBBackend<C, K> = LMDBBackend::new(wallet_config.clone(), "", client)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
|
||||||
|
});
|
||||||
|
Box::new(wallet)
|
||||||
|
}
|
||||||
|
};
|
||||||
wallet.open_with_credentials().unwrap_or_else(|e| {
|
wallet.open_with_credentials().unwrap_or_else(|e| {
|
||||||
panic!(
|
panic!(
|
||||||
"Error initializing wallet: {:?} Config: {:?}",
|
"Error initializing wallet: {:?} Config: {:?}",
|
||||||
e, wallet_config
|
e, wallet_config
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
wallet
|
Arc::new(Mutex::new(wallet))
|
||||||
}
|
}
|
||||||
|
|
420
wallet/tests/common/testclient.rs
Normal file
420
wallet/tests/common/testclient.rs
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
// Copyright 2018 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Test client that acts against a local instance of a node
|
||||||
|
//! so that wallet API can be fully exercised
|
||||||
|
//! Operates directly on a chain instance
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use common::api;
|
||||||
|
use common::serde_json;
|
||||||
|
use store;
|
||||||
|
use util::secp::pedersen::Commitment;
|
||||||
|
use util::{self, LOGGER};
|
||||||
|
|
||||||
|
use common::failure::ResultExt;
|
||||||
|
|
||||||
|
use chain::types::NoopAdapter;
|
||||||
|
use chain::Chain;
|
||||||
|
use core::core::Transaction;
|
||||||
|
use core::global::{set_mining_mode, ChainTypes};
|
||||||
|
use core::{pow, ser};
|
||||||
|
use keychain::Keychain;
|
||||||
|
|
||||||
|
use util::secp::pedersen;
|
||||||
|
use wallet::libtx::slate::Slate;
|
||||||
|
use wallet::libwallet;
|
||||||
|
use wallet::libwallet::types::*;
|
||||||
|
|
||||||
|
use common;
|
||||||
|
|
||||||
|
/// Messages to simulate wallet requests/responses
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct WalletProxyMessage {
|
||||||
|
/// sender ID
|
||||||
|
pub sender_id: String,
|
||||||
|
/// destination wallet (or server)
|
||||||
|
pub dest: String,
|
||||||
|
/// method (like a GET url)
|
||||||
|
pub method: String,
|
||||||
|
/// payload (json body)
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// communicates with a chain instance or other wallet
|
||||||
|
/// listener APIs via message queues
|
||||||
|
pub struct WalletProxy<C, K>
|
||||||
|
where
|
||||||
|
C: WalletClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
/// directory to create the chain in
|
||||||
|
pub chain_dir: String,
|
||||||
|
/// handle to chain itself
|
||||||
|
pub chain: Arc<Chain>,
|
||||||
|
/// list of interested wallets
|
||||||
|
pub wallets: HashMap<
|
||||||
|
String,
|
||||||
|
(
|
||||||
|
Sender<WalletProxyMessage>,
|
||||||
|
Arc<Mutex<Box<WalletInst<LocalWalletClient, K>>>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
/// simulate json send to another client
|
||||||
|
/// address, method, payload (simulate HTTP request)
|
||||||
|
pub tx: Sender<WalletProxyMessage>,
|
||||||
|
/// simulate json receiving
|
||||||
|
pub rx: Receiver<WalletProxyMessage>,
|
||||||
|
/// queue control
|
||||||
|
pub running: Arc<AtomicBool>,
|
||||||
|
/// Phantom
|
||||||
|
phantom_c: PhantomData<C>,
|
||||||
|
/// Phantom
|
||||||
|
phantom_k: PhantomData<K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> WalletProxy<C, K>
|
||||||
|
where
|
||||||
|
C: WalletClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
/// Create a new client that will communicate with the given grin node
|
||||||
|
pub fn new(chain_dir: &str) -> Self {
|
||||||
|
set_mining_mode(ChainTypes::AutomatedTesting);
|
||||||
|
let genesis_block = pow::mine_genesis_block().unwrap();
|
||||||
|
let dir_name = format!("{}/.grin", chain_dir);
|
||||||
|
let db_env = Arc::new(store::new_env(dir_name.to_string()));
|
||||||
|
let c = Chain::init(
|
||||||
|
dir_name.to_string(),
|
||||||
|
db_env,
|
||||||
|
Arc::new(NoopAdapter {}),
|
||||||
|
genesis_block,
|
||||||
|
pow::verify_size,
|
||||||
|
).unwrap();
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let retval = WalletProxy {
|
||||||
|
chain_dir: chain_dir.to_owned(),
|
||||||
|
chain: Arc::new(c),
|
||||||
|
tx: tx,
|
||||||
|
rx: rx,
|
||||||
|
wallets: HashMap::new(),
|
||||||
|
running: Arc::new(AtomicBool::new(false)),
|
||||||
|
phantom_c: PhantomData,
|
||||||
|
phantom_k: PhantomData,
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add wallet with a given "address"
|
||||||
|
pub fn add_wallet(
|
||||||
|
&mut self,
|
||||||
|
addr: &str,
|
||||||
|
tx: Sender<WalletProxyMessage>,
|
||||||
|
wallet: Arc<Mutex<Box<WalletInst<LocalWalletClient, K>>>>,
|
||||||
|
) {
|
||||||
|
self.wallets.insert(addr.to_owned(), (tx, wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the incoming message queue and respond more or less
|
||||||
|
/// synchronously
|
||||||
|
pub fn run(&mut self) -> Result<(), libwallet::Error> {
|
||||||
|
self.running.store(true, Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_millis(10));
|
||||||
|
// read queue
|
||||||
|
let m = self.rx.recv().unwrap();
|
||||||
|
trace!(LOGGER, "Wallet Client Proxy Received: {:?}", m);
|
||||||
|
let resp = match m.method.as_ref() {
|
||||||
|
"get_chain_height" => self.get_chain_height(m)?,
|
||||||
|
"get_outputs_from_node" => self.get_outputs_from_node(m)?,
|
||||||
|
"send_tx_slate" => self.send_tx_slate(m)?,
|
||||||
|
"post_tx" => self.post_tx(m)?,
|
||||||
|
_ => panic!("Unknown Wallet Proxy Message"),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.respond(resp);
|
||||||
|
if !self.running.load(Ordering::Relaxed) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a message to a given wallet client
|
||||||
|
fn respond(&mut self, m: WalletProxyMessage) {
|
||||||
|
if let Some(s) = self.wallets.get_mut(&m.dest) {
|
||||||
|
if let Err(e) = s.0.send(m.clone()) {
|
||||||
|
panic!("Error sending response from proxy: {:?}, {}", m, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Unknown wallet recipient for response message: {:?}", m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// post transaction to the chain (and mine it, taking the reward)
|
||||||
|
fn post_tx(&mut self, m: WalletProxyMessage) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||||
|
let dest_wallet = self.wallets.get_mut(&m.sender_id).unwrap().1.clone();
|
||||||
|
let wrapper: TxWrapper = serde_json::from_str(&m.body).context(
|
||||||
|
libwallet::ErrorKind::ClientCallback("Error parsing TxWrapper"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let tx_bin = util::from_hex(wrapper.tx_hex).context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Error parsing TxWrapper: tx_bin",
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).context(
|
||||||
|
libwallet::ErrorKind::ClientCallback("Error parsing TxWrapper: tx"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
common::award_block_to_wallet(&self.chain, vec![&tx], dest_wallet)?;
|
||||||
|
|
||||||
|
Ok(WalletProxyMessage {
|
||||||
|
sender_id: "node".to_owned(),
|
||||||
|
dest: m.sender_id,
|
||||||
|
method: m.method,
|
||||||
|
body: "".to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send tx slate
|
||||||
|
fn send_tx_slate(
|
||||||
|
&mut self,
|
||||||
|
m: WalletProxyMessage,
|
||||||
|
) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||||
|
let dest_wallet = self.wallets.get_mut(&m.dest);
|
||||||
|
if let None = dest_wallet {
|
||||||
|
panic!("Unknown wallet destination for send_tx_slate: {:?}", m);
|
||||||
|
}
|
||||||
|
let w = dest_wallet.unwrap().1.clone();
|
||||||
|
let mut slate = serde_json::from_str(&m.body).unwrap();
|
||||||
|
libwallet::controller::foreign_single_use(w.clone(), |listener_api| {
|
||||||
|
listener_api.receive_tx(&mut slate)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(WalletProxyMessage {
|
||||||
|
sender_id: m.dest,
|
||||||
|
dest: m.sender_id,
|
||||||
|
method: m.method,
|
||||||
|
body: serde_json::to_string(&slate).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get chain height
|
||||||
|
fn get_chain_height(
|
||||||
|
&mut self,
|
||||||
|
m: WalletProxyMessage,
|
||||||
|
) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||||
|
Ok(WalletProxyMessage {
|
||||||
|
sender_id: "node".to_owned(),
|
||||||
|
dest: m.sender_id,
|
||||||
|
method: m.method,
|
||||||
|
body: format!("{}", self.chain.head().unwrap().height).to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get api outputs
|
||||||
|
fn get_outputs_from_node(
|
||||||
|
&mut self,
|
||||||
|
m: WalletProxyMessage,
|
||||||
|
) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||||
|
let split = m.body.split(",");
|
||||||
|
//let mut api_outputs: HashMap<pedersen::Commitment, String> = HashMap::new();
|
||||||
|
let mut outputs: Vec<api::Output> = vec![];
|
||||||
|
for o in split {
|
||||||
|
let c = util::from_hex(String::from(o)).unwrap();
|
||||||
|
let commit = Commitment::from_vec(c);
|
||||||
|
let out = common::get_output_local(&self.chain.clone(), &commit);
|
||||||
|
if let Some(o) = out {
|
||||||
|
outputs.push(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(WalletProxyMessage {
|
||||||
|
sender_id: "node".to_owned(),
|
||||||
|
dest: m.sender_id,
|
||||||
|
method: m.method,
|
||||||
|
body: serde_json::to_string(&outputs).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LocalWalletClient {
|
||||||
|
/// wallet identifier for the proxy queue
|
||||||
|
pub id: String,
|
||||||
|
/// proxy's tx queue (recieve messsages from other wallets or node
|
||||||
|
pub proxy_tx: Arc<Mutex<Sender<WalletProxyMessage>>>,
|
||||||
|
/// my rx queue
|
||||||
|
pub rx: Arc<Mutex<Receiver<WalletProxyMessage>>>,
|
||||||
|
/// my tx queue
|
||||||
|
pub tx: Arc<Mutex<Sender<WalletProxyMessage>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalWalletClient {
|
||||||
|
/// new
|
||||||
|
pub fn new(id: &str, proxy_rx: Sender<WalletProxyMessage>) -> Self {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
LocalWalletClient {
|
||||||
|
id: id.to_owned(),
|
||||||
|
proxy_tx: Arc::new(Mutex::new(proxy_rx)),
|
||||||
|
rx: Arc::new(Mutex::new(rx)),
|
||||||
|
tx: Arc::new(Mutex::new(tx)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get an instance of the send queue for other senders
|
||||||
|
pub fn get_send_instance(&self) -> Sender<WalletProxyMessage> {
|
||||||
|
self.tx.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletClient for LocalWalletClient {
|
||||||
|
fn node_url(&self) -> &str {
|
||||||
|
"node"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the wallet API to create a coinbase output for the given
|
||||||
|
/// block_fees. Will retry based on default "retry forever with backoff"
|
||||||
|
/// behavior.
|
||||||
|
fn create_coinbase(
|
||||||
|
&self,
|
||||||
|
_dest: &str,
|
||||||
|
_block_fees: &BlockFees,
|
||||||
|
) -> Result<CbData, libwallet::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send the slate to a listening wallet instance
|
||||||
|
fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result<Slate, libwallet::Error> {
|
||||||
|
let m = WalletProxyMessage {
|
||||||
|
sender_id: self.id.clone(),
|
||||||
|
dest: dest.to_owned(),
|
||||||
|
method: "send_tx_slate".to_owned(),
|
||||||
|
body: serde_json::to_string(slate).unwrap(),
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let p = self.proxy_tx.lock().unwrap();
|
||||||
|
p.send(m)
|
||||||
|
.context(libwallet::ErrorKind::ClientCallback("Send TX Slate"))?;
|
||||||
|
}
|
||||||
|
let r = self.rx.lock().unwrap();
|
||||||
|
let m = r.recv().unwrap();
|
||||||
|
trace!(LOGGER, "Received send_tx_slate response: {:?}", m.clone());
|
||||||
|
Ok(
|
||||||
|
serde_json::from_str(&m.body).context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Parsing send_tx_slate response",
|
||||||
|
))?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Posts a transaction to a grin node
|
||||||
|
/// In this case it will create a new block with award rewarded to
|
||||||
|
fn post_tx(&self, tx: &TxWrapper, _fluff: bool) -> Result<(), libwallet::Error> {
|
||||||
|
let m = WalletProxyMessage {
|
||||||
|
sender_id: self.id.clone(),
|
||||||
|
dest: self.node_url().to_owned(),
|
||||||
|
method: "post_tx".to_owned(),
|
||||||
|
body: serde_json::to_string(tx).unwrap(),
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let p = self.proxy_tx.lock().unwrap();
|
||||||
|
p.send(m)
|
||||||
|
.context(libwallet::ErrorKind::ClientCallback("post_tx send"))?;
|
||||||
|
}
|
||||||
|
let r = self.rx.lock().unwrap();
|
||||||
|
let m = r.recv().unwrap();
|
||||||
|
trace!(LOGGER, "Received post_tx response: {:?}", m.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the chain tip from a given node
|
||||||
|
fn get_chain_height(&self) -> Result<u64, libwallet::Error> {
|
||||||
|
let m = WalletProxyMessage {
|
||||||
|
sender_id: self.id.clone(),
|
||||||
|
dest: self.node_url().to_owned(),
|
||||||
|
method: "get_chain_height".to_owned(),
|
||||||
|
body: "".to_owned(),
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let p = self.proxy_tx.lock().unwrap();
|
||||||
|
p.send(m).context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Get chain height send",
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
let r = self.rx.lock().unwrap();
|
||||||
|
let m = r.recv().unwrap();
|
||||||
|
trace!(
|
||||||
|
LOGGER,
|
||||||
|
"Received get_chain_height response: {:?}",
|
||||||
|
m.clone()
|
||||||
|
);
|
||||||
|
Ok(m.body
|
||||||
|
.parse::<u64>()
|
||||||
|
.context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Parsing get_height response",
|
||||||
|
))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve outputs from node
|
||||||
|
fn get_outputs_from_node(
|
||||||
|
&self,
|
||||||
|
wallet_outputs: Vec<pedersen::Commitment>,
|
||||||
|
) -> Result<HashMap<pedersen::Commitment, String>, libwallet::Error> {
|
||||||
|
let query_params: Vec<String> = wallet_outputs
|
||||||
|
.iter()
|
||||||
|
.map(|commit| format!("{}", util::to_hex(commit.as_ref().to_vec())))
|
||||||
|
.collect();
|
||||||
|
let query_str = query_params.join(",");
|
||||||
|
let m = WalletProxyMessage {
|
||||||
|
sender_id: self.id.clone(),
|
||||||
|
dest: self.node_url().to_owned(),
|
||||||
|
method: "get_outputs_from_node".to_owned(),
|
||||||
|
body: query_str,
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let p = self.proxy_tx.lock().unwrap();
|
||||||
|
p.send(m).context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Get outputs from node send",
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
let r = self.rx.lock().unwrap();
|
||||||
|
let m = r.recv().unwrap();
|
||||||
|
let outputs: Vec<api::Output> = serde_json::from_str(&m.body).unwrap();
|
||||||
|
let mut api_outputs: HashMap<pedersen::Commitment, String> = HashMap::new();
|
||||||
|
for out in outputs {
|
||||||
|
api_outputs.insert(out.commit.commit(), util::to_hex(out.commit.to_vec()));
|
||||||
|
}
|
||||||
|
Ok(api_outputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_outputs_by_pmmr_index(
|
||||||
|
&self,
|
||||||
|
_start_height: u64,
|
||||||
|
_max_outputs: u64,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
u64,
|
||||||
|
u64,
|
||||||
|
Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>,
|
||||||
|
),
|
||||||
|
libwallet::Error,
|
||||||
|
> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,186 +26,192 @@ extern crate time;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
use common::testclient::{LocalWalletClient, WalletProxy};
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Arc;
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use chain::Chain;
|
use core::global;
|
||||||
use chain::types::NoopAdapter;
|
|
||||||
use core::global::ChainTypes;
|
use core::global::ChainTypes;
|
||||||
use core::{global, pow};
|
use keychain::ExtKeychain;
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
use wallet::HTTPWalletClient;
|
|
||||||
use wallet::libwallet::internal::selection;
|
|
||||||
|
|
||||||
fn clean_output_dir(test_dir: &str) {
|
fn clean_output_dir(test_dir: &str) {
|
||||||
let _ = fs::remove_dir_all(test_dir);
|
let _ = fs::remove_dir_all(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(test_dir: &str, chain_dir: &str) -> Chain {
|
fn setup(test_dir: &str) {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
clean_output_dir(test_dir);
|
clean_output_dir(test_dir);
|
||||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||||
let genesis_block = pow::mine_genesis_block().unwrap();
|
|
||||||
let dir_name = format!("{}/{}", test_dir, chain_dir);
|
|
||||||
let db_env = Arc::new(store::new_env(dir_name.to_string()));
|
|
||||||
chain::Chain::init(
|
|
||||||
dir_name.to_string(),
|
|
||||||
db_env,
|
|
||||||
Arc::new(NoopAdapter {}),
|
|
||||||
genesis_block,
|
|
||||||
pow::verify_size,
|
|
||||||
).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build and test new version of sending API
|
/// Exercises the Transaction API fully with a test WalletClient operating
|
||||||
|
/// directly on a chain instance
|
||||||
|
/// Callable with any type of wallet
|
||||||
|
fn basic_transaction_api(test_dir: &str, backend_type: common::BackendType) {
|
||||||
|
setup(test_dir);
|
||||||
|
// Create a new proxy to simulate server and wallet responses
|
||||||
|
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
|
||||||
|
let chain = wallet_proxy.chain.clone();
|
||||||
|
|
||||||
|
// Create a new wallet test client, and set its queues to communicate with the
|
||||||
|
// proxy
|
||||||
|
let client = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
||||||
|
let wallet1 = common::create_wallet(
|
||||||
|
&format!("{}/wallet1", test_dir),
|
||||||
|
client.clone(),
|
||||||
|
backend_type.clone(),
|
||||||
|
);
|
||||||
|
wallet_proxy.add_wallet("wallet1", client.get_send_instance(), wallet1.clone());
|
||||||
|
|
||||||
|
// define recipient wallet, add to proxy
|
||||||
|
let client = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
|
||||||
|
let wallet2 = common::create_wallet(
|
||||||
|
&format!("{}/wallet2", test_dir),
|
||||||
|
client.clone(),
|
||||||
|
backend_type.clone(),
|
||||||
|
);
|
||||||
|
wallet_proxy.add_wallet("wallet2", client.get_send_instance(), wallet2.clone());
|
||||||
|
|
||||||
|
// Set the wallet proxy listener running
|
||||||
|
thread::spawn(move || {
|
||||||
|
if let Err(e) = wallet_proxy.run() {
|
||||||
|
error!(LOGGER, "Wallet Proxy error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// few values to keep things shorter
|
||||||
|
let reward = core::consensus::REWARD;
|
||||||
|
let cm = global::coinbase_maturity();
|
||||||
|
// mine a few blocks
|
||||||
|
let _ = common::award_blocks_to_wallet(&chain, wallet1.clone(), 10);
|
||||||
|
|
||||||
|
// Check wallet 1 contents are as expected
|
||||||
|
let sender_res = wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
|
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true)?;
|
||||||
|
debug!(
|
||||||
|
LOGGER,
|
||||||
|
"Wallet 1 Info Pre-Transaction, after {} blocks: {:?}",
|
||||||
|
wallet1_info.last_confirmed_height,
|
||||||
|
wallet1_info
|
||||||
|
);
|
||||||
|
assert!(wallet1_refreshed);
|
||||||
|
assert_eq!(
|
||||||
|
wallet1_info.amount_currently_spendable,
|
||||||
|
(wallet1_info.last_confirmed_height - cm) * reward
|
||||||
|
);
|
||||||
|
assert_eq!(wallet1_info.amount_immature, cm * reward);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if let Err(e) = sender_res {
|
||||||
|
println!("Error starting sender API: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert wallet contents
|
||||||
|
// and a single use api for a send command
|
||||||
|
let amount = 60_000_000_000;
|
||||||
|
let sender_res = wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
|
||||||
|
// note this will increment the block count as part of the transaction "Posting"
|
||||||
|
let issue_tx_res = sender_api.issue_send_tx(
|
||||||
|
amount, // amount
|
||||||
|
2, // minimum confirmations
|
||||||
|
"wallet2", // dest
|
||||||
|
500, // max outputs
|
||||||
|
true, // select all outputs
|
||||||
|
);
|
||||||
|
if issue_tx_res.is_err() {
|
||||||
|
panic!("Error issuing send tx: {}", issue_tx_res.err().unwrap());
|
||||||
|
}
|
||||||
|
let post_res = sender_api.post_tx(&issue_tx_res.unwrap(), false);
|
||||||
|
if post_res.is_err() {
|
||||||
|
panic!("Error posting tx: {}", post_res.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if let Err(e) = sender_res {
|
||||||
|
panic!("Error starting sender API: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check wallet 1 contents are as expected
|
||||||
|
let sender_res = wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
|
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true)?;
|
||||||
|
debug!(
|
||||||
|
LOGGER,
|
||||||
|
"Wallet 1 Info Post Transaction, after {} blocks: {:?}",
|
||||||
|
wallet1_info.last_confirmed_height,
|
||||||
|
wallet1_info
|
||||||
|
);
|
||||||
|
let fee = wallet::libtx::tx_fee(
|
||||||
|
wallet1_info.last_confirmed_height as usize - 1 - cm as usize,
|
||||||
|
2,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert!(wallet1_refreshed);
|
||||||
|
// wallet 1 recieved fees, so amount should be the same
|
||||||
|
assert_eq!(
|
||||||
|
wallet1_info.total,
|
||||||
|
amount * wallet1_info.last_confirmed_height - amount
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wallet1_info.amount_currently_spendable,
|
||||||
|
(wallet1_info.last_confirmed_height - cm) * reward - amount - fee
|
||||||
|
);
|
||||||
|
assert_eq!(wallet1_info.amount_immature, cm * reward + fee);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if let Err(e) = sender_res {
|
||||||
|
println!("Error starting sender API: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mine a few more blocks
|
||||||
|
let _ = common::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
|
||||||
|
|
||||||
|
// refresh wallets and retrieve info/tests for each wallet after maturity
|
||||||
|
let sender_res = wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
|
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true)?;
|
||||||
|
debug!(LOGGER, "Wallet 1 Info: {:?}", wallet1_info);
|
||||||
|
assert!(wallet1_refreshed);
|
||||||
|
assert_eq!(
|
||||||
|
wallet1_info.total,
|
||||||
|
amount * wallet1_info.last_confirmed_height - amount
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wallet1_info.amount_currently_spendable,
|
||||||
|
(wallet1_info.last_confirmed_height - cm - 1) * reward
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if let Err(e) = sender_res {
|
||||||
|
println!("Error starting sender API: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sender_res = wallet::controller::owner_single_use(wallet2.clone(), |api| {
|
||||||
|
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true)?;
|
||||||
|
assert!(wallet2_refreshed);
|
||||||
|
assert_eq!(wallet2_info.amount_currently_spendable, amount);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if let Err(e) = sender_res {
|
||||||
|
println!("Error starting sender API: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let logging finish
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_transaction() {
|
fn file_wallet_basic_transaction_api() {
|
||||||
let client = HTTPWalletClient::new("");
|
let test_dir = "test_output/basic_transaction_api_file";
|
||||||
let chain = setup("test_output", "build_transaction_2/.grin");
|
basic_transaction_api(test_dir, common::BackendType::FileBackend);
|
||||||
let mut wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1", client.clone());
|
}
|
||||||
let mut wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2", client);
|
|
||||||
common::award_blocks_to_wallet(&chain, &mut wallet1, 10);
|
// not yet ready
|
||||||
// Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends
|
#[ignore]
|
||||||
// 300 Grins from wallet 1 to wallet 2, using libtx
|
#[test]
|
||||||
|
fn db_wallet_basic_transaction_api() {
|
||||||
// Get lock height
|
let test_dir = "test_output/basic_transaction_api_db";
|
||||||
let chain_tip = chain.head().unwrap();
|
basic_transaction_api(test_dir, common::BackendType::LMDBBackend);
|
||||||
let amount = 300_000_000_000;
|
|
||||||
let min_confirmations = 3;
|
|
||||||
|
|
||||||
// ensure outputs we're selecting are up to date
|
|
||||||
let res = common::refresh_output_state_local(&mut wallet1, &chain);
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
panic!("Unable to refresh sender wallet outputs: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TRANSACTION WORKFLOW STARTS HERE
|
|
||||||
// Sender selects outputs into a new slate and save our corresponding IDs in
|
|
||||||
// a transaction context. The secret key in our transaction context will be
|
|
||||||
// randomly selected. This returns the public slate, and a closure that locks
|
|
||||||
// our inputs and outputs once we're convinced the transaction exchange went
|
|
||||||
// according to plan
|
|
||||||
// This function is just a big helper to do all of that, in theory
|
|
||||||
// this process can be split up in any way
|
|
||||||
let (mut slate, mut sender_context, sender_lock_fn) = selection::build_send_tx_slate(
|
|
||||||
&mut wallet1,
|
|
||||||
2,
|
|
||||||
amount,
|
|
||||||
chain_tip.height,
|
|
||||||
min_confirmations,
|
|
||||||
chain_tip.height,
|
|
||||||
1000,
|
|
||||||
true,
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
// Generate a kernel offset and subtract from our context's secret key. Store
|
|
||||||
// the offset in the slate's transaction kernel, and adds our public key
|
|
||||||
// information to the slate
|
|
||||||
let _ = slate
|
|
||||||
.fill_round_1(
|
|
||||||
wallet1.keychain.as_ref().unwrap(),
|
|
||||||
&mut sender_context.sec_key,
|
|
||||||
&sender_context.sec_nonce,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
debug!(LOGGER, "Transaction Slate after step 1: sender initiation");
|
|
||||||
debug!(LOGGER, "-----------------------------------------");
|
|
||||||
debug!(LOGGER, "{:?}", slate);
|
|
||||||
|
|
||||||
// Now, just like the sender did, recipient is going to select a target output,
|
|
||||||
// add it to the transaction, and keep track of the corresponding wallet
|
|
||||||
// Identifier Again, this is a helper to do that, which returns a closure that
|
|
||||||
// creates the output when we're satisfied the process was successful
|
|
||||||
let (_, mut recp_context, receiver_create_fn) =
|
|
||||||
selection::build_recipient_output_with_slate(&mut wallet2, &mut slate).unwrap();
|
|
||||||
|
|
||||||
let _ = slate
|
|
||||||
.fill_round_1(
|
|
||||||
wallet2.keychain.as_ref().unwrap(),
|
|
||||||
&mut recp_context.sec_key,
|
|
||||||
&recp_context.sec_nonce,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// recipient can proceed to round 2 now
|
|
||||||
let _ = receiver_create_fn(&mut wallet2);
|
|
||||||
|
|
||||||
let _ = slate
|
|
||||||
.fill_round_2(
|
|
||||||
wallet1.keychain.as_ref().unwrap(),
|
|
||||||
&recp_context.sec_key,
|
|
||||||
&recp_context.sec_nonce,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
LOGGER,
|
|
||||||
"Transaction Slate after step 2: receiver initiation"
|
|
||||||
);
|
|
||||||
debug!(LOGGER, "-----------------------------------------");
|
|
||||||
debug!(LOGGER, "{:?}", slate);
|
|
||||||
|
|
||||||
// SENDER Part 3: Sender confirmation
|
|
||||||
let _ = slate
|
|
||||||
.fill_round_2(
|
|
||||||
wallet1.keychain.as_ref().unwrap(),
|
|
||||||
&sender_context.sec_key,
|
|
||||||
&sender_context.sec_nonce,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
debug!(LOGGER, "PartialTx after step 3: sender confirmation");
|
|
||||||
debug!(LOGGER, "--------------------------------------------");
|
|
||||||
debug!(LOGGER, "{:?}", slate);
|
|
||||||
|
|
||||||
// Final transaction can be built by anyone at this stage
|
|
||||||
let res = slate.finalize(wallet1.keychain.as_ref().unwrap());
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
panic!("Error creating final tx: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(LOGGER, "Final transaction is:");
|
|
||||||
debug!(LOGGER, "--------------------------------------------");
|
|
||||||
debug!(LOGGER, "{:?}", slate.tx);
|
|
||||||
|
|
||||||
// All okay, lock sender's outputs
|
|
||||||
let _ = sender_lock_fn(&mut wallet1);
|
|
||||||
|
|
||||||
// Insert this transaction into a new block, then mine till confirmation
|
|
||||||
common::award_block_to_wallet(&chain, vec![&slate.tx], &mut wallet1);
|
|
||||||
common::award_blocks_to_wallet(&chain, &mut wallet1, 5);
|
|
||||||
|
|
||||||
// Refresh wallets
|
|
||||||
let res = common::refresh_output_state_local(&mut wallet2, &chain);
|
|
||||||
if let Err(e) = res {
|
|
||||||
panic!("Error refreshing output state for wallet: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check recipient wallet
|
|
||||||
let chain_tip = chain.head().unwrap();
|
|
||||||
let balances = common::get_wallet_balances(&mut wallet2, chain_tip.height).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(balances.3, 300_000_000_000);
|
|
||||||
|
|
||||||
// check sender wallet
|
|
||||||
let res = common::refresh_output_state_local(&mut wallet1, &chain);
|
|
||||||
if let Err(e) = res {
|
|
||||||
panic!("Error refreshing output state for wallet: {:?}", e);
|
|
||||||
}
|
|
||||||
let balances = common::get_wallet_balances(&mut wallet1, chain_tip.height).unwrap();
|
|
||||||
println!("tip height: {:?}", chain_tip.height);
|
|
||||||
println!("Sender balances: {:?}", balances);
|
|
||||||
// num blocks * grins per block, and wallet1 mined the fee
|
|
||||||
assert_eq!(
|
|
||||||
balances.3,
|
|
||||||
(chain_tip.height - min_confirmations) * 60_000_000_000 - amount
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue