From 5ba163fa66212f3ccdd24225f3bf4af2255083c8 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Mon, 19 Nov 2018 19:47:40 +0000 Subject: [PATCH] Provide wallet 'plugin' architecture (#1983) * remove receive_coinbase fn from wallet to wallet client * start moving clients into separate mod * rustfmt * move wallet client + start listener into wallet 'clients' * rustfmt * refactor API to make it more modular and completely decouple sending * rustfmt * further decouple API from sending methods * rustfmt * remove wallet to wallet client trait * rustfmt * rename / refactor client + adapters * rustfmt * add adapter concept * add midding node_clients dir * add file and null adapters * rustfmt * remove receive from owner api * factor out receiving slates into trait * rustfmt * adding listen trait * rustfmt * change listener to use trait * rustfmt * add test for file-based exchange * replace http api send command * rustfmt * move controller out of libwallet and into top-level wallet dir * rustfmt * add moved controller --- .gitignore | 1 + api/src/rest.rs | 6 +- doc/wallet/design/design.md | 12 +- doc/wallet/design/wallet-arch.puml | 2 +- servers/tests/framework/mod.rs | 61 ++-- servers/tests/simulnet.rs | 73 ++--- src/bin/cmd/wallet.rs | 302 +++++++----------- wallet/src/adapters/file.rs | 69 ++++ wallet/src/adapters/http.rs | 86 +++++ wallet/src/adapters/mod.rs | 55 ++++ wallet/src/adapters/null.rs | 59 ++++ wallet/src/{libwallet => }/controller.rs | 178 ++++++----- wallet/src/lib.rs | 42 ++- wallet/src/libwallet/api.rs | 237 ++++---------- wallet/src/libwallet/internal/keys.rs | 37 +-- wallet/src/libwallet/internal/restore.rs | 14 +- wallet/src/libwallet/internal/selection.rs | 35 +- wallet/src/libwallet/internal/tx.rs | 46 ++- wallet/src/libwallet/internal/updater.rs | 81 ++--- wallet/src/libwallet/mod.rs | 1 - wallet/src/libwallet/types.rs | 32 +- wallet/src/lmdb_wallet.rs | 40 +-- .../src/{client.rs => node_clients/http.rs} | 69 +--- wallet/src/node_clients/mod.rs | 17 + wallet/tests/accounts.rs | 39 +-- wallet/tests/common/mod.rs | 33 +- wallet/tests/common/testclient.rs | 92 ++++-- wallet/tests/file.rs | 181 +++++++++++ wallet/tests/restore.rs | 82 +++-- wallet/tests/self_send.rs | 52 +-- wallet/tests/transaction.rs | 87 +++-- 31 files changed, 1145 insertions(+), 976 deletions(-) create mode 100644 wallet/src/adapters/file.rs create mode 100644 wallet/src/adapters/http.rs create mode 100644 wallet/src/adapters/mod.rs create mode 100644 wallet/src/adapters/null.rs rename wallet/src/{libwallet => }/controller.rs (82%) rename wallet/src/{client.rs => node_clients/http.rs} (74%) create mode 100644 wallet/src/node_clients/mod.rs create mode 100644 wallet/tests/file.rs diff --git a/.gitignore b/.gitignore index 115aeaeb9..01259d892 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .DS_Store .grin* node* +!node_clients target Cargo.lock *.iml diff --git a/api/src/rest.rs b/api/src/rest.rs index 0de00c7c3..b53a0581e 100644 --- a/api/src/rest.rs +++ b/api/src/rest.rs @@ -92,8 +92,8 @@ impl From> for Error { /// TLS config pub struct TLSConfig { - certificate: String, - private_key: String, + pub certificate: String, + pub private_key: String, } impl TLSConfig { @@ -190,7 +190,7 @@ impl ApiServer { .spawn(move || { let server = Server::bind(&addr) .serve(router) - // TODO graceful shutdown is unstable, investigate + // TODO graceful shutdown is unstable, investigate //.with_graceful_shutdown(rx) .map_err(|e| eprintln!("HTTP API server error: {}", e)); diff --git a/doc/wallet/design/design.md b/doc/wallet/design/design.md index 6f8e2cd48..f33cc6779 100644 --- a/doc/wallet/design/design.md +++ b/doc/wallet/design/design.md @@ -24,7 +24,7 @@ the code is organized into the following components (from highest-level to lowes * **libTx** - Library that provides lower-level transaction building, rangeproof and signing functions, highly-reusable by wallet implementors. * **Wallet Traits** - A set of generic traits defined within libWallet and the `keychain` crate . A wallet implementation such as Grin's current default only needs to implement these traits in order to provide a wallet: - * **WalletToNodeClient** - Defines communication between the wallet, a running grin node and/or other wallets + * **NodeClient** - Defines communication between the wallet, a running grin node and/or other wallets * **WalletBackend** - Defines the storage implementation of the wallet * **KeyChain** - Defines key derivation operations @@ -66,22 +66,22 @@ pub fn retrieve_outputs( ) -> Result, Error> where !·T: WalletBackend, -!·C: WalletToNodeClient, +!·C: NodeClient, !·K: Keychain, { ``` With `T` in this instance being a class that implements the `WalletBackend` trait, which is further parameterized with implementations of -`WalletToNodeClient` and `Keychain`. +`NodeClient` and `Keychain`. There is currently only a single implementation of the Keychain trait within the Grin code, in the `keychain` crate exported as `ExtKeyChain`. The `Keychain` trait makes several assumptions about the underlying implementation, particularly that it will adhere to a [BIP-38 style](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki) 'master key -> child key' model. -There are two implementations of `WalletToNodeClient` within the code, the main version being the `HTTPWalletToNodeClient` found within `wallet/src/client.rs` and -the seconds a test client that communicates with an in-process instance of a chain. The WalletToNodeClient isolates all network calls, so upgrading wallet +There are two implementations of `NodeClient` within the code, the main version being the `HTTPNodeClient` found within `wallet/src/client.rs` and +the seconds a test client that communicates with an in-process instance of a chain. The NodeClient isolates all network calls, so upgrading wallet communication from the current simple http interaction to a more secure protocol (or allowing for many options) should be a simple -matter of dropping in different `WalletToNodeClient` implementations. +matter of dropping in different `NodeClient` implementations. There are also two implementations of `WalletBackend` within the code at the base of the `wallet` crate. `LMDBBackend` found within `wallet/src/lmdb_wallet.rs` is the main implementation, and is now used by all grin wallet commands. The earlier `FileWallet` still exists diff --git a/doc/wallet/design/wallet-arch.puml b/doc/wallet/design/wallet-arch.puml index 3bcea29db..893421985 100644 --- a/doc/wallet/design/wallet-arch.puml +++ b/doc/wallet/design/wallet-arch.puml @@ -40,7 +40,7 @@ folder "Provided by Grin" as services { package "Traits Implemented by Wallets" as traits { database "WalletBackend" as wallet_backend database "KeyChain" as keychain - component "WalletToNodeClient" as wallet_client + component "NodeClient" as wallet_client } note left of wallet_client diff --git a/servers/tests/framework/mod.rs b/servers/tests/framework/mod.rs index 687fb2612..f38fa3639 100644 --- a/servers/tests/framework/mod.rs +++ b/servers/tests/framework/mod.rs @@ -30,7 +30,7 @@ use std::{fs, thread, time}; use util::Mutex; use framework::keychain::Keychain; -use wallet::{HTTPWalletToNodeClient, HTTPWalletToWalletClient, LMDBBackend, WalletConfig}; +use wallet::{HTTPNodeClient, HTTPWalletCommAdapter, LMDBBackend, WalletCommAdapter, WalletConfig}; /// Just removes all results from previous runs pub fn clean_all_output(test_name_dir: &str) { @@ -265,27 +265,19 @@ impl LocalServerContainer { let _ = fs::create_dir_all(self.wallet_config.clone().data_file_dir); let r = wallet::WalletSeed::init_file(&self.wallet_config); - let client_n = - HTTPWalletToNodeClient::new(&self.wallet_config.check_node_api_http_addr, None); - let client_w = HTTPWalletToWalletClient::new(); + let client_n = HTTPNodeClient::new(&self.wallet_config.check_node_api_http_addr, None); if let Err(_e) = r { //panic!("Error initializing wallet seed: {}", e); } - let wallet: LMDBBackend< - HTTPWalletToNodeClient, - HTTPWalletToWalletClient, - keychain::ExtKeychain, - > = - LMDBBackend::new(self.wallet_config.clone(), "", client_n, client_w).unwrap_or_else( - |e| { - panic!( - "Error creating wallet: {:?} Config: {:?}", - e, self.wallet_config - ) - }, - ); + let wallet: LMDBBackend = + LMDBBackend::new(self.wallet_config.clone(), "", client_n).unwrap_or_else(|e| { + panic!( + "Error creating wallet: {:?} Config: {:?}", + e, self.wallet_config + ) + }); wallet::controller::foreign_listener( Arc::new(Mutex::new(wallet)), @@ -316,9 +308,8 @@ impl LocalServerContainer { let keychain: keychain::ExtKeychain = wallet_seed .derive_keychain("") .expect("Failed to derive keychain from seed file and passphrase."); - let client_n = HTTPWalletToNodeClient::new(&config.check_node_api_http_addr, None); - let client_w = HTTPWalletToWalletClient::new(); - let mut wallet = LMDBBackend::new(config.clone(), "", client_n, client_w) + let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None); + let mut wallet = LMDBBackend::new(config.clone(), "", client_n) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); wallet.keychain = Some(keychain); let parent_id = keychain::ExtKeychain::derive_key_id(2, 0, 0, 0, 0); @@ -344,35 +335,33 @@ impl LocalServerContainer { .derive_keychain("") .expect("Failed to derive keychain from seed file and passphrase."); - let client_n = HTTPWalletToNodeClient::new(&config.check_node_api_http_addr, None); - let client_w = HTTPWalletToWalletClient::new(); + let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None); + let client_w = HTTPWalletCommAdapter::new(); let max_outputs = 500; let change_outputs = 1; - let mut wallet = LMDBBackend::new(config.clone(), "", client_n, client_w) + let mut wallet = LMDBBackend::new(config.clone(), "", client_n) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); wallet.keychain = Some(keychain); let _ = wallet::controller::owner_single_use(Arc::new(Mutex::new(wallet)), |api| { - let result = api.issue_send_tx( + let (mut slate, lock_fn) = api.initiate_tx( + None, amount, minimum_confirmations, - dest, max_outputs, change_outputs, selection_strategy == "all", + )?; + slate = client_w.send_tx_sync(dest, &slate)?; + api.finalize_tx(&mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; + println!( + "Tx sent: {} grin to {} (strategy '{}')", + core::core::amount_to_hr_string(amount, false), + dest, + selection_strategy, ); - match result { - Ok(_) => println!( - "Tx sent: {} grin to {} (strategy '{}')", - core::core::amount_to_hr_string(amount, false), - dest, - selection_strategy, - ), - Err(e) => { - println!("Tx not sent to {}: {:?}", dest, e); - } - }; Ok(()) }).unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); } diff --git a/servers/tests/simulnet.rs b/servers/tests/simulnet.rs index a87d407c0..6406d5ebe 100644 --- a/servers/tests/simulnet.rs +++ b/servers/tests/simulnet.rs @@ -37,11 +37,10 @@ use core::core::hash::Hashed; use core::global::{self, ChainTypes}; use wallet::controller; -use wallet::libtx::slate::Slate; use wallet::libwallet::types::{WalletBackend, WalletInst}; use wallet::lmdb_wallet::LMDBBackend; use wallet::WalletConfig; -use wallet::{HTTPWalletToNodeClient, HTTPWalletToWalletClient}; +use wallet::{HTTPNodeClient, HTTPWalletCommAdapter, WalletCommAdapter}; use framework::{ config, stop_all_servers, LocalServerContainerConfig, LocalServerContainerPool, @@ -880,19 +879,15 @@ fn long_fork_test_case_6(s: &Vec) { pub fn create_wallet( dir: &str, - client_n: HTTPWalletToNodeClient, - client_w: HTTPWalletToWalletClient, -) -> Arc>> -{ + client_n: HTTPNodeClient, +) -> Arc>> { let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = String::from(dir); let _ = wallet::WalletSeed::init_file(&wallet_config); - let mut wallet: LMDBBackend< - HTTPWalletToNodeClient, - HTTPWalletToWalletClient, - keychain::ExtKeychain, - > = LMDBBackend::new(wallet_config.clone(), "", client_n, client_w) - .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)); + let mut wallet: LMDBBackend = + LMDBBackend::new(wallet_config.clone(), "", client_n).unwrap_or_else(|e| { + panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config) + }); wallet.open_with_credentials().unwrap_or_else(|e| { panic!( "Error initializing wallet: {:?} Config: {:?}", @@ -912,26 +907,17 @@ fn replicate_tx_fluff_failure() { // Create Wallet 1 (Mining Input) and start it listening // Wallet 1 post to another node, just for fun - let client1 = HTTPWalletToNodeClient::new("http://127.0.0.1:23003", None); - let client1_w = HTTPWalletToWalletClient::new(); - let wallet1 = create_wallet( - "target/tmp/tx_fluff/wallet1", - client1.clone(), - client1_w.clone(), - ); + let client1 = HTTPNodeClient::new("http://127.0.0.1:23003", None); + let client1_w = HTTPWalletCommAdapter::new(); + let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); let wallet1_handle = thread::spawn(move || { controller::foreign_listener(wallet1, "127.0.0.1:33000", None) .unwrap_or_else(|e| panic!("Error creating wallet1 listener: {:?}", e,)); }); // Create Wallet 2 (Recipient) and launch - let client2 = HTTPWalletToNodeClient::new("http://127.0.0.1:23001", None); - let client2_w = HTTPWalletToWalletClient::new(); - let wallet2 = create_wallet( - "target/tmp/tx_fluff/wallet2", - client2.clone(), - client2_w.clone(), - ); + let client2 = HTTPNodeClient::new("http://127.0.0.1:23001", None); + let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); let wallet2_handle = thread::spawn(move || { controller::foreign_listener(wallet2, "127.0.0.1:33001", None) .unwrap_or_else(|e| panic!("Error creating wallet2 listener: {:?}", e,)); @@ -971,26 +957,23 @@ fn replicate_tx_fluff_failure() { thread::sleep(time::Duration::from_secs(10)); // get another instance of wallet1 (to update contents and perform a send) - let wallet1 = create_wallet( - "target/tmp/tx_fluff/wallet1", - client1.clone(), - client1_w.clone(), - ); + let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); let amount = 30_000_000_000; - let mut slate = Slate::blank(1); + let dest = "http://127.0.0.1:33001"; wallet::controller::owner_single_use(wallet1, |api| { - slate = api - .issue_send_tx( - amount, // amount - 2, // minimum confirmations - "http://127.0.0.1:33001", // dest - 500, // max outputs - 1000, // num change outputs - true, // select all outputs - ).unwrap(); - api.post_tx(&slate, false).unwrap(); + let (mut slate, lock_fn) = api.initiate_tx( + None, amount, // amount + 2, // minimum confirmations + 500, // max outputs + 1000, // num change outputs + true, // select all outputs + )?; + slate = client1_w.send_tx_sync(dest, &slate)?; + api.finalize_tx(&mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; + api.post_tx(&slate, false)?; Ok(()) }).unwrap(); @@ -998,11 +981,7 @@ fn replicate_tx_fluff_failure() { thread::sleep(time::Duration::from_secs(200)); // get another instance of wallet (to check contents) - let wallet2 = create_wallet( - "target/tmp/tx_fluff/wallet2", - client2.clone(), - client2_w.clone(), - ); + let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); wallet::controller::owner_single_use(wallet2, |api| { let res = api.retrieve_summary_info(true).unwrap(); diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs index 7b5909a67..0d3fc1849 100644 --- a/src/bin/cmd/wallet.rs +++ b/src/bin/cmd/wallet.rs @@ -13,15 +13,11 @@ // limitations under the License. use clap::ArgMatches; -use serde_json as json; -use std::fs::File; -use std::io::Read; +use std::collections::HashMap; use std::path::{Path, PathBuf}; /// Wallet commands processing -use std::sync::Arc; use std::thread; use std::time::Duration; -use util::Mutex; use api::TLSConfig; use config::GlobalWalletConfig; @@ -29,8 +25,8 @@ use core::{core, global}; use grin_wallet::libwallet::ErrorKind; use grin_wallet::{self, controller, display, libwallet}; use grin_wallet::{ - HTTPWalletToNodeClient, HTTPWalletToWalletClient, LMDBBackend, WalletBackend, WalletConfig, - WalletInst, WalletSeed, + instantiate_wallet, FileWalletCommAdapter, HTTPNodeClient, HTTPWalletCommAdapter, LMDBBackend, + NullWalletCommAdapter, WalletConfig, WalletSeed, }; use keychain; use servers::start_webwallet_server; @@ -52,31 +48,6 @@ pub fn seed_exists(wallet_config: WalletConfig) -> bool { false } } -pub fn instantiate_wallet( - wallet_config: WalletConfig, - passphrase: &str, - account: &str, - node_api_secret: Option, -) -> Arc>> -{ - let client_n = - HTTPWalletToNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret); - let client_w = HTTPWalletToWalletClient::new(); - let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n, client_w) - .unwrap_or_else(|e| { - panic!( - "Error creating DB wallet: {} Config: {:?}", - e, wallet_config - ); - }); - db_wallet - .set_parent_key_id_by_name(account) - .unwrap_or_else(|e| { - panic!("Error starting wallet: {}", e); - }); - info!("Using LMDB Backend for wallet"); - Arc::new(Mutex::new(db_wallet)) -} pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i32 { // just get defaults from the global config @@ -110,18 +81,14 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file."); info!("Wallet seed file created"); let client_n = - HTTPWalletToNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret); - let client_w = HTTPWalletToWalletClient::new(); - let _: LMDBBackend< - HTTPWalletToNodeClient, - HTTPWalletToWalletClient, - keychain::ExtKeychain, - > = LMDBBackend::new(wallet_config.clone(), "", client_n, client_w).unwrap_or_else(|e| { - panic!( - "Error creating DB for wallet: {} Config: {:?}", - e, wallet_config - ); - }); + HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret); + let _: LMDBBackend = + LMDBBackend::new(wallet_config.clone(), "", client_n).unwrap_or_else(|e| { + panic!( + "Error creating DB for wallet: {} Config: {:?}", + e, wallet_config + ); + }); info!("Wallet database backend created"); // give logging thread a moment to catch up thread::sleep(Duration::from_millis(200)); @@ -145,13 +112,6 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i Some(p) => p, }; - let wallet = instantiate_wallet( - wallet_config.clone(), - passphrase, - account, - node_api_secret.clone(), - ); - // Handle listener startup commands { let api_secret = get_first_line(wallet_config.api_secret_path.clone()); @@ -173,18 +133,37 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i if let Some(port) = listen_args.value_of("port") { wallet_config.api_listen_port = port.parse().unwrap(); } - controller::foreign_listener( - wallet.clone(), - &wallet_config.api_listen_addr(), - tls_conf, - ).unwrap_or_else(|e| { - panic!( - "Error creating wallet listener: {:?} Config: {:?}", - e, wallet_config - ); - }); + let mut params = HashMap::new(); + params.insert( + "api_listen_addr".to_owned(), + wallet_config.api_listen_addr(), + ); + if let Some(t) = tls_conf { + params.insert("certificate".to_owned(), t.certificate); + params.insert("private_key".to_owned(), t.private_key); + } + let adapter = HTTPWalletCommAdapter::new(); + adapter + .listen( + params, + wallet_config.clone(), + passphrase, + account, + node_api_secret.clone(), + ).unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, wallet_config + ); + }); } ("owner_api", Some(_api_args)) => { + let wallet = instantiate_wallet( + wallet_config.clone(), + passphrase, + account, + node_api_secret.clone(), + ); // TLS is disabled because we bind to localhost controller::owner_listener(wallet.clone(), "127.0.0.1:13420", api_secret, None) .unwrap_or_else(|e| { @@ -195,6 +174,12 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i }); } ("web", Some(_api_args)) => { + let wallet = instantiate_wallet( + wallet_config.clone(), + passphrase, + account, + node_api_secret.clone(), + ); // start owner listener and run static file server start_webwallet_server(); controller::owner_listener(wallet.clone(), "127.0.0.1:13420", api_secret, tls_conf) @@ -209,6 +194,13 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i }; } + let wallet = instantiate_wallet( + wallet_config.clone(), + passphrase, + account, + node_api_secret.clone(), + ); + let res = controller::owner_single_use(wallet.clone(), |api| { match wallet_args.subcommand() { ("account", Some(acct_args)) => { @@ -299,122 +291,79 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i })?; let fluff = send_args.is_present("fluff"); let max_outputs = 500; - if method == "http" { - if dest.starts_with("http://") || dest.starts_with("https://") { - let result = api.issue_send_tx( - amount, - minimum_confirmations, + if method == "http" && !dest.starts_with("http://") && !dest.starts_with("https://") + { + return Err(ErrorKind::GenericError(format!( + "HTTP Destination should start with http://: or https://: {}", + dest + )).into()); + } + let result = api.initiate_tx( + None, + amount, + minimum_confirmations, + max_outputs, + change_outputs, + selection_strategy == "all", + ); + let (mut slate, lock_fn) = match result { + Ok(s) => { + info!( + "Tx created: {} grin to {} (strategy '{}')", + core::amount_to_hr_string(amount, false), dest, - max_outputs, - change_outputs, - selection_strategy == "all", + selection_strategy, ); - let slate = match result { - Ok(s) => { - info!( - "Tx created: {} grin to {} (strategy '{}')", - core::amount_to_hr_string(amount, false), - dest, - selection_strategy, - ); - s - } - Err(e) => { - error!("Tx not created: {}", e); - match e.kind() { - // user errors, don't backtrace - libwallet::ErrorKind::NotEnoughFunds { .. } => {} - libwallet::ErrorKind::FeeDispute { .. } => {} - libwallet::ErrorKind::FeeExceedsAmount { .. } => {} - _ => { - // otherwise give full dump - error!("Backtrace: {}", e.backtrace().unwrap()); - } - }; - return Err(e); + s + } + Err(e) => { + error!("Tx not created: {}", e); + match e.kind() { + // user errors, don't backtrace + libwallet::ErrorKind::NotEnoughFunds { .. } => {} + libwallet::ErrorKind::FeeDispute { .. } => {} + libwallet::ErrorKind::FeeExceedsAmount { .. } => {} + _ => { + // otherwise give full dump + error!("Backtrace: {}", e.backtrace().unwrap()); } }; - let result = api.post_tx(&slate, fluff); - match result { - Ok(_) => { - info!("Tx sent",); - Ok(()) - } - Err(e) => { - error!("Tx not sent: {}", e); - Err(e) - } - } - } else { - return Err(ErrorKind::GenericError(format!( - "HTTP Destination should start with http://: or https://: {}", - dest - )).into()); + return Err(e); } - } else if method == "self" { - let result = api.issue_self_tx( - amount, - minimum_confirmations, - max_outputs, - change_outputs, - selection_strategy == "all", - account, - dest, - ); - let slate = match result { - Ok(s) => { - info!( - "Tx created: {} grin to self, source acct: {} dest_acct: {} (strategy '{}')", - core::amount_to_hr_string(amount, false), - account, - dest, - selection_strategy, - ); - s - } - Err(e) => { - error!("Tx not created: {}", e); - match e.kind() { - // user errors, don't backtrace - libwallet::ErrorKind::NotEnoughFunds { .. } => {} - libwallet::ErrorKind::FeeDispute { .. } => {} - libwallet::ErrorKind::FeeExceedsAmount { .. } => {} - _ => { - // otherwise give full dump - error!("Backtrace: {}", e.backtrace().unwrap()); - } - }; - return Err(e); - } - }; + }; + let adapter = match method { + "http" => HTTPWalletCommAdapter::new(), + "file" => FileWalletCommAdapter::new(), + "self" => NullWalletCommAdapter::new(), + _ => NullWalletCommAdapter::new(), + }; + if adapter.supports_sync() { + slate = adapter.send_tx_sync(dest, &slate)?; + if method == "self" { + controller::foreign_single_use(wallet, |api| { + api.receive_tx(&mut slate, Some(dest))?; + Ok(()) + })?; + } + api.finalize_tx(&mut slate)?; + } else { + adapter.send_tx_async(dest, &slate)?; + } + api.tx_lock_outputs(&slate, lock_fn)?; + if adapter.supports_sync() { let result = api.post_tx(&slate, fluff); match result { Ok(_) => { info!("Tx sent",); - Ok(()) + return Ok(()); } Err(e) => { error!("Tx not sent: {}", e); - Err(e) + return Err(e); } } - } else if method == "file" { - api.send_tx( - true, - amount, - minimum_confirmations, - dest, - max_outputs, - change_outputs, - selection_strategy == "all", - ).map_err(|e| ErrorKind::GenericError(format!("Send failed. e={:?}", e)))?; - Ok(()) - } else { - return Err(ErrorKind::GenericError(format!( - "unsupported payment method: {}", - method - )).into()); } + Ok(()) } ("receive", Some(send_args)) => { let mut receive_result: Result<(), grin_wallet::libwallet::Error> = Ok(()); @@ -426,18 +375,18 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i ErrorKind::GenericError(format!("File {} not found.", tx_file)).into(), ); } - let res = controller::foreign_single_use(wallet, |api| { - receive_result = api.file_receive_tx(tx_file); + let adapter = FileWalletCommAdapter::new(); + let mut slate = adapter.receive_tx_async(tx_file)?; + controller::foreign_single_use(wallet, |api| { + api.receive_tx(&mut slate, Some(account))?; Ok(()) - }); - if res.is_err() { - return res; - } else { - info!( - "Response file {}.response generated, sending it back to the transaction originator.", - tx_file, - ); - } + })?; + let send_tx = format!("{}.response", tx_file); + adapter.send_tx_async(&send_tx, &slate)?; + info!( + "Response file {}.response generated, sending it back to the transaction originator.", + tx_file, + ); receive_result } ("finalize", Some(send_args)) => { @@ -450,11 +399,8 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i ErrorKind::GenericError(format!("File {} not found.", tx_file)).into(), ); } - let mut pub_tx_f = File::open(tx_file)?; - let mut content = String::new(); - pub_tx_f.read_to_string(&mut content)?; - let mut slate: grin_wallet::libtx::slate::Slate = json::from_str(&content) - .map_err(|_| grin_wallet::libwallet::ErrorKind::Format)?; + let adapter = FileWalletCommAdapter::new(); + let mut slate = adapter.receive_tx_async(tx_file)?; let _ = api.finalize_tx(&mut slate).expect("Finalize failed"); let result = api.post_tx(&slate, fluff); diff --git a/wallet/src/adapters/file.rs b/wallet/src/adapters/file.rs new file mode 100644 index 000000000..ed6516ff4 --- /dev/null +++ b/wallet/src/adapters/file.rs @@ -0,0 +1,69 @@ +// 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. + +/// File Output 'plugin' implementation +use std::fs::File; +use std::io::{Read, Write}; + +use serde_json as json; +use std::collections::HashMap; + +use libtx::slate::Slate; +use libwallet::{Error, ErrorKind}; +use {WalletCommAdapter, WalletConfig}; + +#[derive(Clone)] +pub struct FileWalletCommAdapter {} + +impl FileWalletCommAdapter { + /// Create + pub fn new() -> Box { + Box::new(FileWalletCommAdapter {}) + } +} + +impl WalletCommAdapter for FileWalletCommAdapter { + fn supports_sync(&self) -> bool { + false + } + + fn send_tx_sync(&self, _dest: &str, _slate: &Slate) -> Result { + unimplemented!(); + } + + fn send_tx_async(&self, dest: &str, slate: &Slate) -> Result<(), Error> { + let mut pub_tx = File::create(dest)?; + pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?; + pub_tx.sync_all()?; + Ok(()) + } + + fn receive_tx_async(&self, params: &str) -> Result { + let mut pub_tx_f = File::open(params)?; + let mut content = String::new(); + pub_tx_f.read_to_string(&mut content)?; + Ok(json::from_str(&content).map_err(|_| ErrorKind::Format)?) + } + + fn listen( + &self, + _params: HashMap, + _config: WalletConfig, + _passphrase: &str, + _account: &str, + _node_api_secret: Option, + ) -> Result<(), Error> { + unimplemented!(); + } +} diff --git a/wallet/src/adapters/http.rs b/wallet/src/adapters/http.rs new file mode 100644 index 000000000..08cdb4e6a --- /dev/null +++ b/wallet/src/adapters/http.rs @@ -0,0 +1,86 @@ +// 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. + +/// HTTP Wallet 'plugin' implementation +use failure::ResultExt; +use std::collections::HashMap; + +use api; +use controller; +use libtx::slate::Slate; +use libwallet::{Error, ErrorKind}; +use {instantiate_wallet, WalletCommAdapter, WalletConfig}; + +#[derive(Clone)] +pub struct HTTPWalletCommAdapter {} + +impl HTTPWalletCommAdapter { + /// Create + pub fn new() -> Box { + Box::new(HTTPWalletCommAdapter {}) + } +} + +impl WalletCommAdapter for HTTPWalletCommAdapter { + fn supports_sync(&self) -> bool { + true + } + + fn send_tx_sync(&self, dest: &str, slate: &Slate) -> Result { + if &dest[..4] != "http" { + let err_str = format!( + "dest formatted as {} but send -d expected stdout or http://IP:port", + dest + ); + error!("{}", err_str,); + Err(ErrorKind::Uri)? + } + let url = format!("{}/v1/wallet/foreign/receive_tx", dest); + debug!("Posting transaction slate to {}", url); + + let res = api::client::post(url.as_str(), None, slate) + .context(ErrorKind::ClientCallback("Posting transaction slate"))?; + Ok(res) + } + + fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> { + unimplemented!(); + } + + fn receive_tx_async(&self, _params: &str) -> Result { + unimplemented!(); + } + + fn listen( + &self, + params: HashMap, + config: WalletConfig, + passphrase: &str, + account: &str, + node_api_secret: Option, + ) -> Result<(), Error> { + let wallet = + instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone()); + let listen_addr = params.get("api_listen_addr").unwrap(); + let tls_conf = match params.get("certificate") { + Some(s) => Some(api::TLSConfig::new( + s.to_owned(), + params.get("private_key").unwrap().to_owned(), + )), + None => None, + }; + controller::foreign_listener(wallet.clone(), &listen_addr, tls_conf)?; + Ok(()) + } +} diff --git a/wallet/src/adapters/mod.rs b/wallet/src/adapters/mod.rs new file mode 100644 index 000000000..2b9c0113f --- /dev/null +++ b/wallet/src/adapters/mod.rs @@ -0,0 +1,55 @@ +// 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. + +mod file; +mod http; +mod null; + +pub use self::file::FileWalletCommAdapter; +pub use self::http::HTTPWalletCommAdapter; +pub use self::null::NullWalletCommAdapter; + +use std::collections::HashMap; + +use libtx::slate::Slate; +use libwallet::Error; +use WalletConfig; + +/// Encapsulate wallet to wallet communication functions +pub trait WalletCommAdapter { + /// Whether this adapter supports sync mode + fn supports_sync(&self) -> bool; + + /// Send a transaction slate to another listening wallet and return result + /// TODO: Probably need a slate wrapper type + fn send_tx_sync(&self, addr: &str, slate: &Slate) -> Result; + + /// Send a transaction asynchronously (result will be returned via the listener) + fn send_tx_async(&self, addr: &str, slate: &Slate) -> Result<(), Error>; + + /// Receive a transaction async. (Actually just read it from wherever and return the slate) + fn receive_tx_async(&self, params: &str) -> Result; + + /// Start a listener, passing received messages to the wallet api directly + /// Takes a wallet config for now to avoid needing all sorts of awkward + /// type parameters on this trait + fn listen( + &self, + params: HashMap, + config: WalletConfig, + passphrase: &str, + account: &str, + node_api_secret: Option, + ) -> Result<(), Error>; +} diff --git a/wallet/src/adapters/null.rs b/wallet/src/adapters/null.rs new file mode 100644 index 000000000..34ed5c576 --- /dev/null +++ b/wallet/src/adapters/null.rs @@ -0,0 +1,59 @@ +// 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. + +/// Null Output 'plugin' implementation +use libtx::slate::Slate; +use libwallet::Error; +use {WalletCommAdapter, WalletConfig}; + +use std::collections::HashMap; + +#[derive(Clone)] +pub struct NullWalletCommAdapter {} + +impl NullWalletCommAdapter { + /// Create + pub fn new() -> Box { + Box::new(NullWalletCommAdapter {}) + } +} + +impl WalletCommAdapter for NullWalletCommAdapter { + fn supports_sync(&self) -> bool { + true + } + + fn send_tx_sync(&self, _dest: &str, _slate: &Slate) -> Result { + unimplemented!(); + } + + fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> { + unimplemented!(); + } + + fn receive_tx_async(&self, _params: &str) -> Result { + unimplemented!(); + } + + fn listen( + &self, + _params: HashMap, + _config: WalletConfig, + _passphrase: &str, + _account: &str, + _node_api_secret: Option, + ) -> Result<(), Error> { + unimplemented!(); + } +} diff --git a/wallet/src/libwallet/controller.rs b/wallet/src/controller.rs similarity index 82% rename from wallet/src/libwallet/controller.rs rename to wallet/src/controller.rs index da7eb7754..30e53a400 100644 --- a/wallet/src/libwallet/controller.rs +++ b/wallet/src/controller.rs @@ -15,7 +15,9 @@ //! Controller for wallet.. instantiates and handles listeners (or single-run //! invocations) as needed. //! Still experimental +use adapters::{FileWalletCommAdapter, HTTPWalletCommAdapter}; use api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router, TLSConfig}; +use core::core; use core::core::Transaction; use failure::ResultExt; use futures::future::{err, ok}; @@ -25,8 +27,7 @@ use keychain::Keychain; use libtx::slate::Slate; use libwallet::api::{APIForeign, APIOwner}; use libwallet::types::{ - CbData, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletInfo, WalletToNodeClient, - WalletToWalletClient, + CbData, NodeClient, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletInfo, }; use libwallet::{Error, ErrorKind}; use serde::{Deserialize, Serialize}; @@ -42,12 +43,11 @@ use util::Mutex; /// Instantiate wallet Owner API for a single-use (command line) call /// Return a function containing a loaded API context to call -pub fn owner_single_use(wallet: Arc>, f: F) -> Result<(), Error> +pub fn owner_single_use(wallet: Arc>, f: F) -> Result<(), Error> where - T: WalletBackend, - F: FnOnce(&mut APIOwner) -> Result<(), Error>, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + F: FnOnce(&mut APIOwner) -> Result<(), Error>, + C: NodeClient, K: Keychain, { f(&mut APIOwner::new(wallet.clone()))?; @@ -56,12 +56,11 @@ where /// Instantiate wallet Foreign API for a single-use (command line) call /// Return a function containing a loaded API context to call -pub fn foreign_single_use(wallet: Arc>, f: F) -> Result<(), Error> +pub fn foreign_single_use(wallet: Arc>, f: F) -> Result<(), Error> where - T: WalletBackend, - F: FnOnce(&mut APIForeign) -> Result<(), Error>, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + F: FnOnce(&mut APIForeign) -> Result<(), Error>, + C: NodeClient, K: Keychain, { f(&mut APIForeign::new(wallet.clone()))?; @@ -70,17 +69,16 @@ where /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn owner_listener( +pub fn owner_listener( wallet: Arc>, addr: &str, api_secret: Option, tls_config: Option, ) -> Result<(), Error> where - T: WalletBackend + Send + Sync + 'static, - OwnerAPIHandler: Handler, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + OwnerAPIHandler: Handler, + C: NodeClient + 'static, K: Keychain + 'static, { let api_handler = OwnerAPIHandler::new(wallet); @@ -112,15 +110,14 @@ where /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn foreign_listener( +pub fn foreign_listener( wallet: Arc>, addr: &str, tls_config: Option, ) -> Result<(), Error> where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, K: Keychain + 'static, { let api_handler = ForeignAPIHandler::new(wallet); @@ -147,41 +144,37 @@ where type WalletResponseFuture = Box, Error = Error> + Send>; /// API Handler/Wrapper for owner functions -pub struct OwnerAPIHandler +pub struct OwnerAPIHandler where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, K: Keychain + 'static, { /// Wallet instance pub wallet: Arc>, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } -impl OwnerAPIHandler +impl OwnerAPIHandler where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, K: Keychain + 'static, { /// Create a new owner API handler for GET methods - pub fn new(wallet: Arc>) -> OwnerAPIHandler { + pub fn new(wallet: Arc>) -> OwnerAPIHandler { OwnerAPIHandler { wallet, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } } fn retrieve_outputs( &self, req: &Request, - api: APIOwner, + api: APIOwner, ) -> Result<(bool, Vec<(OutputData, pedersen::Commitment)>), Error> { let mut update_from_node = false; let mut id = None; @@ -205,7 +198,7 @@ where fn retrieve_txs( &self, req: &Request, - api: APIOwner, + api: APIOwner, ) -> Result<(bool, Vec), Error> { let mut tx_id = None; let mut tx_slate_id = None; @@ -232,7 +225,7 @@ where fn dump_stored_tx( &self, req: &Request, - api: APIOwner, + api: APIOwner, ) -> Result { let params = parse_params(req); if let Some(id_string) = params.get("id") { @@ -261,7 +254,7 @@ where fn retrieve_summary_info( &self, req: &Request, - mut api: APIOwner, + mut api: APIOwner, ) -> Result<(bool, WalletInfo), Error> { let update_from_node = param_exists(req, "refresh"); api.retrieve_summary_info(update_from_node) @@ -270,7 +263,7 @@ where fn node_height( &self, _req: &Request, - mut api: APIOwner, + mut api: APIOwner, ) -> Result<(u64, bool), Error> { api.node_height() } @@ -298,39 +291,62 @@ where fn issue_send_tx( &self, req: Request, - mut api: APIOwner, + mut api: APIOwner, ) -> Box + Send> { Box::new(parse_body(req).and_then(move |args: SendTXArgs| { + let result = api.initiate_tx( + None, + args.amount, + args.minimum_confirmations, + args.max_outputs, + args.num_change_outputs, + args.selection_strategy_is_use_all, + ); + let (mut slate, lock_fn) = match result { + Ok(s) => { + info!( + "Tx created: {} grin to {} (strategy '{}')", + core::amount_to_hr_string(args.amount, false), + &args.dest, + args.selection_strategy_is_use_all, + ); + s + } + Err(e) => { + error!("Tx not created: {}", e); + match e.kind() { + // user errors, don't backtrace + ErrorKind::NotEnoughFunds { .. } => {} + ErrorKind::FeeDispute { .. } => {} + ErrorKind::FeeExceedsAmount { .. } => {} + _ => { + // otherwise give full dump + error!("Backtrace: {}", e.backtrace().unwrap()); + } + }; + return Err(e); + } + }; if args.method == "http" { - api.issue_send_tx( - args.amount, - args.minimum_confirmations, - &args.dest, - args.max_outputs, - args.num_change_outputs, - args.selection_strategy_is_use_all, - ) + let adapter = HTTPWalletCommAdapter::new(); + slate = adapter.send_tx_sync(&args.dest, &slate)?; + api.finalize_tx(&mut slate)?; } else if args.method == "file" { - api.send_tx( - false, - args.amount, - args.minimum_confirmations, - &args.dest, - args.max_outputs, - args.num_change_outputs, - args.selection_strategy_is_use_all, - ) + let adapter = FileWalletCommAdapter::new(); + adapter.send_tx_async(&args.dest, &slate)?; } else { error!("unsupported payment method: {}", args.method); return Err(ErrorKind::ClientCallback("unsupported payment method"))?; } + api.tx_lock_outputs(&slate, lock_fn)?; + Ok(slate) })) } fn finalize_tx( &self, req: Request, - mut api: APIOwner, + mut api: APIOwner, ) -> Box + Send> { Box::new( parse_body(req).and_then(move |mut slate| match api.finalize_tx(&mut slate) { @@ -346,7 +362,7 @@ where fn cancel_tx( &self, req: Request, - mut api: APIOwner, + mut api: APIOwner, ) -> Box + Send> { let params = parse_params(&req); if let Some(id_string) = params.get("id") { @@ -391,7 +407,7 @@ where fn post_tx( &self, req: Request, - api: APIOwner, + api: APIOwner, ) -> Box + Send> { let params = match req.uri().query() { Some(query_string) => form_urlencoded::parse(query_string.as_bytes()) @@ -417,7 +433,7 @@ where fn issue_burn_tx( &self, _req: Request, - mut api: APIOwner, + mut api: APIOwner, ) -> Box + Send> { // TODO: Args Box::new(match api.issue_burn_tx(60, 10, 1000) { @@ -463,11 +479,10 @@ where } } -impl Handler for OwnerAPIHandler +impl Handler for OwnerAPIHandler where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, K: Keychain + 'static, { fn get(&self, req: Request) -> ResponseFuture { @@ -498,41 +513,37 @@ where /// API Handler/Wrapper for foreign functions -pub struct ForeignAPIHandler +pub struct ForeignAPIHandler where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, K: Keychain + 'static, { /// Wallet instance pub wallet: Arc>, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } -impl ForeignAPIHandler +impl ForeignAPIHandler where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, K: Keychain + 'static, { /// create a new api handler - pub fn new(wallet: Arc>) -> ForeignAPIHandler { + pub fn new(wallet: Arc>) -> ForeignAPIHandler { ForeignAPIHandler { wallet, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } } fn build_coinbase( &self, req: Request, - mut api: APIForeign, + mut api: APIForeign, ) -> Box + Send> { Box::new(parse_body(req).and_then(move |block_fees| api.build_coinbase(&block_fees))) } @@ -540,17 +551,17 @@ where fn receive_tx( &self, req: Request, - mut api: APIForeign, + mut api: APIForeign, ) -> Box + Send> { - Box::new( - parse_body(req).and_then(move |mut slate| match api.receive_tx(&mut slate) { + Box::new(parse_body(req).and_then( + move |mut slate| match api.receive_tx(&mut slate, None) { Ok(_) => ok(slate.clone()), Err(e) => { error!("receive_tx: failed with error: {}", e); err(e) } - }), - ) + }, + )) } fn handle_request(&self, req: Request) -> WalletResponseFuture { @@ -575,11 +586,10 @@ where } } } -impl Handler for ForeignAPIHandler +impl Handler for ForeignAPIHandler where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient + Send + Sync + 'static, - L: WalletToWalletClient + Send + Sync + 'static, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + Send + Sync + 'static, K: Keychain + 'static, { fn post(&self, req: Request) -> ResponseFuture { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 183f211e6..bf2698502 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -45,20 +45,48 @@ extern crate grin_keychain as keychain; extern crate grin_store as store; extern crate grin_util as util; -mod client; +mod adapters; +pub mod controller; pub mod display; mod error; pub mod libtx; pub mod libwallet; pub mod lmdb_wallet; +mod node_clients; mod types; -pub use client::{create_coinbase, HTTPWalletToNodeClient, HTTPWalletToWalletClient}; -pub use error::{Error, ErrorKind}; -pub use libwallet::controller; -pub use libwallet::types::{ - BlockFees, CbData, WalletBackend, WalletInfo, WalletInst, WalletToNodeClient, - WalletToWalletClient, +pub use adapters::{ + FileWalletCommAdapter, HTTPWalletCommAdapter, NullWalletCommAdapter, WalletCommAdapter, }; +pub use error::{Error, ErrorKind}; +pub use libwallet::types::{BlockFees, CbData, NodeClient, WalletBackend, WalletInfo, WalletInst}; pub use lmdb_wallet::{wallet_db_exists, LMDBBackend}; +pub use node_clients::{create_coinbase, HTTPNodeClient}; pub use types::{WalletConfig, WalletSeed, SEED_FILE}; + +use std::sync::Arc; +use util::Mutex; + +/// Helper to create an instance of the LMDB wallet +pub fn instantiate_wallet( + wallet_config: WalletConfig, + passphrase: &str, + account: &str, + node_api_secret: Option, +) -> Arc>> { + let client_n = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret); + let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n) + .unwrap_or_else(|e| { + panic!( + "Error creating DB wallet: {} Config: {:?}", + e, wallet_config + ); + }); + db_wallet + .set_parent_key_id_by_name(account) + .unwrap_or_else(|e| { + panic!("Error starting wallet: {}", e); + }); + info!("Using LMDB Backend for wallet"); + Arc::new(Mutex::new(db_wallet)) +} diff --git a/wallet/src/libwallet/api.rs b/wallet/src/libwallet/api.rs index cb9778f35..faf27f626 100644 --- a/wallet/src/libwallet/api.rs +++ b/wallet/src/libwallet/api.rs @@ -18,7 +18,7 @@ //! Still experimental, not sure this is the best way to do this use std::fs::File; -use std::io::{Read, Write}; +use std::io::Write; use std::marker::PhantomData; use std::sync::Arc; use util::Mutex; @@ -31,10 +31,10 @@ use core::core::Transaction; use core::ser; use keychain::{Identifier, Keychain}; use libtx::slate::Slate; -use libwallet::internal::{keys, selection, tx, updater}; +use libwallet::internal::{keys, tx, updater}; use libwallet::types::{ - AcctPathMapping, BlockFees, CbData, OutputData, TxLogEntry, TxWrapper, WalletBackend, - WalletInfo, WalletToNodeClient, WalletToWalletClient, + AcctPathMapping, BlockFees, CbData, NodeClient, OutputData, TxLogEntry, TxWrapper, + WalletBackend, WalletInfo, }; use libwallet::{Error, ErrorKind}; use util; @@ -42,11 +42,10 @@ use util::secp::pedersen; /// Wrapper around internal API functions, containing a reference to /// the wallet/keychain that they're acting upon -pub struct APIOwner +pub struct APIOwner where - W: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + W: WalletBackend, + C: NodeClient, K: Keychain, { /// Wallet, contains its keychain (TODO: Split these up into 2 traits @@ -54,14 +53,12 @@ where pub wallet: Arc>, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } -impl APIOwner +impl APIOwner where - W: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + W: WalletBackend, + C: NodeClient, K: Keychain, { /// Create new API instance @@ -70,7 +67,6 @@ where wallet: wallet_in, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } } @@ -160,117 +156,29 @@ where keys::new_acct_path(&mut *w, label) } - /// Issues a send transaction and sends to recipient - pub fn issue_send_tx( + /// Creates a new partial transaction for the given amount + pub fn initiate_tx( &mut self, + src_acct_name: Option<&str>, amount: u64, minimum_confirmations: u64, - dest: &str, max_outputs: usize, num_change_outputs: usize, selection_strategy_is_use_all: bool, - ) -> Result { + ) -> Result<(Slate, impl FnOnce(&mut W, &str) -> Result<(), Error>), Error> { let mut w = self.wallet.lock(); w.open_with_credentials()?; - let parent_key_id = w.parent_key_id(); - - let client; - let mut slate_out: Slate; - let lock_fn_out; - - client = w.w2w_client().clone(); - let (slate, context, lock_fn) = tx::create_send_tx( - &mut *w, - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - &parent_key_id, - false, - )?; - - lock_fn_out = lock_fn; - slate_out = match client.send_tx_slate(dest, &slate) { - Ok(s) => s, - Err(e) => { - error!( - "Communication with receiver failed on SenderInitiation send. Aborting transaction {:?}", - e, - ); - return Err(e)?; + let parent_key_id = match src_acct_name { + Some(d) => { + let pm = w.get_acct_path(d.to_owned())?; + match pm { + Some(p) => p.path, + None => w.parent_key_id(), + } } + None => w.parent_key_id(), }; - tx::complete_tx(&mut *w, &mut slate_out, &context)?; - let tx_hex = util::to_hex(ser::ser_vec(&slate_out.tx).unwrap()); - - // lock our inputs - lock_fn_out(&mut *w, &tx_hex)?; - w.close()?; - Ok(slate_out) - } - - /// Issues a send transaction to the same wallet, without needing communication - /// good for consolidating outputs, or can be extended to split outputs to multiple - /// accounts - pub fn issue_self_tx( - &mut self, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - src_acct_name: &str, - dest_acct_name: &str, - ) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let orig_parent_key_id = w.parent_key_id(); - w.set_parent_key_id_by_name(src_acct_name)?; - let parent_key_id = w.parent_key_id(); - - let (mut slate, context, lock_fn) = tx::create_send_tx( - &mut *w, - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - &parent_key_id, - true, - )?; - - w.set_parent_key_id_by_name(dest_acct_name)?; - let parent_key_id = w.parent_key_id(); - tx::receive_tx(&mut *w, &mut slate, &parent_key_id, true)?; - - tx::complete_tx(&mut *w, &mut slate, &context)?; - let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap()); - - // lock our inputs - lock_fn(&mut *w, &tx_hex)?; - w.set_parent_key_id(orig_parent_key_id); - w.close()?; - Ok(slate) - } - - /// Write a transaction to send to file so a user can transmit it to the - /// receiver in whichever way they see fit (aka carrier pigeon mode). - pub fn send_tx( - &mut self, - write_to_disk: bool, - amount: u64, - minimum_confirmations: u64, - dest: &str, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - ) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let parent_key_id = w.parent_key_id(); - let (slate, context, lock_fn) = tx::create_send_tx( &mut *w, amount, @@ -281,24 +189,30 @@ where &parent_key_id, false, )?; - if write_to_disk { - let mut pub_tx = File::create(dest)?; - pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?; - pub_tx.sync_all()?; - } + // Save the aggsig context in our DB for when we + // recieve the transaction back { let mut batch = w.batch()?; batch.save_private_context(slate.id.as_bytes(), &context)?; batch.commit()?; } - let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap()); - - // lock our inputs - lock_fn(&mut *w, &tx_hex)?; w.close()?; - Ok(slate) + Ok((slate, lock_fn)) + } + + /// Lock outputs associated with a given slate/transaction + pub fn tx_lock_outputs( + &mut self, + slate: &Slate, + lock_fn: impl FnOnce(&mut W, &str) -> Result<(), Error>, + ) -> Result<(), Error> { + let mut w = self.wallet.lock(); + w.open_with_credentials()?; + let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap()); + lock_fn(&mut *w, &tx_hex)?; + Ok(()) } /// Sender finalization of the transaction. Takes the file returned by the @@ -308,7 +222,6 @@ where pub fn finalize_tx(&mut self, slate: &mut Slate) -> Result<(), Error> { let mut w = self.wallet.lock(); w.open_with_credentials()?; - let context = w.get_private_context(slate.id.as_bytes())?; tx::complete_tx(&mut *w, slate, &context)?; { @@ -513,11 +426,10 @@ where /// Wrapper around external API functions, intended to communicate /// with other parties -pub struct APIForeign +pub struct APIForeign where - W: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + W: WalletBackend, + C: NodeClient, K: Keychain, { /// Wallet, contains its keychain (TODO: Split these up into 2 traits @@ -525,14 +437,12 @@ where pub wallet: Arc>, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, } -impl<'a, W: ?Sized, C, L, K> APIForeign +impl<'a, W: ?Sized, C, K> APIForeign where - W: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + W: WalletBackend, + C: NodeClient, K: Keychain, { /// Create new API instance @@ -541,7 +451,6 @@ where wallet: wallet_in, phantom: PhantomData, phantom_c: PhantomData, - phantom_l: PhantomData, }) } @@ -554,52 +463,24 @@ where res } - /// A sender provided a transaction file with appropriate public keys and - /// metadata. Complete the receivers' end of it to generate another file - /// to send back. - pub fn file_receive_tx(&mut self, source: &str) -> Result<(), Error> { - let mut pub_tx_f = File::open(source)?; - let mut content = String::new(); - pub_tx_f.read_to_string(&mut content)?; - let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?; - - let mut wallet = self.wallet.lock(); - wallet.open_with_credentials()?; - let parent_key_id = wallet.parent_key_id(); - - // create an output using the amount in the slate - let (_, mut context, receiver_create_fn) = selection::build_recipient_output_with_slate( - &mut *wallet, - &mut slate, - parent_key_id, - false, - )?; - - // fill public keys - let _ = slate.fill_round_1( - wallet.keychain(), - &mut context.sec_key, - &context.sec_nonce, - 1, - )?; - - // perform partial sig - let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?; - - // save to file - let mut pub_tx = File::create(source.to_owned() + ".response")?; - pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?; - - // Save output in wallet - let _ = receiver_create_fn(&mut wallet); - Ok(()) - } - /// 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, + dest_acct_name: Option<&str>, + ) -> Result<(), Error> { let mut w = self.wallet.lock(); w.open_with_credentials()?; - let parent_key_id = w.parent_key_id(); + let parent_key_id = match dest_acct_name { + Some(d) => { + let pm = w.get_acct_path(d.to_owned())?; + match pm { + Some(p) => p.path, + None => w.parent_key_id(), + } + } + None => w.parent_key_id(), + }; let res = tx::receive_tx(&mut *w, slate, &parent_key_id, false); w.close()?; diff --git a/wallet/src/libwallet/internal/keys.rs b/wallet/src/libwallet/internal/keys.rs index 2ed964e65..2b789808c 100644 --- a/wallet/src/libwallet/internal/keys.rs +++ b/wallet/src/libwallet/internal/keys.rs @@ -15,14 +15,13 @@ //! Wallet key management functions use keychain::{ChildNumber, ExtKeychain, Identifier, Keychain}; use libwallet::error::{Error, ErrorKind}; -use libwallet::types::{AcctPathMapping, WalletBackend, WalletToNodeClient, WalletToWalletClient}; +use libwallet::types::{AcctPathMapping, NodeClient, WalletBackend}; /// Get next available key in the wallet for a given parent -pub fn next_available_key(wallet: &mut T) -> Result +pub fn next_available_key(wallet: &mut T) -> Result where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let child = wallet.next_child()?; @@ -30,14 +29,13 @@ where } /// Retrieve an existing key from a wallet -pub fn retrieve_existing_key( +pub fn retrieve_existing_key( wallet: &T, key_id: Identifier, ) -> Result<(Identifier, u32), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let existing = wallet.get(&key_id)?; @@ -47,22 +45,20 @@ where } /// Returns a list of account to BIP32 path mappings -pub fn accounts(wallet: &mut T) -> Result, Error> +pub fn accounts(wallet: &mut T) -> Result, Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { Ok(wallet.acct_path_iter().collect()) } /// Adds an new parent account path with a given label -pub fn new_acct_path(wallet: &mut T, label: &str) -> Result +pub fn new_acct_path(wallet: &mut T, label: &str) -> Result where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let label = label.to_owned(); @@ -100,15 +96,14 @@ where } /// Adds/sets a particular account path with a given label -pub fn set_acct_path( +pub fn set_acct_path( wallet: &mut T, label: &str, path: &Identifier, ) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let label = label.to_owned(); diff --git a/wallet/src/libwallet/internal/restore.rs b/wallet/src/libwallet/internal/restore.rs index 490b01341..a553b67eb 100644 --- a/wallet/src/libwallet/internal/restore.rs +++ b/wallet/src/libwallet/internal/restore.rs @@ -42,14 +42,13 @@ struct OutputResult { pub blinding: SecretKey, } -fn identify_utxo_outputs( +fn identify_utxo_outputs( wallet: &mut T, outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>, ) -> Result, Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let mut wallet_outputs: Vec = Vec::new(); @@ -99,11 +98,10 @@ where } /// Restore a wallet -pub fn restore(wallet: &mut T) -> Result<(), Error> +pub fn restore(wallet: &mut T) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // Don't proceed if wallet_data has anything in it diff --git a/wallet/src/libwallet/internal/selection.rs b/wallet/src/libwallet/internal/selection.rs index bffc4d095..eae6664e2 100644 --- a/wallet/src/libwallet/internal/selection.rs +++ b/wallet/src/libwallet/internal/selection.rs @@ -25,7 +25,7 @@ use libwallet::types::*; /// and saves the private wallet identifiers of our selected outputs /// into our transaction context -pub fn build_send_tx_slate( +pub fn build_send_tx_slate( wallet: &mut T, num_participants: usize, amount: u64, @@ -46,9 +46,8 @@ pub fn build_send_tx_slate( Error, > where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let (elems, inputs, change_amounts_derivations, amount, fee) = select_send_tx( @@ -145,7 +144,7 @@ where /// returning the key of the fresh output and a closure /// that actually performs the addition of the output to the /// wallet -pub fn build_recipient_output_with_slate( +pub fn build_recipient_output_with_slate( wallet: &mut T, slate: &mut Slate, parent_key_id: Identifier, @@ -159,9 +158,8 @@ pub fn build_recipient_output_with_slate( Error, > where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // Create a potential output for this transaction @@ -219,7 +217,7 @@ where /// Builds a transaction to send to someone from the HD seed associated with the /// wallet and the amount to send. Handles reading through the wallet data file, /// selecting outputs to spend and building the change. -pub fn select_send_tx( +pub fn select_send_tx( wallet: &mut T, amount: u64, current_height: u64, @@ -240,9 +238,8 @@ pub fn select_send_tx( Error, > where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // select some spendable coins from the wallet @@ -329,7 +326,7 @@ where } /// Selects inputs and change for a transaction -pub fn inputs_and_change( +pub fn inputs_and_change( coins: &Vec, wallet: &mut T, amount: u64, @@ -337,9 +334,8 @@ pub fn inputs_and_change( num_change_outputs: usize, ) -> Result<(Vec>>, Vec<(u64, Identifier)>), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let mut parts = vec![]; @@ -401,7 +397,7 @@ where /// we should pass something other than a bool in. /// TODO: Possibly move this into another trait to be owned by a wallet? -pub fn select_coins( +pub fn select_coins( wallet: &mut T, amount: u64, current_height: u64, @@ -412,9 +408,8 @@ pub fn select_coins( ) -> (usize, Vec) // max_outputs_available, Outputs where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // first find all eligible outputs based on number of confirmations diff --git a/wallet/src/libwallet/internal/tx.rs b/wallet/src/libwallet/internal/tx.rs index 82aca05d0..d082f2972 100644 --- a/wallet/src/libwallet/internal/tx.rs +++ b/wallet/src/libwallet/internal/tx.rs @@ -24,23 +24,20 @@ use keychain::{Identifier, Keychain}; use libtx::slate::Slate; use libtx::{build, tx_fee}; use libwallet::internal::{selection, updater}; -use libwallet::types::{ - Context, TxLogEntryType, WalletBackend, WalletToNodeClient, WalletToWalletClient, -}; +use libwallet::types::{Context, NodeClient, TxLogEntryType, WalletBackend}; use libwallet::{Error, ErrorKind}; /// Receive a transaction, modifying the slate accordingly (which can then be /// sent back to sender for posting) -pub fn receive_tx( +pub fn receive_tx( wallet: &mut T, slate: &mut Slate, parent_key_id: &Identifier, is_self: bool, ) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // create an output using the amount in the slate @@ -70,7 +67,7 @@ where /// Issue a new transaction to the provided sender by spending some of our /// wallet -pub fn create_send_tx( +pub fn create_send_tx( wallet: &mut T, amount: u64, minimum_confirmations: u64, @@ -88,9 +85,8 @@ pub fn create_send_tx( Error, > where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // Get lock height @@ -135,15 +131,14 @@ where } /// Complete a transaction as the sender -pub fn complete_tx( +pub fn complete_tx( wallet: &mut T, slate: &mut Slate, context: &Context, ) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 0)?; @@ -156,16 +151,15 @@ where } /// Rollback outputs associated with a transaction in the wallet -pub fn cancel_tx( +pub fn cancel_tx( wallet: &mut T, parent_key_id: &Identifier, tx_id: Option, tx_slate_id: Option, ) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let mut tx_id_string = String::new(); @@ -194,15 +188,14 @@ where /// Retrieve the associated stored finalised hex Transaction for a given transaction Id /// as well as whether it's been confirmed -pub fn retrieve_tx_hex( +pub fn retrieve_tx_hex( wallet: &mut T, parent_key_id: &Identifier, tx_id: u32, ) -> Result<(bool, Option), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let tx_vec = updater::retrieve_txs(wallet, Some(tx_id), None, parent_key_id)?; @@ -214,7 +207,7 @@ where } /// Issue a burn tx -pub fn issue_burn_tx( +pub fn issue_burn_tx( wallet: &mut T, amount: u64, minimum_confirmations: u64, @@ -222,9 +215,8 @@ pub fn issue_burn_tx( parent_key_id: &Identifier, ) -> Result where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // TODO diff --git a/wallet/src/libwallet/internal/updater.rs b/wallet/src/libwallet/internal/updater.rs index 4f0abb0f4..2e08d26ca 100644 --- a/wallet/src/libwallet/internal/updater.rs +++ b/wallet/src/libwallet/internal/updater.rs @@ -28,23 +28,22 @@ use libwallet; use libwallet::error::{Error, ErrorKind}; use libwallet::internal::keys; use libwallet::types::{ - BlockFees, CbData, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, WalletBackend, - WalletInfo, WalletToNodeClient, WalletToWalletClient, + BlockFees, CbData, NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, + WalletBackend, WalletInfo, }; use util; use util::secp::pedersen; /// Retrieve all of the outputs (doesn't attempt to update from node) -pub fn retrieve_outputs( +pub fn retrieve_outputs( wallet: &mut T, show_spent: bool, tx_id: Option, parent_key_id: &Identifier, ) -> Result, Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // just read the wallet here, no need for a write lock @@ -79,16 +78,15 @@ where } /// Retrieve all of the transaction entries, or a particular entry -pub fn retrieve_txs( +pub fn retrieve_txs( wallet: &mut T, tx_id: Option, tx_slate_id: Option, parent_key_id: &Identifier, ) -> Result, Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // just read the wallet here, no need for a write lock @@ -117,14 +115,13 @@ where } /// Refreshes the outputs in a wallet with the latest information /// from a node -pub fn refresh_outputs( +pub fn refresh_outputs( wallet: &mut T, parent_key_id: &Identifier, ) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let height = wallet.w2n_client().get_chain_height()?; @@ -134,14 +131,13 @@ where /// build a local map of wallet outputs keyed by commit /// and a list of outputs we want to query the node for -pub fn map_wallet_outputs( +pub fn map_wallet_outputs( wallet: &mut T, parent_key_id: &Identifier, ) -> Result, Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let mut wallet_outputs: HashMap = HashMap::new(); @@ -157,16 +153,15 @@ where } /// Cancel transaction and associated outputs -pub fn cancel_tx_and_outputs( +pub fn cancel_tx_and_outputs( wallet: &mut T, tx: TxLogEntry, outputs: Vec, parent_key_id: &Identifier, ) -> Result<(), libwallet::Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let mut batch = wallet.batch()?; @@ -194,7 +189,7 @@ where } /// Apply refreshed API output data to the wallet -pub fn apply_api_outputs( +pub fn apply_api_outputs( wallet: &mut T, wallet_outputs: &HashMap, api_outputs: &HashMap, @@ -202,9 +197,8 @@ pub fn apply_api_outputs( parent_key_id: &Identifier, ) -> Result<(), libwallet::Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { // now for each commit, find the output in the wallet and the corresponding @@ -275,15 +269,14 @@ where /// Builds a single api query to retrieve the latest output data from the node. /// So we can refresh the local wallet outputs. -fn refresh_output_state( +fn refresh_output_state( wallet: &mut T, height: u64, parent_key_id: &Identifier, ) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { debug!("Refreshing wallet outputs"); @@ -302,11 +295,10 @@ where Ok(()) } -fn clean_old_unconfirmed(wallet: &mut T, height: u64) -> Result<(), Error> +fn clean_old_unconfirmed(wallet: &mut T, height: u64) -> Result<(), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { if height < 500 { @@ -328,14 +320,13 @@ where /// Retrieve summary info about the wallet /// caller should refresh first if desired -pub fn retrieve_info( +pub fn retrieve_info( wallet: &mut T, parent_key_id: &Identifier, ) -> Result where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let current_height = wallet.last_confirmed_height()?; @@ -373,14 +364,13 @@ where } /// Build a coinbase output and insert into wallet -pub fn build_coinbase( +pub fn build_coinbase( wallet: &mut T, block_fees: &BlockFees, ) -> Result where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let (out, kern, block_fees) = receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?; @@ -403,14 +393,13 @@ where //TODO: Split up the output creation and the wallet insertion /// Build a coinbase output and the corresponding kernel -pub fn receive_coinbase( +pub fn receive_coinbase( wallet: &mut T, block_fees: &BlockFees, ) -> Result<(Output, TxKernel, BlockFees), Error> where - T: WalletBackend, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend, + C: NodeClient, K: Keychain, { let height = block_fees.height; diff --git a/wallet/src/libwallet/mod.rs b/wallet/src/libwallet/mod.rs index 2b2f24078..8170164ac 100644 --- a/wallet/src/libwallet/mod.rs +++ b/wallet/src/libwallet/mod.rs @@ -23,7 +23,6 @@ #![warn(missing_docs)] pub mod api; -pub mod controller; mod error; pub mod internal; pub mod types; diff --git a/wallet/src/libwallet/types.rs b/wallet/src/libwallet/types.rs index 17c5f99c2..af5d23fee 100644 --- a/wallet/src/libwallet/types.rs +++ b/wallet/src/libwallet/types.rs @@ -38,18 +38,16 @@ use util::secp::key::{PublicKey, SecretKey}; use util::secp::{self, pedersen, Secp256k1}; /// Combined trait to allow dynamic wallet dispatch -pub trait WalletInst: WalletBackend + Send + Sync + 'static +pub trait WalletInst: WalletBackend + Send + Sync + 'static where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: Keychain, { } -impl WalletInst for T +impl WalletInst for T where - T: WalletBackend + Send + Sync + 'static, - C: WalletToNodeClient, - L: WalletToWalletClient, + T: WalletBackend + Send + Sync + 'static, + C: NodeClient, K: Keychain, {} @@ -57,10 +55,9 @@ where /// Wallets should implement this backend for their storage. All functions /// here expect that the wallet instance has instantiated itself or stored /// whatever credentials it needs -pub trait WalletBackend +pub trait WalletBackend where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: Keychain, { /// Initialize with whatever stored credentials we have @@ -75,9 +72,6 @@ where /// Return the client being used to communicate with the node fn w2n_client(&mut self) -> &mut C; - /// Return the client being used to communicate with other wallets - fn w2w_client(&mut self) -> &mut L; - /// Set parent key id by stored account name fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>; @@ -189,7 +183,7 @@ where /// Encapsulate all wallet-node communication functions. No functions within libwallet /// should care about communication details -pub trait WalletToNodeClient: Sync + Send + Clone { +pub trait NodeClient: Sync + Send + Clone { /// Return the URL of the check node fn node_url(&self) -> &str; @@ -228,16 +222,6 @@ pub trait WalletToNodeClient: Sync + Send + Clone { >; } -/// Encapsulate wallet to wallet communication functions -pub trait WalletToWalletClient: Sync + Send + Clone { - /// Call the wallet API to create a coinbase transaction - fn create_coinbase(&self, dest: &str, block_fees: &BlockFees) -> Result; - - /// Send a transaction slate to another listening wallet and return result - /// TODO: Probably need a slate wrapper type - fn send_tx_slate(&self, addr: &str, slate: &Slate) -> Result; -} - /// Information about an output that's being tracked by the wallet. Must be /// enough to reconstruct the commitment associated with the ouput when the /// root private key is known. diff --git a/wallet/src/lmdb_wallet.rs b/wallet/src/lmdb_wallet.rs index 751f3b1cb..7d300b817 100644 --- a/wallet/src/lmdb_wallet.rs +++ b/wallet/src/lmdb_wallet.rs @@ -51,7 +51,7 @@ pub fn wallet_db_exists(config: WalletConfig) -> bool { db_path.exists() } -pub struct LMDBBackend { +pub struct LMDBBackend { db: store::Store, config: WalletConfig, /// passphrase: TODO better ways of dealing with this other than storing @@ -62,17 +62,10 @@ pub struct LMDBBackend { parent_key_id: Identifier, /// wallet to node client w2n_client: C, - /// w2w client - w2w_client: L, } -impl LMDBBackend { - pub fn new( - config: WalletConfig, - passphrase: &str, - n_client: C, - w_client: L, - ) -> Result { +impl LMDBBackend { + pub fn new(config: WalletConfig, passphrase: &str, n_client: C) -> Result { let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR); fs::create_dir_all(&db_path).expect("Couldn't create wallet backend directory!"); @@ -82,7 +75,7 @@ impl LMDBBackend { // Make sure default wallet derivation path always exists let default_account = AcctPathMapping { label: "default".to_owned(), - path: LMDBBackend::::default_path(), + path: LMDBBackend::::default_path(), }; let acct_key = to_key( ACCOUNT_PATH_MAPPING_PREFIX, @@ -100,9 +93,8 @@ impl LMDBBackend { config: config.clone(), passphrase: String::from(passphrase), keychain: None, - parent_key_id: LMDBBackend::::default_path(), + parent_key_id: LMDBBackend::::default_path(), w2n_client: n_client, - w2w_client: w_client, }; Ok(res) } @@ -122,10 +114,9 @@ impl LMDBBackend { } } -impl WalletBackend for LMDBBackend +impl WalletBackend for LMDBBackend where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: Keychain, { /// Initialise with whatever stored credentials we have @@ -155,11 +146,6 @@ where &mut self.w2n_client } - /// Return the wallet to wallet client being used - fn w2w_client(&mut self) -> &mut L { - &mut self.w2w_client - } - /// Set parent path by account name fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> { let label = label.to_owned(); @@ -292,21 +278,21 @@ where /// An atomic batch in which all changes can be committed all at once or /// discarded on error. -pub struct Batch<'a, C: 'a, L: 'a, K: 'a> +pub struct Batch<'a, C: 'a, K: 'a> where - C: WalletToNodeClient, + C: NodeClient, K: Keychain, { - _store: &'a LMDBBackend, + _store: &'a LMDBBackend, db: RefCell>>, /// Keychain keychain: Option, } #[allow(missing_docs)] -impl<'a, C, L, K> WalletOutputBatch for Batch<'a, C, L, K> +impl<'a, C, K> WalletOutputBatch for Batch<'a, C, K> where - C: WalletToNodeClient, + C: NodeClient, K: Keychain, { fn keychain(&mut self) -> &mut K { @@ -460,6 +446,8 @@ where self.save(out.clone()) } + //TODO: Keys stored unencrypted in DB.. not good + // should store keys as derivation paths instead fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error> { let ctx_key = to_key(PRIVATE_TX_CONTEXT_PREFIX, &mut slate_id.to_vec()); self.db.borrow().as_ref().unwrap().put_ser(&ctx_key, &ctx)?; diff --git a/wallet/src/client.rs b/wallet/src/node_clients/http.rs similarity index 74% rename from wallet/src/client.rs rename to wallet/src/node_clients/http.rs index 1012750ea..1225b73fe 100644 --- a/wallet/src/client.rs +++ b/wallet/src/node_clients/http.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Client functions, implementations of the WalletToNodeClient trait +//! Client functions, implementations of the NodeClient trait //! specific to the FileWallet use failure::ResultExt; @@ -24,28 +24,27 @@ use tokio::runtime::Runtime; use api; use error::{Error, ErrorKind}; -use libtx::slate::Slate; use libwallet; use util; use util::secp::pedersen; #[derive(Clone)] -pub struct HTTPWalletToNodeClient { +pub struct HTTPNodeClient { node_url: String, node_api_secret: Option, } -impl HTTPWalletToNodeClient { +impl HTTPNodeClient { /// Create a new client that will communicate with the given grin node - pub fn new(node_url: &str, node_api_secret: Option) -> HTTPWalletToNodeClient { - HTTPWalletToNodeClient { + pub fn new(node_url: &str, node_api_secret: Option) -> HTTPNodeClient { + HTTPNodeClient { node_url: node_url.to_owned(), node_api_secret: node_api_secret, } } } -impl WalletToNodeClient for HTTPWalletToNodeClient { +impl NodeClient for HTTPNodeClient { fn node_url(&self) -> &str { &self.node_url } @@ -176,62 +175,6 @@ impl WalletToNodeClient for HTTPWalletToNodeClient { } } -#[derive(Clone)] -pub struct HTTPWalletToWalletClient {} - -impl HTTPWalletToWalletClient { - /// Create a new client that will communicate other wallets - pub fn new() -> HTTPWalletToWalletClient { - HTTPWalletToWalletClient {} - } -} - -impl WalletToWalletClient for HTTPWalletToWalletClient { - /// 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 { - let url = format!("{}/v1/wallet/foreign/build_coinbase", dest); - match single_create_coinbase(&url, &block_fees) { - Err(e) => { - error!( - "Failed to get coinbase from {}. Run grin wallet listen?", - url - ); - error!("Underlying Error: {}", e.cause().unwrap()); - error!("Backtrace: {}", e.backtrace().unwrap()); - Err(libwallet::ErrorKind::ClientCallback( - "Failed to get coinbase", - ))? - } - Ok(res) => Ok(res), - } - } - - /// Send the slate to a listening wallet instance - fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result { - if &dest[..4] != "http" { - let err_str = format!( - "dest formatted as {} but send -d expected stdout or http://IP:port", - dest - ); - error!("{}", err_str,); - Err(libwallet::ErrorKind::Uri)? - } - let url = format!("{}/v1/wallet/foreign/receive_tx", dest); - debug!("Posting transaction slate to {}", url); - - let res = api::client::post(url.as_str(), None, slate).context( - libwallet::ErrorKind::ClientCallback("Posting transaction slate"), - )?; - Ok(res) - } -} - /// Call the wallet API to create a coinbase output for the given block_fees. /// Will retry based on default "retry forever with backoff" behavior. pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result { diff --git a/wallet/src/node_clients/mod.rs b/wallet/src/node_clients/mod.rs new file mode 100644 index 000000000..ffe0c71b5 --- /dev/null +++ b/wallet/src/node_clients/mod.rs @@ -0,0 +1,17 @@ +// 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. + +mod http; + +pub use self::http::{create_coinbase, HTTPNodeClient}; diff --git a/wallet/tests/accounts.rs b/wallet/tests/accounts.rs index 9646b2617..1681eb9f4 100644 --- a/wallet/tests/accounts.rs +++ b/wallet/tests/accounts.rs @@ -51,28 +51,19 @@ fn setup(test_dir: &str) { fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { setup(test_dir); // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy: WalletProxy = - WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy = 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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet1", client.get_send_instance(), wallet1.clone()); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); // define recipient wallet, add to proxy - let wallet2 = common::create_wallet( - &format!("{}/wallet2", test_dir), - client.clone(), - client.clone(), - ); - let client = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - wallet_proxy.add_wallet("wallet2", client.get_send_instance(), wallet2.clone()); + let wallet2 = common::create_wallet(&format!("{}/wallet2", test_dir), client2.clone()); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); // Set the wallet proxy listener running thread::spawn(move || { @@ -193,14 +184,16 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { } wallet::controller::owner_single_use(wallet1.clone(), |api| { - let slate = api.issue_send_tx( - reward, // amount - 2, // minimum confirmations - "wallet2", // dest - 500, // max outputs - 1, // num change outputs - true, // select all outputs + let (mut slate, lock_fn) = api.initiate_tx( + None, reward, // amount + 2, // minimum confirmations + 500, // max outputs + 1, // num change outputs + true, // select all outputs )?; + slate = client1.send_tx_slate_direct("wallet2", &slate)?; + api.finalize_tx(&mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; api.post_tx(&slate, false)?; Ok(()) })?; diff --git a/wallet/tests/common/mod.rs b/wallet/tests/common/mod.rs index a0b09dfba..f82ee7923 100644 --- a/wallet/tests/common/mod.rs +++ b/wallet/tests/common/mod.rs @@ -28,11 +28,9 @@ use util::Mutex; use chain::Chain; use core::core::{OutputFeatures, OutputIdentifier, Transaction}; use core::{consensus, global, pow, ser}; -use wallet::libwallet; -use wallet::libwallet::types::{ - BlockFees, CbData, WalletInst, WalletToNodeClient, WalletToWalletClient, -}; +use wallet::libwallet::types::{BlockFees, CbData, NodeClient, WalletInst}; use wallet::lmdb_wallet::LMDBBackend; +use wallet::{controller, libwallet}; use wallet::{WalletBackend, WalletConfig}; use util; @@ -115,14 +113,13 @@ pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: CbDa /// 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. /// Helpful for building up precise wallet balances for testing. -pub fn award_block_to_wallet( +pub fn award_block_to_wallet( chain: &Chain, txs: Vec<&Transaction>, - wallet: Arc>>, + wallet: Arc>>, ) -> Result<(), libwallet::Error> where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: keychain::Keychain, { // build block fees @@ -134,7 +131,7 @@ where height: prev.height + 1, }; // build coinbase (via api) and add block - libwallet::controller::foreign_single_use(wallet.clone(), |api| { + controller::foreign_single_use(wallet.clone(), |api| { let coinbase_tx = api.build_coinbase(&block_fees)?; add_block_with_reward(chain, txs, coinbase_tx.clone()); Ok(()) @@ -143,14 +140,13 @@ where } /// Award a blocks to a wallet directly -pub fn award_blocks_to_wallet( +pub fn award_blocks_to_wallet( chain: &Chain, - wallet: Arc>>, + wallet: Arc>>, number: usize, ) -> Result<(), libwallet::Error> where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: keychain::Keychain, { for _ in 0..number { @@ -160,20 +156,15 @@ where } /// dispatch a db wallet -pub fn create_wallet( - dir: &str, - n_client: C, - w_client: L, -) -> Arc>> +pub fn create_wallet(dir: &str, n_client: C) -> Arc>> where - C: WalletToNodeClient + 'static, - L: WalletToWalletClient + 'static, + C: NodeClient + 'static, K: keychain::Keychain + 'static, { let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = String::from(dir); let _ = wallet::WalletSeed::init_file(&wallet_config); - let mut wallet = LMDBBackend::new(wallet_config.clone(), "", n_client, w_client) + let mut wallet = LMDBBackend::new(wallet_config.clone(), "", n_client) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)); wallet.open_with_credentials().unwrap_or_else(|e| { panic!( diff --git a/wallet/tests/common/testclient.rs b/wallet/tests/common/testclient.rs index 56a847727..4ef9cb3d5 100644 --- a/wallet/tests/common/testclient.rs +++ b/wallet/tests/common/testclient.rs @@ -43,8 +43,8 @@ use keychain::Keychain; use util::secp::pedersen; use wallet::libtx::slate::Slate; -use wallet::libwallet; use wallet::libwallet::types::*; +use wallet::{controller, libwallet, WalletCommAdapter, WalletConfig}; use common; @@ -63,10 +63,9 @@ pub struct WalletProxyMessage { /// communicates with a chain instance or other wallet /// listener APIs via message queues -pub struct WalletProxy +pub struct WalletProxy where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: Keychain, { /// directory to create the chain in @@ -78,7 +77,7 @@ where String, ( Sender, - Arc>>, + Arc>>, ), >, /// simulate json send to another client @@ -92,14 +91,11 @@ where phantom_c: PhantomData, /// Phantom phantom_k: PhantomData, - /// Phantom - phantom_l: PhantomData, } -impl WalletProxy +impl WalletProxy where - C: WalletToNodeClient, - L: WalletToWalletClient, + C: NodeClient, K: Keychain, { /// Create a new client that will communicate with the given grin node @@ -128,7 +124,6 @@ where running: Arc::new(AtomicBool::new(false)), phantom_c: PhantomData, phantom_k: PhantomData, - phantom_l: PhantomData, }; retval } @@ -138,7 +133,7 @@ where &mut self, addr: &str, tx: Sender, - wallet: Arc>>, + wallet: Arc>>, ) { self.wallets.insert(addr.to_owned(), (tx, wallet)); } @@ -215,8 +210,8 @@ where } 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)?; + controller::foreign_single_use(w.clone(), |listener_api| { + listener_api.receive_tx(&mut slate, None)?; Ok(()) })?; Ok(WalletProxyMessage { @@ -314,22 +309,13 @@ impl LocalWalletClient { pub fn get_send_instance(&self) -> Sender { self.tx.lock().clone() } -} - -impl WalletToWalletClient for LocalWalletClient { - /// 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 { - unimplemented!(); - } /// Send the slate to a listening wallet instance - fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result { + pub fn send_tx_slate_direct( + &self, + dest: &str, + slate: &Slate, + ) -> Result { let m = WalletProxyMessage { sender_id: self.id.clone(), dest: dest.to_owned(), @@ -352,7 +338,55 @@ impl WalletToWalletClient for LocalWalletClient { } } -impl WalletToNodeClient for LocalWalletClient { +impl WalletCommAdapter for LocalWalletClient { + fn supports_sync(&self) -> bool { + true + } + + /// Send the slate to a listening wallet instance + fn send_tx_sync(&self, dest: &str, slate: &Slate) -> Result { + 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(); + p.send(m) + .context(libwallet::ErrorKind::ClientCallback("Send TX Slate"))?; + } + let r = self.rx.lock(); + let m = r.recv().unwrap(); + trace!("Received send_tx_slate response: {:?}", m.clone()); + Ok( + serde_json::from_str(&m.body).context(libwallet::ErrorKind::ClientCallback( + "Parsing send_tx_slate response", + ))?, + ) + } + + fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), libwallet::Error> { + unimplemented!(); + } + + fn receive_tx_async(&self, _params: &str) -> Result { + unimplemented!(); + } + + fn listen( + &self, + params: HashMap, + config: WalletConfig, + passphrase: &str, + account: &str, + node_api_secret: Option, + ) -> Result<(), libwallet::Error> { + unimplemented!(); + } +} + +impl NodeClient for LocalWalletClient { fn node_url(&self) -> &str { "node" } diff --git a/wallet/tests/file.rs b/wallet/tests/file.rs new file mode 100644 index 000000000..4be06400b --- /dev/null +++ b/wallet/tests/file.rs @@ -0,0 +1,181 @@ +// 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 a wallet file send/recieve +extern crate grin_chain as chain; +extern crate grin_core as core; +extern crate grin_keychain as keychain; +extern crate grin_store as store; +extern crate grin_util as util; +extern crate grin_wallet as wallet; +extern crate rand; +#[macro_use] +extern crate log; +extern crate chrono; +extern crate serde; +extern crate uuid; + +mod common; +use common::testclient::{LocalWalletClient, WalletProxy}; + +use std::fs; +use std::thread; +use std::time::Duration; + +use core::global; +use core::global::ChainTypes; +use keychain::ExtKeychain; +use wallet::{libwallet, FileWalletCommAdapter}; + +fn clean_output_dir(test_dir: &str) { + let _ = fs::remove_dir_all(test_dir); +} + +fn setup(test_dir: &str) { + util::init_test_logger(); + clean_output_dir(test_dir); + global::set_mining_mode(ChainTypes::AutomatedTesting); +} + +/// self send impl +fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { + setup(test_dir); + // Create a new proxy to simulate server and wallet responses + let mut wallet_proxy: WalletProxy = WalletProxy::new(test_dir); + let chain = wallet_proxy.chain.clone(); + + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); + + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + let wallet2 = common::create_wallet(&format!("{}/wallet2", test_dir), client2.clone()); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + + // Set the wallet proxy listener running + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + // few values to keep things shorter + let reward = core::consensus::REWARD; + + // add some accounts + wallet::controller::owner_single_use(wallet1.clone(), |api| { + api.new_account_path("mining")?; + api.new_account_path("listener")?; + Ok(()) + })?; + + // add some accounts + wallet::controller::owner_single_use(wallet2.clone(), |api| { + api.new_account_path("account1")?; + api.new_account_path("account2")?; + Ok(()) + })?; + + // Get some mining done + { + let mut w = wallet1.lock(); + w.set_parent_key_id_by_name("mining")?; + } + let mut bh = 10u64; + let _ = common::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize); + + let send_file = format!("{}/part_tx_1.tx", test_dir); + let receive_file = format!("{}/part_tx_2.tx", test_dir); + + // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) + wallet::controller::owner_single_use(wallet1.clone(), |api| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let (mut slate, lock_fn) = api.initiate_tx( + Some("mining"), + reward * 2, // amount + 2, // minimum confirmations + 500, // max outputs + 1, // num change outputs + true, // select all outputs + //"mining", + //"listener", + )?; + /// output tx file + let file_adapter = FileWalletCommAdapter::new(); + file_adapter.send_tx_async(&send_file, &mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; + Ok(()) + })?; + + // Get some mining done + { + let mut w = wallet2.lock(); + w.set_parent_key_id_by_name("account1")?; + } + + // wallet 2 receives file, completes, sends file back + wallet::controller::foreign_single_use(wallet2.clone(), |api| { + let adapter = FileWalletCommAdapter::new(); + let mut slate = adapter.receive_tx_async(&send_file)?; + api.receive_tx(&mut slate, None)?; + adapter.send_tx_async(&receive_file, &mut slate)?; + Ok(()) + })?; + + // wallet 1 finalises and posts + wallet::controller::owner_single_use(wallet1.clone(), |api| { + let adapter = FileWalletCommAdapter::new(); + let mut slate = adapter.receive_tx_async(&receive_file)?; + api.finalize_tx(&mut slate)?; + api.post_tx(&slate, false); + bh += 1; + Ok(()) + })?; + + let _ = common::award_blocks_to_wallet(&chain, wallet1.clone(), 3); + bh += 3; + + // Check total in mining account + wallet::controller::owner_single_use(wallet1.clone(), |api| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + })?; + + // Check total in 'wallet 2' account + 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.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); + Ok(()) + })?; + + // let logging finish + thread::sleep(Duration::from_millis(200)); + Ok(()) +} + +#[test] +fn wallet_file_exchange() { + let test_dir = "test_output/file_exchange"; + if let Err(e) = file_exchange_test_impl(test_dir) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } +} diff --git a/wallet/tests/restore.rs b/wallet/tests/restore.rs index b9b36a994..2089271de 100644 --- a/wallet/tests/restore.rs +++ b/wallet/tests/restore.rs @@ -56,11 +56,10 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err let dest_seed = format!("{}/wallet.seed", dest_dir); fs::copy(source_seed, dest_seed)?; - let mut wallet_proxy: WalletProxy = - WalletProxy::new(base_dir); + let mut wallet_proxy: WalletProxy = WalletProxy::new(base_dir); let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone()); - let wallet = common::create_wallet(&dest_dir, client.clone(), client.clone()); + let wallet = common::create_wallet(&dest_dir, client.clone()); wallet_proxy.add_wallet(wallet_dir, client.get_send_instance(), wallet.clone()); @@ -90,11 +89,10 @@ fn compare_wallet_restore( let source_dir = format!("{}/{}", base_dir, wallet_dir); let dest_dir = format!("{}/{}", base_dir, restore_name); - let mut wallet_proxy: WalletProxy = - WalletProxy::new(base_dir); + let mut wallet_proxy: WalletProxy = WalletProxy::new(base_dir); let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone()); - let wallet_source = common::create_wallet(&source_dir, client.clone(), client.clone()); + let wallet_source = common::create_wallet(&source_dir, client.clone()); wallet_proxy.add_wallet( &wallet_dir, client.get_send_instance(), @@ -102,7 +100,7 @@ fn compare_wallet_restore( ); let client = LocalWalletClient::new(&restore_name, wallet_proxy.tx.clone()); - let wallet_dest = common::create_wallet(&dest_dir, client.clone(), client.clone()); + let wallet_dest = common::create_wallet(&dest_dir, client.clone()); wallet_proxy.add_wallet( &restore_name, client.get_send_instance(), @@ -184,28 +182,19 @@ fn compare_wallet_restore( fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { setup(test_dir); // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy: WalletProxy = - WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy = 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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet1", client.get_send_instance(), wallet1.clone()); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + wallet_proxy.add_wallet("wallet1", client1.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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet2", client.get_send_instance(), wallet2.clone()); + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + let wallet2 = common::create_wallet(&format!("{}/wallet2", test_dir), client2.clone()); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); // wallet 2 will use another account wallet::controller::owner_single_use(wallet2.clone(), |api| { @@ -221,13 +210,9 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { } // Another wallet - let client = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone()); - let wallet3 = common::create_wallet( - &format!("{}/wallet3", test_dir), - client.clone(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet3", client.get_send_instance(), wallet3.clone()); + let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone()); + let wallet3 = common::create_wallet(&format!("{}/wallet3", test_dir), client3.clone()); + wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone()); // Set the wallet proxy listener running thread::spawn(move || { @@ -245,14 +230,16 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( - amount, // amount - 2, // minimum confirmations - "wallet2", // dest - 500, // max outputs - 1, // num change outputs - true, // select all outputs + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount, // amount + 2, // minimum confirmations + 500, // max outputs + 1, // num change outputs + true, // select all outputs )?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.post_tx(&slate, false)?; Ok(()) })?; @@ -263,14 +250,17 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Send some to wallet 3 wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount * 2, // amount 2, // minimum confirmations - "wallet3", // dest 500, // max outputs 1, // num change outputs true, // select all outputs )?; + slate = client1.send_tx_slate_direct("wallet3", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.post_tx(&slate, false)?; Ok(()) })?; @@ -281,14 +271,17 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Wallet3 to wallet 2 wallet::controller::owner_single_use(wallet3.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount * 3, // amount 2, // minimum confirmations - "wallet2", // dest 500, // max outputs 1, // num change outputs true, // select all outputs )?; + slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.post_tx(&slate, false)?; Ok(()) })?; @@ -305,14 +298,17 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Wallet3 to wallet 2 again (to another account) wallet::controller::owner_single_use(wallet3.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount * 3, // amount 2, // minimum confirmations - "wallet2", // dest 500, // max outputs 1, // num change outputs true, // select all outputs )?; + slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.post_tx(&slate, false)?; Ok(()) })?; diff --git a/wallet/tests/self_send.rs b/wallet/tests/self_send.rs index 4e1cca790..3df70e52a 100644 --- a/wallet/tests/self_send.rs +++ b/wallet/tests/self_send.rs @@ -51,28 +51,14 @@ fn setup(test_dir: &str) { fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { setup(test_dir); // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy: WalletProxy = - WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy = 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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet1", client.get_send_instance(), wallet1.clone()); - - // define recipient wallet, add to proxy - let wallet2 = common::create_wallet( - &format!("{}/wallet2", test_dir), - client.clone(), - client.clone(), - ); - let client = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - wallet_proxy.add_wallet("wallet2", client.get_send_instance(), wallet2.clone()); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); // Set the wallet proxy listener running thread::spawn(move || { @@ -91,18 +77,6 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { Ok(()) })?; - // add account to wallet 2 - wallet::controller::owner_single_use(wallet2.clone(), |api| { - api.new_account_path("listener")?; - Ok(()) - })?; - - // Default wallet 2 to listen on that account - { - let mut w = wallet2.lock(); - w.set_parent_key_id_by_name("listener")?; - } - // Get some mining done { let mut w = wallet1.lock(); @@ -118,16 +92,24 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.last_confirmed_height, bh); assert_eq!(wallet1_info.total, bh * reward); // send to send - let slate = api.issue_self_tx( + let (mut slate, lock_fn) = api.initiate_tx( + Some("mining"), reward * 2, // amount 2, // minimum confirmations 500, // max outputs 1, // num change outputs true, // select all outputs - "mining", - "listener", + //"mining", + //"listener", )?; - api.post_tx(&slate, false)?; //mines a block + // Send directly to self + wallet::controller::foreign_single_use(wallet1.clone(), |api| { + api.receive_tx(&mut slate, Some("listener"))?; + Ok(()) + })?; + api.finalize_tx(&mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; + api.post_tx(&slate, false)?; // mines a block bh += 1; Ok(()) })?; @@ -163,7 +145,7 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { } #[test] -fn wallet_stress() { +fn wallet_self_send() { let test_dir = "test_output/self_send"; if let Err(e) = self_send_test_impl(test_dir) { panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); diff --git a/wallet/tests/transaction.rs b/wallet/tests/transaction.rs index 1c73d172d..1e3981198 100644 --- a/wallet/tests/transaction.rs +++ b/wallet/tests/transaction.rs @@ -49,34 +49,25 @@ fn setup(test_dir: &str) { global::set_mining_mode(ChainTypes::AutomatedTesting); } -/// Exercises the Transaction API fully with a test WalletToNodeClient operating +/// Exercises the Transaction API fully with a test NodeClient operating /// directly on a chain instance /// Callable with any type of wallet fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { setup(test_dir); // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy: WalletProxy = - WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy = 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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet1", client.get_send_instance(), wallet1.clone()); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); // define recipient wallet, add to proxy - let wallet2 = common::create_wallet( - &format!("{}/wallet2", test_dir), - client.clone(), - client.clone(), - ); - let client = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - wallet_proxy.add_wallet("wallet2", client.get_send_instance(), wallet2.clone()); + let wallet2 = common::create_wallet(&format!("{}/wallet2", test_dir), client2.clone()); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); // Set the wallet proxy listener running thread::spawn(move || { @@ -113,14 +104,16 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( - amount, // amount - 2, // minimum confirmations - "wallet2", // dest - 500, // max outputs - 1, // num change outputs - true, // select all outputs + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount, // amount + 2, // minimum confirmations + 500, // max outputs + 1, // num change outputs + true, // select all outputs )?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; Ok(()) })?; @@ -244,14 +237,17 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { // the stored transaction instead wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount * 2, // amount 2, // minimum confirmations - "wallet2", // dest 500, // max outputs 1, // num change outputs true, // select all outputs )?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; Ok(()) })?; @@ -305,28 +301,19 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> { setup(test_dir); // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy: WalletProxy = - WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy = 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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet1", client.get_send_instance(), wallet1.clone()); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + wallet_proxy.add_wallet("wallet1", client1.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(), - client.clone(), - ); - wallet_proxy.add_wallet("wallet2", client.get_send_instance(), wallet2.clone()); + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + let wallet2 = common::create_wallet(&format!("{}/wallet2", test_dir), client2.clone()); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); // Set the wallet proxy listener running thread::spawn(move || { @@ -345,14 +332,16 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - slate = sender_api.issue_send_tx( - amount, // amount - 2, // minimum confirmations - "wallet2", // dest - 500, // max outputs - 1, // num change outputs - true, // select all outputs + let (slate_i, lock_fn) = sender_api.initiate_tx( + None, amount, // amount + 2, // minimum confirmations + 500, // max outputs + 1, // num change outputs + true, // select all outputs )?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.finalize_tx(&mut slate)?; + sender_api.tx_lock_outputs(&slate, lock_fn)?; Ok(()) })?;