diff --git a/Cargo.lock b/Cargo.lock index 3767f38f..15eb5e2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -756,19 +756,19 @@ dependencies = [ [[package]] name = "grin_wallet" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "built 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_wallet_api 2.0.1-beta.1", - "grin_wallet_config 2.0.1-beta.1", - "grin_wallet_controller 2.0.1-beta.1", - "grin_wallet_impls 2.0.1-beta.1", - "grin_wallet_libwallet 2.0.1-beta.1", - "grin_wallet_util 2.0.1-beta.1", + "grin_wallet_api 2.1.0-beta.1", + "grin_wallet_config 2.1.0-beta.1", + "grin_wallet_controller 2.1.0-beta.1", + "grin_wallet_impls 2.1.0-beta.1", + "grin_wallet_libwallet 2.1.0-beta.1", + "grin_wallet_util 2.1.0-beta.1", "linefeed 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -778,16 +778,16 @@ dependencies = [ [[package]] name = "grin_wallet_api" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "easy-jsonrpc 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_wallet_config 2.0.1-beta.1", - "grin_wallet_impls 2.0.1-beta.1", - "grin_wallet_libwallet 2.0.1-beta.1", - "grin_wallet_util 2.0.1-beta.1", + "grin_wallet_config 2.1.0-beta.1", + "grin_wallet_impls 2.1.0-beta.1", + "grin_wallet_libwallet 2.1.0-beta.1", + "grin_wallet_util 2.1.0-beta.1", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -796,10 +796,10 @@ dependencies = [ [[package]] name = "grin_wallet_config" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_wallet_util 2.0.1-beta.1", + "grin_wallet_util 2.1.0-beta.1", "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", @@ -809,18 +809,18 @@ dependencies = [ [[package]] name = "grin_wallet_controller" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "easy-jsonrpc 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_wallet_api 2.0.1-beta.1", - "grin_wallet_config 2.0.1-beta.1", - "grin_wallet_impls 2.0.1-beta.1", - "grin_wallet_libwallet 2.0.1-beta.1", - "grin_wallet_util 2.0.1-beta.1", + "grin_wallet_api 2.1.0-beta.1", + "grin_wallet_config 2.1.0-beta.1", + "grin_wallet_impls 2.1.0-beta.1", + "grin_wallet_libwallet 2.1.0-beta.1", + "grin_wallet_util 2.1.0-beta.1", "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -840,16 +840,16 @@ dependencies = [ [[package]] name = "grin_wallet_impls" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_wallet_config 2.0.1-beta.1", - "grin_wallet_libwallet 2.0.1-beta.1", - "grin_wallet_util 2.0.1-beta.1", + "grin_wallet_config 2.1.0-beta.1", + "grin_wallet_libwallet 2.1.0-beta.1", + "grin_wallet_util 2.1.0-beta.1", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -859,19 +859,20 @@ dependencies = [ "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-retry 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "grin_wallet_libwallet" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_wallet_config 2.0.1-beta.1", - "grin_wallet_util 2.0.1-beta.1", + "grin_wallet_config 2.1.0-beta.1", + "grin_wallet_util 2.1.0-beta.1", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -885,7 +886,7 @@ dependencies = [ [[package]] name = "grin_wallet_util" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" dependencies = [ "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "grin_api 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)", diff --git a/Cargo.toml b/Cargo.toml index aeb98510..e26ada10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -30,13 +30,13 @@ log = "0.4" linefeed = "0.5" semver = "0.9" -grin_wallet_api = { path = "./api", version = "2.0.1-beta.1" } -grin_wallet_impls = { path = "./impls", version = "2.0.1-beta.1" } -grin_wallet_libwallet = { path = "./libwallet", version = "2.0.1-beta.1" } -grin_wallet_controller = { path = "./controller", version = "2.0.1-beta.1" } -grin_wallet_config = { path = "./config", version = "2.0.1-beta.1" } +grin_wallet_api = { path = "./api", version = "2.1.0-beta.1" } +grin_wallet_impls = { path = "./impls", version = "2.1.0-beta.1" } +grin_wallet_libwallet = { path = "./libwallet", version = "2.1.0-beta.1" } +grin_wallet_controller = { path = "./controller", version = "2.1.0-beta.1" } +grin_wallet_config = { path = "./config", version = "2.1.0-beta.1" } -grin_wallet_util = { path = "./util", version = "2.0.1-beta.1" } +grin_wallet_util = { path = "./util", version = "2.1.0-beta.1" } [build-dependencies] built = "0.3" diff --git a/api/Cargo.toml b/api/Cargo.toml index fdebf65b..4829a424 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_api" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Grin Wallet API" license = "Apache-2.0" @@ -18,10 +18,10 @@ serde_json = "1" easy-jsonrpc = "0.5.1" chrono = { version = "0.4.4", features = ["serde"] } -grin_wallet_libwallet = { path = "../libwallet", version = "2.0.1-beta.1" } -grin_wallet_config = { path = "../config", version = "2.0.1-beta.1" } -grin_wallet_impls = { path = "../impls", version = "2.0.1-beta.1" } -grin_wallet_util = { path = "../util", version = "2.0.1-beta.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "2.1.0-beta.1" } +grin_wallet_config = { path = "../config", version = "2.1.0-beta.1" } +grin_wallet_impls = { path = "../impls", version = "2.1.0-beta.1" } +grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" } [dev-dependencies] serde_json = "1" diff --git a/api/src/foreign.rs b/api/src/foreign.rs index 1c64590e..aa6ffc5e 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -17,10 +17,10 @@ use crate::keychain::Keychain; use crate::libwallet::api_impl::foreign; use crate::libwallet::{ - BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletBackend, + BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst, + WalletLCProvider, }; use crate::util::Mutex; -use std::marker::PhantomData; use std::sync::Arc; /// ForeignAPI Middleware Check callback @@ -55,30 +55,25 @@ pub enum ForeignCheckMiddlewareFn { /// its operation, then 'close' the wallet (unloading references to the keychain and master /// seed). -pub struct Foreign +pub struct Foreign<'a, L, C, K> where - W: WalletBackend, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { - /// Wallet, contains its keychain (TODO: Split these up into 2 traits - /// perhaps) - pub wallet: Arc>, + /// Wallet instance + pub wallet_inst: Arc>>>, /// Flag to normalize some output during testing. Can mostly be ignored. pub doctest_mode: bool, - /// phantom - phantom: PhantomData, - /// phantom - phantom_c: PhantomData, /// foreign check middleware middleware: Option, } -impl<'a, W: ?Sized, C, K> Foreign +impl<'a, L, C, K> Foreign<'a, L, C, K> where - W: WalletBackend, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { /// Create a new API instance with the given wallet instance. All subsequent /// API calls will operate on this instance of the wallet. @@ -110,12 +105,12 @@ where /// use tempfile::tempdir; /// /// use std::sync::Arc; - /// use util::Mutex; + /// use util::{Mutex, ZeroingString}; /// /// use api::Foreign; /// use config::WalletConfig; - /// use impls::{HTTPNodeClient, LMDBBackend}; - /// use libwallet::WalletBackend; + /// use impls::{DefaultWalletImpl, DefaultLCProvider, HTTPNodeClient}; + /// use libwallet::WalletInst; /// /// let mut wallet_config = WalletConfig::default(); /// # let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); @@ -128,24 +123,42 @@ where /// /// // A NodeClient must first be created to handle communication between /// // the wallet and the node. - /// /// let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); - /// let mut wallet:Arc>> = - /// Arc::new(Mutex::new( - /// LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() - /// )); + /// + /// // impls::DefaultWalletImpl is provided for convenience in instantiating the wallet + /// // It contains the LMDBBackend, DefaultLCProvider (lifecycle) and ExtKeychain used + /// // by the reference wallet implementation. + /// // These traits can be replaced with alternative implementations if desired + /// + /// let mut wallet = Box::new(DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap()) + /// as Box, HTTPNodeClient, ExtKeychain>>; + /// + /// // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc... + /// let lc = wallet.lc_provider().unwrap(); + /// + /// // The top level wallet directory should be set manually (in the reference implementation, + /// // this is provided in the WalletConfig) + /// lc.set_wallet_directory(&wallet_config.data_file_dir); + /// + /// // Wallet must be opened with the password (TBD) + /// let pw = ZeroingString::from("wallet_password"); + /// lc.open_wallet(None, pw); + /// + /// // All wallet functions operate on an Arc::Mutex to allow multithreading where needed + /// let mut wallet = Arc::new(Mutex::new(wallet)); /// /// let api_foreign = Foreign::new(wallet.clone(), None); /// // .. perform wallet operations /// /// ``` - pub fn new(wallet_in: Arc>, middleware: Option) -> Self { + pub fn new( + wallet_inst: Arc>>>, + middleware: Option, + ) -> Self { Foreign { - wallet: wallet_in, + wallet_inst, doctest_mode: false, - phantom: PhantomData, - phantom_c: PhantomData, middleware, } } @@ -168,7 +181,8 @@ where pub fn check_version(&self) -> Result { if let Some(m) = self.middleware.as_ref() { - let mut w = self.wallet.lock(); + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; m( ForeignCheckMiddlewareFn::CheckVersion, w.w2n_client().get_version_info(), @@ -229,7 +243,8 @@ where /// ``` pub fn build_coinbase(&self, block_fees: &BlockFees) -> Result { - let mut w = self.wallet.lock(); + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { m( ForeignCheckMiddlewareFn::BuildCoinbase, @@ -237,10 +252,7 @@ where None, )?; } - w.open_with_credentials()?; - let res = foreign::build_coinbase(&mut *w, block_fees, self.doctest_mode); - w.close()?; - res + foreign::build_coinbase(&mut **w, block_fees, self.doctest_mode) } /// Verifies all messages in the slate match their public keys. @@ -283,7 +295,8 @@ where pub fn verify_slate_messages(&self, slate: &Slate) -> Result<(), Error> { if let Some(m) = self.middleware.as_ref() { - let mut w = self.wallet.lock(); + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; m( ForeignCheckMiddlewareFn::VerifySlateMessages, w.w2n_client().get_version_info(), @@ -355,7 +368,8 @@ where dest_acct_name: Option<&str>, message: Option, ) -> Result { - let mut w = self.wallet.lock(); + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { m( ForeignCheckMiddlewareFn::ReceiveTx, @@ -363,10 +377,7 @@ where Some(slate), )?; } - w.open_with_credentials()?; - let res = foreign::receive_tx(&mut *w, slate, dest_acct_name, message, self.doctest_mode); - w.close()?; - res + foreign::receive_tx(&mut **w, slate, dest_acct_name, message, self.doctest_mode) } /// Finalizes an invoice transaction initiated by this wallet's Owner api. @@ -417,7 +428,8 @@ where /// ``` pub fn finalize_invoice_tx(&self, slate: &Slate) -> Result { - let mut w = self.wallet.lock(); + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { m( ForeignCheckMiddlewareFn::FinalizeInvoiceTx, @@ -425,10 +437,7 @@ where Some(slate), )?; } - w.open_with_credentials()?; - let res = foreign::finalize_invoice_tx(&mut *w, slate); - w.close()?; - res + foreign::finalize_invoice_tx(&mut **w, slate) } } @@ -447,12 +456,12 @@ macro_rules! doctest_helper_setup_doc_env_foreign { use tempfile::tempdir; use std::sync::Arc; - use util::Mutex; + use util::{Mutex, ZeroingString}; use api::{Foreign, Owner}; use config::WalletConfig; - use impls::{HTTPNodeClient, LMDBBackend, WalletSeed}; - use libwallet::{BlockFees, IssueInvoiceTxArgs, Slate, WalletBackend}; + use impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; + use libwallet::{BlockFees, IssueInvoiceTxArgs, Slate, WalletInst}; let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); let dir = dir @@ -462,11 +471,23 @@ macro_rules! doctest_helper_setup_doc_env_foreign { .unwrap(); let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = dir.to_owned(); - let pw = ""; + let pw = ZeroingString::from(""); let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); - let mut $wallet: Arc>> = Arc::new( - Mutex::new(LMDBBackend::new(wallet_config.clone(), pw, node_client).unwrap()), - ); + let mut wallet = Box::new( + DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), + ) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + HTTPNodeClient, + ExtKeychain, + >, + >; + let lc = wallet.lc_provider().unwrap(); + lc.set_wallet_directory(&wallet_config.data_file_dir); + lc.open_wallet(None, pw); + let mut $wallet = Arc::new(Mutex::new(wallet)); }; } diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index b95f6401..11d865ce 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -17,7 +17,7 @@ use crate::keychain::Keychain; use crate::libwallet::{ self, BlockFees, CbData, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, - NodeVersionInfo, Slate, VersionInfo, VersionedSlate, WalletBackend, + NodeVersionInfo, Slate, VersionInfo, VersionedSlate, WalletLCProvider, }; use crate::{Foreign, ForeignCheckMiddlewareFn}; use easy_jsonrpc; @@ -515,11 +515,11 @@ pub trait ForeignRpc { fn finalize_invoice_tx(&self, slate: &Slate) -> Result; } -impl ForeignRpc for Foreign +impl<'a, L, C, K> ForeignRpc for Foreign<'a, L, C, K> where - W: WalletBackend, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { fn check_version(&self) -> Result { Foreign::check_version(self).map_err(|e| e.kind()) @@ -577,13 +577,17 @@ pub fn run_doctest_foreign( ) -> Result, String> { use easy_jsonrpc::Handler; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; - use grin_wallet_libwallet::api_impl; + use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; + use grin_wallet_libwallet::{api_impl, WalletInst}; use grin_wallet_util::grin_keychain::ExtKeychain; use crate::core::global; use crate::core::global::ChainTypes; use grin_wallet_util::grin_util as util; + use std::sync::Arc; + use util::Mutex; + use std::fs; use std::thread; @@ -591,29 +595,60 @@ pub fn run_doctest_foreign( let _ = fs::remove_dir_all(test_dir); global::set_mining_mode(ChainTypes::AutomatedTesting); - let mut wallet_proxy: WalletProxy = WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy< + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + > = WalletProxy::new(test_dir); let chain = wallet_proxy.chain.clone(); - let rec_phrase_1 = + let rec_phrase_1 = util::ZeroingString::from( "fat twenty mean degree forget shell check candy immense awful \ - flame next during february bulb bike sun wink theory day kiwi embrace peace lunch"; - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = test_framework::create_wallet( - &format!("{}/wallet1", test_dir), - client1.clone(), - Some(rec_phrase_1), + flame next during february bulb bike sun wink theory day kiwi embrace peace lunch", ); + let empty_string = util::ZeroingString::from(""); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let mut wallet1 = + Box::new(DefaultWalletImpl::::new(client1.clone()).unwrap()) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet1.lc_provider().unwrap(); + lc.set_wallet_directory(&format!("{}/wallet1", test_dir)); + lc.create_wallet(None, Some(rec_phrase_1), 32, empty_string.clone()) + .unwrap(); + lc.open_wallet(None, empty_string.clone()).unwrap(); + let wallet1 = Arc::new(Mutex::new(wallet1)); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - let rec_phrase_2 = + let rec_phrase_2 = util::ZeroingString::from( "hour kingdom ripple lunch razor inquiry coyote clay stamp mean \ - sell finish magic kid tiny wage stand panther inside settle feed song hole exile"; - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = test_framework::create_wallet( - &format!("{}/wallet2", test_dir), - client2.clone(), - Some(rec_phrase_2), + sell finish magic kid tiny wage stand panther inside settle feed song hole exile", ); + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + let mut wallet2 = + Box::new(DefaultWalletImpl::::new(client2.clone()).unwrap()) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet2.lc_provider().unwrap(); + lc.set_wallet_directory(&format!("{}/wallet2", test_dir)); + lc.create_wallet(None, Some(rec_phrase_2), 32, empty_string.clone()) + .unwrap(); + lc.open_wallet(None, empty_string.clone()).unwrap(); + let wallet2 = Arc::new(Mutex::new(wallet2)); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); // Set the wallet proxy listener running @@ -627,28 +662,27 @@ pub fn run_doctest_foreign( for _ in 0..blocks_to_mine { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 1 as usize, false); //update local outputs after each block, so transaction IDs stay consistent - let mut w = wallet1.lock(); - w.open_with_credentials().unwrap(); + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let (wallet_refreshed, _) = - api_impl::owner::retrieve_summary_info(&mut *w, true, 1).unwrap(); + api_impl::owner::retrieve_summary_info(&mut **w, true, 1).unwrap(); assert!(wallet_refreshed); - w.close().unwrap(); } if init_invoice_tx { let amount = 60_000_000_000; let mut slate = { - let mut w = wallet2.lock(); - w.open_with_credentials().unwrap(); + let mut w_lock = wallet2.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let args = IssueInvoiceTxArgs { amount, ..Default::default() }; - api_impl::owner::issue_invoice_tx(&mut *w, args, true).unwrap() + api_impl::owner::issue_invoice_tx(&mut **w, args, true).unwrap() }; slate = { - let mut w = wallet1.lock(); - w.open_with_credentials().unwrap(); + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let args = InitTxArgs { src_acct_name: None, amount: slate.amount, @@ -658,7 +692,7 @@ pub fn run_doctest_foreign( selection_strategy_is_use_all: true, ..Default::default() }; - api_impl::owner::process_invoice_tx(&mut *w, &slate, args, true).unwrap() + api_impl::owner::process_invoice_tx(&mut **w, &slate, args, true).unwrap() }; println!("INIT INVOICE SLATE"); // Spit out slate for input to finalize_invoice_tx @@ -667,8 +701,8 @@ pub fn run_doctest_foreign( if init_tx { let amount = 60_000_000_000; - let mut w = wallet1.lock(); - w.open_with_credentials().unwrap(); + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let args = InitTxArgs { src_acct_name: None, amount, @@ -678,15 +712,15 @@ pub fn run_doctest_foreign( selection_strategy_is_use_all: true, ..Default::default() }; - let slate = api_impl::owner::init_send_tx(&mut *w, args, true).unwrap(); + let slate = api_impl::owner::init_send_tx(&mut **w, args, true).unwrap(); println!("INIT SLATE"); // Spit out slate for input to finalize_tx println!("{}", serde_json::to_string_pretty(&slate).unwrap()); } let mut api_foreign = match init_invoice_tx { - false => Foreign::new(wallet1.clone(), Some(test_check_middleware)), - true => Foreign::new(wallet2.clone(), Some(test_check_middleware)), + false => Foreign::new(wallet1, Some(test_check_middleware)), + true => Foreign::new(wallet2, Some(test_check_middleware)), }; api_foreign.doctest_mode = true; let foreign_api = &api_foreign as &dyn ForeignRpc; diff --git a/api/src/lib.rs b/api/src/lib.rs index d38ae602..3a798b3e 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -36,8 +36,10 @@ extern crate log; mod foreign; mod foreign_rpc; + mod owner; mod owner_rpc; + pub use crate::foreign::{Foreign, ForeignCheckMiddleware, ForeignCheckMiddlewareFn}; pub use crate::foreign_rpc::ForeignRpc; pub use crate::owner::Owner; diff --git a/api/src/owner.rs b/api/src/owner.rs index bc6d9661..85a310fa 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -14,20 +14,20 @@ //! Owner API External Definition -use crate::util::Mutex; use chrono::prelude::*; -use std::marker::PhantomData; -use std::sync::Arc; use uuid::Uuid; use crate::core::core::Transaction; -use crate::impls::{HTTPWalletCommAdapter, KeybaseWalletCommAdapter}; +use crate::impls::create_sender; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner; use crate::libwallet::{ AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, - NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletBackend, WalletInfo, + NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletInfo, WalletInst, + WalletLCProvider, }; +use crate::util::Mutex; +use std::sync::Arc; /// Main interface into all wallet API functions. /// Wallet APIs are split into two seperate blocks of functionality @@ -43,24 +43,21 @@ use crate::libwallet::{ /// its operation, then 'close' the wallet (unloading references to the keychain and master /// seed). -pub struct Owner +pub struct Owner<'a, L, C, K> where - W: WalletBackend, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { - /// A reference-counted mutex to an implementation of the - /// [`WalletBackend`](../grin_wallet_libwallet/types/trait.WalletBackend.html) trait. - pub wallet: Arc>, + /// contain all methods to manage the wallet + pub wallet_inst: Arc>>>, /// Flag to normalize some output during testing. Can mostly be ignored. pub doctest_mode: bool, - phantom: PhantomData, - phantom_c: PhantomData, } -impl Owner +impl<'a, L, C, K> Owner<'a, L, C, K> where - W: WalletBackend, + L: WalletLCProvider<'a, C, K>, C: NodeClient, K: Keychain, { @@ -92,12 +89,12 @@ where /// use tempfile::tempdir; /// /// use std::sync::Arc; - /// use util::Mutex; + /// use util::{Mutex, ZeroingString}; /// /// use api::Owner; /// use config::WalletConfig; - /// use impls::{HTTPNodeClient, LMDBBackend}; - /// use libwallet::WalletBackend; + /// use impls::{DefaultWalletImpl, DefaultLCProvider, HTTPNodeClient}; + /// use libwallet::WalletInst; /// /// let mut wallet_config = WalletConfig::default(); /// # let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); @@ -110,24 +107,39 @@ where /// /// // A NodeClient must first be created to handle communication between /// // the wallet and the node. - /// /// let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); - /// let mut wallet:Arc>> = - /// Arc::new(Mutex::new( - /// LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() - /// )); + /// + /// // impls::DefaultWalletImpl is provided for convenience in instantiating the wallet + /// // It contains the LMDBBackend, DefaultLCProvider (lifecycle) and ExtKeychain used + /// // by the reference wallet implementation. + /// // These traits can be replaced with alternative implementations if desired + /// + /// let mut wallet = Box::new(DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap()) + /// as Box, HTTPNodeClient, ExtKeychain>>; + /// + /// // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc... + /// let lc = wallet.lc_provider().unwrap(); + /// + /// // The top level wallet directory should be set manually (in the reference implementation, + /// // this is provided in the WalletConfig) + /// lc.set_wallet_directory(&wallet_config.data_file_dir); + /// + /// // Wallet must be opened with the password (TBD) + /// let pw = ZeroingString::from("wallet_password"); + /// lc.open_wallet(None, pw); + /// + /// // All wallet functions operate on an Arc::Mutex to allow multithreading where needed + /// let mut wallet = Arc::new(Mutex::new(wallet)); /// /// let api_owner = Owner::new(wallet.clone()); /// // .. perform wallet operations /// /// ``` - pub fn new(wallet_in: Arc>) -> Self { + pub fn new(wallet_inst: Arc>>>) -> Self { Owner { - wallet: wallet_in, + wallet_inst, doctest_mode: false, - phantom: PhantomData, - phantom_c: PhantomData, } } @@ -160,8 +172,9 @@ where /// ``` pub fn accounts(&self) -> Result, Error> { - let mut w = self.wallet.lock(); - owner::accounts(&mut *w) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::accounts(&mut **w) } /// Creates a new 'account', which is a mapping of a user-specified @@ -202,8 +215,9 @@ where /// ``` pub fn create_account_path(&self, label: &str) -> Result { - let mut w = self.wallet.lock(); - owner::create_account_path(&mut *w, label) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::create_account_path(&mut **w, label) } /// Sets the wallet's currently active account. This sets the @@ -243,8 +257,9 @@ where /// ``` pub fn set_active_account(&self, label: &str) -> Result<(), Error> { - let mut w = self.wallet.lock(); - owner::set_active_account(&mut *w, label) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::set_active_account(&mut **w, label) } /// Returns a list of outputs from the active account in the wallet. @@ -295,11 +310,9 @@ where refresh_from_node: bool, tx_id: Option, ) -> Result<(bool, Vec), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::retrieve_outputs(&mut *w, include_spent, refresh_from_node, tx_id); - w.close()?; - res + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::retrieve_outputs(&mut **w, include_spent, refresh_from_node, tx_id) } /// Returns a list of [Transaction Log Entries](../grin_wallet_libwallet/types/struct.TxLogEntry.html) @@ -348,9 +361,9 @@ where tx_id: Option, tx_slate_id: Option, ) -> Result<(bool, Vec), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let mut res = owner::retrieve_txs(&mut *w, refresh_from_node, tx_id, tx_slate_id)?; + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let mut res = owner::retrieve_txs(&mut **w, refresh_from_node, tx_id, tx_slate_id)?; if self.doctest_mode { res.1 = res .1 @@ -362,7 +375,6 @@ where }) .collect(); } - w.close()?; Ok(res) } @@ -406,11 +418,9 @@ where refresh_from_node: bool, minimum_confirmations: u64, ) -> Result<(bool, WalletInfo), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::retrieve_summary_info(&mut *w, refresh_from_node, minimum_confirmations); - w.close()?; - res + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::retrieve_summary_info(&mut **w, refresh_from_node, minimum_confirmations) } /// Initiates a new transaction as the sender, creating a new @@ -489,30 +499,27 @@ where pub fn init_send_tx(&self, args: InitTxArgs) -> Result { let send_args = args.send_args.clone(); let mut slate = { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let slate = owner::init_send_tx(&mut *w, args, self.doctest_mode)?; - w.close()?; - slate + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::init_send_tx(&mut **w, args, self.doctest_mode)? }; // Helper functionality. If send arguments exist, attempt to send match send_args { Some(sa) => { + //TODO: in case of keybase, the response might take 60s and leave the service hanging match sa.method.as_ref() { - "http" => { - slate = HTTPWalletCommAdapter::new().send_tx_sync(&sa.dest, &slate)? - } - "keybase" => { - //TODO: in case of keybase, the response might take 60s and leave the service hanging - slate = KeybaseWalletCommAdapter::new().send_tx_sync(&sa.dest, &slate)?; - } + "http" | "keybase" => {} _ => { error!("unsupported payment method: {}", sa.method); return Err(ErrorKind::ClientCallback( "unsupported payment method".to_owned(), - ))?; + ) + .into()); } - } + }; + let comm_adapter = create_sender(&sa.method, &sa.dest) + .map_err(|e| ErrorKind::GenericError(format!("{}", e)))?; + slate = comm_adapter.send_tx(&slate)?; self.tx_lock_outputs(&slate, 0)?; let slate = match sa.finalize { true => self.finalize_tx(&slate)?, @@ -562,11 +569,9 @@ where /// } /// ``` pub fn issue_invoice_tx(&self, args: IssueInvoiceTxArgs) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let slate = owner::issue_invoice_tx(&mut *w, args, self.doctest_mode)?; - w.close()?; - Ok(slate) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::issue_invoice_tx(&mut **w, args, self.doctest_mode) } /// Processes an invoice tranaction created by another party, essentially @@ -623,11 +628,9 @@ where /// ``` pub fn process_invoice_tx(&self, slate: &Slate, args: InitTxArgs) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let slate = owner::process_invoice_tx(&mut *w, slate, args, self.doctest_mode)?; - w.close()?; - Ok(slate) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::process_invoice_tx(&mut **w, slate, args, self.doctest_mode) } /// Locks the outputs associated with the inputs to the transaction in the given @@ -683,11 +686,9 @@ where /// ``` pub fn tx_lock_outputs(&self, slate: &Slate, participant_id: usize) -> Result<(), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::tx_lock_outputs(&mut *w, slate, participant_id); - w.close()?; - res + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::tx_lock_outputs(&mut **w, slate, participant_id) } /// Finalizes a transaction, after all parties @@ -745,12 +746,9 @@ where /// ``` pub fn finalize_tx(&self, slate: &Slate) -> Result { - let mut w = self.wallet.lock(); - let mut slate = slate.clone(); - w.open_with_credentials()?; - slate = owner::finalize_tx(&mut *w, &slate)?; - w.close()?; - Ok(slate) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::finalize_tx(&mut **w, &slate) } /// Posts a completed transaction to the listening node for validation and inclusion in a block @@ -804,7 +802,8 @@ where pub fn post_tx(&self, tx: &Transaction, fluff: bool) -> Result<(), Error> { let client = { - let mut w = self.wallet.lock(); + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; w.w2n_client().clone() }; owner::post_tx(&client, tx, fluff) @@ -863,11 +862,9 @@ where /// ``` pub fn cancel_tx(&self, tx_id: Option, tx_slate_id: Option) -> Result<(), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::cancel_tx(&mut *w, tx_id, tx_slate_id); - w.close()?; - res + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::cancel_tx(&mut **w, tx_id, tx_slate_id) } /// Retrieves the stored transaction associated with a TxLogEntry. Can be used even after the @@ -903,8 +900,9 @@ where // TODO: Should be accepting an id, not an entire entry struct pub fn get_stored_tx(&self, tx_log_entry: &TxLogEntry) -> Result, Error> { - let w = self.wallet.lock(); - owner::get_stored_tx(&*w, tx_log_entry) + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::get_stored_tx(&**w, tx_log_entry) } /// Verifies all messages in the slate match their public keys. @@ -993,10 +991,9 @@ where /// } /// ``` pub fn restore(&self) -> Result<(), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::restore(&mut *w); - w.close()?; + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let res = owner::restore(&mut **w); res } @@ -1047,11 +1044,9 @@ where /// ``` pub fn check_repair(&self, delete_unconfirmed: bool) -> Result<(), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::check_repair(&mut *w, delete_unconfirmed); - w.close()?; - res + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::check_repair(&mut **w, delete_unconfirmed) } /// Retrieves the last known height known by the wallet. This is determined as follows: @@ -1092,11 +1087,9 @@ where /// ``` pub fn node_height(&self) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::node_height(&mut *w); - w.close()?; - res + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + owner::node_height(&mut **w) } } @@ -1115,12 +1108,12 @@ macro_rules! doctest_helper_setup_doc_env { use tempfile::tempdir; use std::sync::Arc; - use util::Mutex; + use util::{Mutex, ZeroingString}; - use api::Owner; + use api::{Foreign, Owner}; use config::WalletConfig; - use impls::{HTTPNodeClient, LMDBBackend, WalletSeed}; - use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, WalletBackend}; + use impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; + use libwallet::{BlockFees, InitTxArgs, IssueInvoiceTxArgs, Slate, WalletInst}; let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); let dir = dir @@ -1130,11 +1123,23 @@ macro_rules! doctest_helper_setup_doc_env { .unwrap(); let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = dir.to_owned(); - let pw = ""; + let pw = ZeroingString::from(""); let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); - let mut $wallet: Arc>> = Arc::new( - Mutex::new(LMDBBackend::new(wallet_config.clone(), pw, node_client).unwrap()), - ); + let mut wallet = Box::new( + DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), + ) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + HTTPNodeClient, + ExtKeychain, + >, + >; + let lc = wallet.lc_provider().unwrap(); + lc.set_wallet_directory(&wallet_config.data_file_dir); + lc.open_wallet(None, pw); + let mut $wallet = Arc::new(Mutex::new(wallet)); }; } diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index c8a69b11..05c28370 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -19,10 +19,12 @@ use crate::core::core::Transaction; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::{ AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, - OutputCommitMapping, Slate, TxLogEntry, WalletBackend, WalletInfo, + OutputCommitMapping, Slate, TxLogEntry, WalletInfo, WalletLCProvider, }; +use crate::util::Mutex; use crate::Owner; use easy_jsonrpc; +use std::sync::Arc; /// Public definition used to generate Owner jsonrpc api. /// * When running `grin-wallet owner_api` with defaults, the V2 api is available at @@ -1233,11 +1235,11 @@ pub trait OwnerRpc { fn node_height(&self) -> Result; } -impl OwnerRpc for Owner +impl<'a, L, C, K> OwnerRpc for Owner<'a, L, C, K> where - W: WalletBackend, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { fn accounts(&self) -> Result, ErrorKind> { Owner::accounts(self).map_err(|e| e.kind()) @@ -1338,7 +1340,8 @@ pub fn run_doctest_owner( ) -> Result, String> { use easy_jsonrpc::Handler; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; - use grin_wallet_libwallet::api_impl; + use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; + use grin_wallet_libwallet::{api_impl, WalletInst}; use grin_wallet_util::grin_keychain::ExtKeychain; use crate::core::global; @@ -1352,29 +1355,61 @@ pub fn run_doctest_owner( let _ = fs::remove_dir_all(test_dir); global::set_mining_mode(ChainTypes::AutomatedTesting); - let mut wallet_proxy: WalletProxy = WalletProxy::new(test_dir); + let mut wallet_proxy: WalletProxy< + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + > = WalletProxy::new(test_dir); let chain = wallet_proxy.chain.clone(); - let rec_phrase_1 = + let rec_phrase_1 = util::ZeroingString::from( "fat twenty mean degree forget shell check candy immense awful \ - flame next during february bulb bike sun wink theory day kiwi embrace peace lunch"; - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = test_framework::create_wallet( - &format!("{}/wallet1", test_dir), - client1.clone(), - Some(rec_phrase_1), + flame next during february bulb bike sun wink theory day kiwi embrace peace lunch", ); + let empty_string = util::ZeroingString::from(""); + + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + let mut wallet1 = + Box::new(DefaultWalletImpl::::new(client1.clone()).unwrap()) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet1.lc_provider().unwrap(); + lc.set_wallet_directory(&format!("{}/wallet1", test_dir)); + lc.create_wallet(None, Some(rec_phrase_1), 32, empty_string.clone()) + .unwrap(); + lc.open_wallet(None, empty_string.clone()).unwrap(); + let wallet1 = Arc::new(Mutex::new(wallet1)); + wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - let rec_phrase_2 = + let rec_phrase_2 = util::ZeroingString::from( "hour kingdom ripple lunch razor inquiry coyote clay stamp mean \ - sell finish magic kid tiny wage stand panther inside settle feed song hole exile"; - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = test_framework::create_wallet( - &format!("{}/wallet2", test_dir), - client2.clone(), - Some(rec_phrase_2), + sell finish magic kid tiny wage stand panther inside settle feed song hole exile", ); + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + let mut wallet2 = + Box::new(DefaultWalletImpl::::new(client2.clone()).unwrap()) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet2.lc_provider().unwrap(); + lc.set_wallet_directory(&format!("{}/wallet2", test_dir)); + lc.create_wallet(None, Some(rec_phrase_2), 32, empty_string.clone()) + .unwrap(); + lc.open_wallet(None, empty_string.clone()).unwrap(); + let wallet2 = Arc::new(Mutex::new(wallet2)); + wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); // Set the wallet proxy listener running @@ -1388,18 +1423,17 @@ pub fn run_doctest_owner( for _ in 0..blocks_to_mine { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 1 as usize, false); //update local outputs after each block, so transaction IDs stay consistent - let mut w = wallet1.lock(); - w.open_with_credentials().unwrap(); + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let (wallet_refreshed, _) = - api_impl::owner::retrieve_summary_info(&mut *w, true, 1).unwrap(); + api_impl::owner::retrieve_summary_info(&mut **w, true, 1).unwrap(); assert!(wallet_refreshed); - w.close().unwrap(); } if perform_tx { let amount = 60_000_000_000; - let mut w = wallet1.lock(); - w.open_with_credentials().unwrap(); + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let args = InitTxArgs { src_acct_name: None, amount, @@ -1409,27 +1443,26 @@ pub fn run_doctest_owner( selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api_impl::owner::init_send_tx(&mut *w, args, true).unwrap(); + let mut slate = api_impl::owner::init_send_tx(&mut **w, args, true).unwrap(); println!("INITIAL SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); { - let mut w2 = wallet2.lock(); - w2.open_with_credentials().unwrap(); - slate = api_impl::foreign::receive_tx(&mut *w2, &slate, None, None, true).unwrap(); + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + slate = api_impl::foreign::receive_tx(&mut **w2, &slate, None, None, true).unwrap(); w2.close().unwrap(); } // Spit out slate for input to finalize_tx if lock_tx { - api_impl::owner::tx_lock_outputs(&mut *w, &slate, 0).unwrap(); + api_impl::owner::tx_lock_outputs(&mut **w, &slate, 0).unwrap(); } println!("RECEIPIENT SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); if finalize_tx { - slate = api_impl::owner::finalize_tx(&mut *w, &slate).unwrap(); + slate = api_impl::owner::finalize_tx(&mut **w, &slate).unwrap(); error!("FINALIZED TX SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); } - w.close().unwrap(); } if perform_tx && lock_tx && finalize_tx { @@ -1437,7 +1470,7 @@ pub fn run_doctest_owner( let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3 as usize, false); } - let mut api_owner = Owner::new(wallet1.clone()); + let mut api_owner = Owner::new(wallet1); api_owner.doctest_mode = true; let owner_api = &api_owner as &dyn OwnerRpc; Ok(owner_api.handle_request(request).as_option()) diff --git a/config/Cargo.toml b/config/Cargo.toml index 7213c197..3ecec831 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_config" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Configuration for grin wallet , a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -16,7 +16,7 @@ serde_derive = "1" toml = "0.4" dirs = "1.0.3" -grin_wallet_util = { path = "../util", version = "2.0.1-beta.1" } +grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" } [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/config/src/config.rs b/config/src/config.rs index 9aa47b2b..27db0262 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -95,8 +95,14 @@ pub fn check_api_secret(api_secret_path: &PathBuf) -> Result<(), ConfigError> { } /// Check that the api secret file exists and is valid -fn check_api_secret_file(chain_type: &global::ChainTypes) -> Result<(), ConfigError> { - let grin_path = get_grin_path(chain_type)?; +fn check_api_secret_file( + chain_type: &global::ChainTypes, + data_path: Option, +) -> Result<(), ConfigError> { + let grin_path = match data_path { + Some(p) => p, + None => get_grin_path(chain_type)?, + }; let mut api_secret_path = grin_path.clone(); api_secret_path.push(API_SECRET_FILE_NAME); if !api_secret_path.exists() { @@ -109,28 +115,33 @@ fn check_api_secret_file(chain_type: &global::ChainTypes) -> Result<(), ConfigEr /// Handles setup and detection of paths for wallet pub fn initial_setup_wallet( chain_type: &global::ChainTypes, + data_path: Option, ) -> Result { - check_api_secret_file(chain_type)?; + check_api_secret_file(chain_type, data_path.clone())?; // Use config file if current directory if it exists, .grin home otherwise if let Some(p) = check_config_current_dir(WALLET_CONFIG_FILE_NAME) { GlobalWalletConfig::new(p.to_str().unwrap()) } else { // Check if grin dir exists - let grin_path = get_grin_path(chain_type)?; + let grin_path = match data_path { + Some(p) => p, + None => get_grin_path(chain_type)?, + }; // Get path to default config file let mut config_path = grin_path.clone(); config_path.push(WALLET_CONFIG_FILE_NAME); - // Spit it out if it doesn't exist + // Return defaults if file doesn't exist if !config_path.exists() { let mut default_config = GlobalWalletConfig::for_chain(chain_type); + default_config.config_file_path = Some(config_path); // update paths relative to current dir default_config.update_paths(&grin_path); - default_config.write_to_file(config_path.to_str().unwrap())?; + Ok(default_config) + } else { + GlobalWalletConfig::new(config_path.to_str().unwrap()) } - - GlobalWalletConfig::new(config_path.to_str().unwrap()) } } @@ -170,9 +181,7 @@ impl GlobalWalletConfig { defaults.api_listen_port = 23415; defaults.check_node_api_http_addr = "http://127.0.0.1:23413".to_owned(); } - global::ChainTypes::AutomatedTesting => { - panic!("Can't run automated testing directly"); - } + _ => {} } defaults_conf } diff --git a/controller/Cargo.toml b/controller/Cargo.toml index bf659348..6027ad95 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_controller" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Controllers for grin wallet instantiation" license = "Apache-2.0" @@ -32,9 +32,9 @@ chrono = { version = "0.4.4", features = ["serde"] } easy-jsonrpc = "0.5.1" lazy_static = "1" -grin_wallet_util = { path = "../util", version = "2.0.1-beta.1" } +grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" } -grin_wallet_api = { path = "../api", version = "2.0.1-beta.1" } -grin_wallet_impls = { path = "../impls", version = "2.0.1-beta.1" } -grin_wallet_libwallet = { path = "../libwallet", version = "2.0.1-beta.1" } -grin_wallet_config = { path = "../config", version = "2.0.1-beta.1" } +grin_wallet_api = { path = "../api", version = "2.1.0-beta.1" } +grin_wallet_impls = { path = "../impls", version = "2.1.0-beta.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "2.1.0-beta.1" } +grin_wallet_config = { path = "../config", version = "2.1.0-beta.1" } diff --git a/controller/src/command.rs b/controller/src/command.rs index c8731b89..78419f0b 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -12,31 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Grin wallet command-line function implementations + +use crate::api::TLSConfig; +use crate::config::{WalletConfig, WALLET_CONFIG_FILE_NAME}; +use crate::core::{core, global}; +use crate::error::{Error, ErrorKind}; +use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateReceiver as _}; +use crate::impls::{PathToSlate, SlatePutter}; +use crate::keychain; +use crate::libwallet::{InitTxArgs, IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; use crate::util::{Mutex, ZeroingString}; -use std::collections::HashMap; -/// Grin wallet command-line function implementations +use crate::{controller, display}; +use serde_json as json; use std::fs::File; use std::io::Write; use std::sync::Arc; use std::thread; use std::time::Duration; - -use serde_json as json; use uuid::Uuid; -use crate::api::TLSConfig; -use crate::core::core; -use crate::keychain; - -use crate::config::WalletConfig; -use crate::error::{Error, ErrorKind}; -use crate::impls::{ - instantiate_wallet, FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter, - LMDBBackend, NullWalletCommAdapter, -}; -use crate::impls::{HTTPNodeClient, WalletSeed}; -use crate::libwallet::{InitTxArgs, IssueInvoiceTxArgs, NodeClient, WalletInst}; -use crate::{controller, display}; +fn show_recovery_phrase(phrase: ZeroingString) { + println!("Your recovery phrase is:"); + println!(); + println!("{}", &*phrase); + println!(); + println!("Please back-up these words in a non-digital format."); +} /// Arguments common to all wallet commands #[derive(Clone)] @@ -44,6 +46,7 @@ pub struct GlobalArgs { pub account: String, pub node_api_secret: Option, pub show_spent: bool, + pub chain_type: global::ChainTypes, pub password: Option, pub tls_conf: Option, } @@ -58,21 +61,28 @@ pub struct InitArgs { pub restore: bool, } -pub fn init(g_args: &GlobalArgs, args: InitArgs) -> Result<(), Error> { - WalletSeed::init_file( - &args.config, - args.list_length, +pub fn init<'a, L, C, K>( + wallet: Arc>>>, + g_args: &GlobalArgs, + args: InitArgs, +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ + let mut w_lock = wallet.lock(); + let p = w_lock.lc_provider()?; + p.create_config(&g_args.chain_type, WALLET_CONFIG_FILE_NAME)?; + p.create_wallet( + None, args.recovery_phrase, - &args.password, + args.list_length, + args.password.clone(), )?; - info!("Wallet seed file created"); - let client_n = HTTPNodeClient::new( - &args.config.check_node_api_http_addr, - g_args.node_api_secret.clone(), - ); - let _: LMDBBackend = - LMDBBackend::new(args.config.clone(), &args.password, client_n)?; - info!("Wallet database backend created"); + + let m = p.get_mnemonic(None, args.password)?; + show_recovery_phrase(m); Ok(()) } @@ -82,30 +92,23 @@ pub struct RecoverArgs { pub passphrase: ZeroingString, } -/// Check whether seed file exists -pub fn wallet_seed_exists(config: &WalletConfig) -> Result<(), Error> { - let res = WalletSeed::seed_file_exists(&config)?; - Ok(res) -} - -pub fn recover(config: &WalletConfig, args: RecoverArgs) -> Result<(), Error> { - if args.recovery_phrase.is_none() { - let res = WalletSeed::from_file(config, &args.passphrase); - if let Err(e) = res { - error!("Error loading wallet seed (check password): {}", e); - return Err(e.into()); - } - let _ = res.unwrap().show_recovery_phrase(); - } else { - let res = WalletSeed::recover_from_phrase( - &config, - &args.recovery_phrase.as_ref().unwrap(), - &args.passphrase, - ); - if let Err(e) = res { - error!("Error recovering seed - {}", e); - return Err(e.into()); +pub fn recover<'a, L, C, K>( + wallet: Arc>>>, + args: RecoverArgs, +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ + let mut w_lock = wallet.lock(); + let p = w_lock.lc_provider()?; + match args.recovery_phrase { + None => { + let m = p.get_mnemonic(None, args.passphrase)?; + show_recovery_phrase(m); } + Some(phrase) => p.recover_from_mnemonic(phrase, args.passphrase)?, } Ok(()) } @@ -115,49 +118,36 @@ pub struct ListenArgs { pub method: String, } -pub fn listen(config: &WalletConfig, args: &ListenArgs, g_args: &GlobalArgs) -> Result<(), Error> { - let mut params = HashMap::new(); - params.insert("api_listen_addr".to_owned(), config.api_listen_addr()); - if let Some(t) = g_args.tls_conf.as_ref() { - params.insert("certificate".to_owned(), t.certificate.clone()); - params.insert("private_key".to_owned(), t.private_key.clone()); - } +pub fn listen<'a, L, C, K>( + wallet: Arc>>>, + config: &WalletConfig, + args: &ListenArgs, + g_args: &GlobalArgs, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ let res = match args.method.as_str() { - "http" => { - // HTTP adapter can't use the listen trait method because of the - // crate structure. May be able to fix when V1 API is deprecated - let node_client = HTTPNodeClient::new( - &config.check_node_api_http_addr, - g_args.node_api_secret.clone(), - ); - let wallet = instantiate_wallet( - config.clone(), - node_client, - &g_args.password.clone().unwrap(), - &g_args.account, - )?; - let listen_addr = params.get("api_listen_addr").unwrap(); - let tls_conf = match params.get("certificate") { - Some(s) => Some(TLSConfig::new( - s.to_owned(), - params.get("private_key").unwrap().to_owned(), - )), - None => None, - }; - controller::foreign_listener(wallet.clone(), &listen_addr, tls_conf)?; - Ok(()) + "http" => controller::foreign_listener( + wallet.clone(), + &config.api_listen_addr(), + g_args.tls_conf.clone(), + ), + "keybase" => KeybaseAllChannels::new()?.listen( + config.clone(), + g_args.password.clone().unwrap(), + &g_args.account, + g_args.node_api_secret.clone(), + ), + method => { + return Err(ErrorKind::ArgumentError(format!( + "No listener for method \"{}\".", + method + )) + .into()); } - "keybase" => { - let adapter = KeybaseWalletCommAdapter::new(); - adapter.listen( - params, - config.clone(), - &g_args.password.clone().unwrap(), - &g_args.account, - g_args.node_api_secret.clone(), - ) - } - _ => Ok(()), }; if let Err(e) = res { @@ -166,11 +156,16 @@ pub fn listen(config: &WalletConfig, args: &ListenArgs, g_args: &GlobalArgs) -> Ok(()) } -pub fn owner_api( - wallet: Arc>>, +pub fn owner_api( + wallet: Arc>>>, config: &WalletConfig, g_args: &GlobalArgs, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K> + Send + Sync + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ let res = controller::owner_listener( wallet, config.owner_api_listen_addr().as_str(), @@ -189,10 +184,15 @@ pub struct AccountArgs { pub create: Option, } -pub fn account( - wallet: Arc>>, +pub fn account<'a, L, C, K>( + wallet: Arc>>>, args: AccountArgs, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ if args.create.is_none() { let res = controller::owner_single_use(wallet, |api| { let acct_mappings = api.accounts()?; @@ -237,11 +237,16 @@ pub struct SendArgs { pub target_slate_version: Option, } -pub fn send( - wallet: Arc>>, +pub fn send<'a, L, C, K>( + wallet: Arc>>>, args: SendArgs, dark_scheme: bool, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { if args.estimate_selection_strategies { let strategies = vec!["smallest", "all"] @@ -291,42 +296,41 @@ pub fn send( return Err(e); } }; - let adapter = match args.method.as_str() { - "http" => HTTPWalletCommAdapter::new(), - "file" => FileWalletCommAdapter::new(), - "keybase" => KeybaseWalletCommAdapter::new(), - "self" => NullWalletCommAdapter::new(), - _ => NullWalletCommAdapter::new(), - }; - if adapter.supports_sync() { - slate = adapter.send_tx_sync(&args.dest, &slate)?; - api.tx_lock_outputs(&slate, 0)?; - if args.method == "self" { + + match args.method.as_str() { + "file" => { + PathToSlate((&args.dest).into()).put_tx(&slate)?; + api.tx_lock_outputs(&slate, 0)?; + return Ok(()); + } + "self" => { + api.tx_lock_outputs(&slate, 0)?; controller::foreign_single_use(wallet, |api| { slate = api.receive_tx(&slate, Some(&args.dest), None)?; Ok(()) })?; } - if let Err(e) = api.verify_slate_messages(&slate) { - error!("Error validating participant messages: {}", e); - return Err(e); + method => { + let sender = create_sender(method, &args.dest)?; + slate = sender.send_tx(&slate)?; + api.tx_lock_outputs(&slate, 0)?; } - slate = api.finalize_tx(&slate)?; - } else { - adapter.send_tx_async(&args.dest, &slate)?; - api.tx_lock_outputs(&slate, 0)?; } - if adapter.supports_sync() { - let result = api.post_tx(&slate.tx, args.fluff); - match result { - Ok(_) => { - info!("Tx sent ok",); - return Ok(()); - } - Err(e) => { - error!("Tx sent fail: {}", e); - return Err(e); - } + + api.verify_slate_messages(&slate).map_err(|e| { + error!("Error validating participant messages: {}", e); + e + })?; + slate = api.finalize_tx(&slate)?; + let result = api.post_tx(&slate.tx, args.fluff); + match result { + Ok(_) => { + info!("Tx sent ok",); + return Ok(()); + } + Err(e) => { + error!("Tx sent fail: {}", e); + return Err(e); } } } @@ -341,13 +345,17 @@ pub struct ReceiveArgs { pub message: Option, } -pub fn receive( - wallet: Arc>>, +pub fn receive<'a, L, C, K>( + wallet: Arc>>>, g_args: &GlobalArgs, args: ReceiveArgs, -) -> Result<(), Error> { - let adapter = FileWalletCommAdapter::new(); - let mut slate = adapter.receive_tx_async(&args.input)?; +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ + let mut slate = PathToSlate((&args.input).into()).get_tx()?; controller::foreign_single_use(wallet, |api| { if let Err(e) = api.verify_slate_messages(&slate) { error!("Error validating participant messages: {}", e); @@ -356,8 +364,7 @@ pub fn receive( slate = api.receive_tx(&slate, Some(&g_args.account), args.message.clone())?; Ok(()) })?; - let send_tx = format!("{}.response", args.input); - adapter.send_tx_async(&send_tx, &slate)?; + PathToSlate(format!("{}.response", args.input).into()).put_tx(&slate)?; info!( "Response file {}.response generated, and can be sent back to the transaction originator.", args.input @@ -371,12 +378,17 @@ pub struct FinalizeArgs { pub fluff: bool, } -pub fn finalize( - wallet: Arc>>, +pub fn finalize<'a, L, C, K>( + wallet: Arc>>>, args: FinalizeArgs, -) -> Result<(), Error> { - let adapter = FileWalletCommAdapter::new(); - let mut slate = adapter.receive_tx_async(&args.input)?; +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ + let mut slate = PathToSlate((&args.input).into()).get_tx()?; + // Rather than duplicating the entire command, we'll just // try to determine what kind of finalization this is // based on the slate contents @@ -440,10 +452,15 @@ pub struct IssueInvoiceArgs { pub issue_args: IssueInvoiceTxArgs, } -pub fn issue_invoice_tx( - wallet: Arc>>, +pub fn issue_invoice_tx<'a, L, C, K>( + wallet: Arc>>>, args: IssueInvoiceArgs, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let slate = api.issue_invoice_tx(args.issue_args)?; let mut tx_file = File::create(args.dest.clone())?; @@ -467,13 +484,17 @@ pub struct ProcessInvoiceArgs { } /// Process invoice -pub fn process_invoice( - wallet: Arc>>, +pub fn process_invoice<'a, L, C, K>( + wallet: Arc>>>, args: ProcessInvoiceArgs, dark_scheme: bool, -) -> Result<(), Error> { - let adapter = FileWalletCommAdapter::new(); - let slate = adapter.receive_tx_async(&args.input)?; +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ + let slate = PathToSlate((&args.input).into()).get_tx()?; controller::owner_single_use(wallet.clone(), |api| { if args.estimate_selection_strategies { let strategies = vec!["smallest", "all"] @@ -526,24 +547,25 @@ pub fn process_invoice( return Err(e); } }; - let adapter = match args.method.as_str() { - "http" => HTTPWalletCommAdapter::new(), - "file" => FileWalletCommAdapter::new(), - "self" => NullWalletCommAdapter::new(), - _ => NullWalletCommAdapter::new(), - }; - if adapter.supports_sync() { - slate = adapter.send_tx_sync(&args.dest, &slate)?; - api.tx_lock_outputs(&slate, 0)?; - if args.method == "self" { + + match args.method.as_str() { + "file" => { + let slate_putter = PathToSlate((&args.dest).into()); + slate_putter.put_tx(&slate)?; + api.tx_lock_outputs(&slate, 0)?; + } + "self" => { + api.tx_lock_outputs(&slate, 0)?; controller::foreign_single_use(wallet, |api| { slate = api.finalize_invoice_tx(&slate)?; Ok(()) })?; } - } else { - adapter.send_tx_async(&args.dest, &slate)?; - api.tx_lock_outputs(&slate, 0)?; + method => { + let sender = create_sender(method, &args.dest)?; + slate = sender.send_tx(&slate)?; + api.tx_lock_outputs(&slate, 0)?; + } } } Ok(()) @@ -555,12 +577,17 @@ pub struct InfoArgs { pub minimum_confirmations: u64, } -pub fn info( - wallet: Arc>>, +pub fn info<'a, L, C, K>( + wallet: Arc>>>, g_args: &GlobalArgs, args: InfoArgs, dark_scheme: bool, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let (validated, wallet_info) = api.retrieve_summary_info(true, args.minimum_confirmations)?; @@ -570,11 +597,16 @@ pub fn info( Ok(()) } -pub fn outputs( - wallet: Arc>>, +pub fn outputs<'a, L, C, K>( + wallet: Arc>>>, g_args: &GlobalArgs, dark_scheme: bool, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let res = api.node_height()?; let (validated, outputs) = api.retrieve_outputs(g_args.show_spent, true, None)?; @@ -590,12 +622,17 @@ pub struct TxsArgs { pub tx_slate_id: Option, } -pub fn txs( - wallet: Arc>>, +pub fn txs<'a, L, C, K>( + wallet: Arc>>>, g_args: &GlobalArgs, args: TxsArgs, dark_scheme: bool, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let res = api.node_height()?; let (validated, txs) = api.retrieve_txs(true, args.id, args.tx_slate_id)?; @@ -645,10 +682,15 @@ pub struct RepostArgs { pub fluff: bool, } -pub fn repost( - wallet: Arc>>, +pub fn repost<'a, L, C, K>( + wallet: Arc>>>, args: RepostArgs, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let (_, txs) = api.retrieve_txs(true, Some(args.id), None)?; let stored_tx = api.get_stored_tx(&txs[0])?; @@ -691,10 +733,15 @@ pub struct CancelArgs { pub tx_id_string: String, } -pub fn cancel( - wallet: Arc>>, +pub fn cancel<'a, L, C, K>( + wallet: Arc>>>, args: CancelArgs, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let result = api.cancel_tx(args.tx_id, args.tx_slate_id); match result { @@ -711,9 +758,14 @@ pub fn cancel( Ok(()) } -pub fn restore( - wallet: Arc>>, -) -> Result<(), Error> { +pub fn restore<'a, L, C, K>( + wallet: Arc>>>, +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { let result = api.restore(); match result { @@ -736,10 +788,15 @@ pub struct CheckArgs { pub delete_unconfirmed: bool, } -pub fn check_repair( - wallet: Arc>>, +pub fn check_repair<'a, L, C, K>( + wallet: Arc>>>, args: CheckArgs, -) -> Result<(), Error> { +) -> Result<(), Error> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, +{ controller::owner_single_use(wallet.clone(), |api| { warn!("Starting wallet check...",); warn!("Updating all wallet outputs, please wait ...",); diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 18606015..5c987579 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -17,11 +17,10 @@ use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig}; use crate::keychain::Keychain; use crate::libwallet::{ - Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletBackend, CURRENT_SLATE_VERSION, - GRIN_BLOCK_HEADER_VERSION, + Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletInst, WalletLCProvider, + CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION, }; -use crate::util::to_base64; -use crate::util::Mutex; +use crate::util::{to_base64, Mutex}; use failure::ResultExt; use futures::future::{err, ok}; use futures::{Future, Stream}; @@ -29,7 +28,6 @@ use hyper::header::HeaderValue; use hyper::{Body, Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; use serde_json; -use std::marker::PhantomData; use std::net::SocketAddr; use std::sync::Arc; @@ -74,41 +72,47 @@ fn check_middleware( /// 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<'a, L, F, C, K>( + lc_provider: Arc>>>, + f: F, +) -> Result<(), Error> where - T: WalletBackend, - F: FnOnce(&mut Owner) -> Result<(), Error>, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + F: FnOnce(&mut Owner<'a, L, C, K>) -> Result<(), Error>, + C: NodeClient + 'a, + K: Keychain + 'a, { - f(&mut Owner::new(wallet.clone()))?; + f(&mut Owner::new(lc_provider))?; Ok(()) } /// 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<'a, L, F, C, K>( + lc_provider: Arc>>>, + f: F, +) -> Result<(), Error> where - T: WalletBackend, - F: FnOnce(&mut Foreign) -> Result<(), Error>, - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + F: FnOnce(&mut Foreign<'a, L, C, K>) -> Result<(), Error>, + C: NodeClient + 'a, + K: Keychain + 'a, { - f(&mut Foreign::new(wallet.clone(), Some(check_middleware)))?; + f(&mut Foreign::new(lc_provider, Some(check_middleware)))?; Ok(()) } /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn owner_listener( - wallet: Arc>, +pub fn owner_listener( + wallet: Arc + 'static>>>, addr: &str, api_secret: Option, tls_config: Option, owner_api_include_foreign: Option, ) -> Result<(), Error> where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { @@ -131,21 +135,22 @@ where // If so configured, add the foreign API to the same port if owner_api_include_foreign.unwrap_or(false) { - info!("Starting HTTP Foreign API on Owner server at {}.", addr); - let foreign_api_handler_v2 = ForeignAPIHandlerV2::new(wallet.clone()); + warn!("Starting HTTP Foreign API on Owner server at {}.", addr); + let foreign_api_handler_v2 = ForeignAPIHandlerV2::new(wallet); router .add_route("/v2/foreign", Arc::new(foreign_api_handler_v2)) .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; } let mut apis = ApiServer::new(); - info!("Starting HTTP Owner API server at {}.", addr); + warn!("Starting HTTP Owner API server at {}.", addr); let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address"); let api_thread = apis.start(socket_addr, router, tls_config) .context(ErrorKind::GenericError( "API thread failed to start".to_string(), ))?; + warn!("HTTP Owner listener started."); api_thread .join() .map_err(|e| ErrorKind::GenericError(format!("API thread panicked :{:?}", e)).into()) @@ -153,13 +158,13 @@ where /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn foreign_listener( - wallet: Arc>, +pub fn foreign_listener( + wallet: Arc + 'static>>>, addr: &str, tls_config: Option, ) -> Result<(), Error> where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { @@ -189,37 +194,33 @@ where type WalletResponseFuture = Box, Error = Error> + Send>; /// V2 API Handler/Wrapper for owner functions -pub struct OwnerAPIHandlerV2 +pub struct OwnerAPIHandlerV2 where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { /// Wallet instance - pub wallet: Arc>, - phantom: PhantomData, - phantom_c: PhantomData, + pub wallet: Arc + 'static>>>, } -impl OwnerAPIHandlerV2 +impl OwnerAPIHandlerV2 where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K>, C: NodeClient + 'static, K: Keychain + 'static, { /// Create a new owner API handler for GET methods - pub fn new(wallet: Arc>) -> OwnerAPIHandlerV2 { - OwnerAPIHandlerV2 { - wallet, - phantom: PhantomData, - phantom_c: PhantomData, - } + pub fn new( + wallet: Arc + 'static>>>, + ) -> OwnerAPIHandlerV2 { + OwnerAPIHandlerV2 { wallet } } fn call_api( &self, req: Request, - api: Owner, + api: Owner<'static, L, C, K>, ) -> Box + Send> { Box::new(parse_body(req).and_then(move |val: serde_json::Value| { let owner_api = &api as &dyn OwnerRpc; @@ -243,9 +244,9 @@ where } } -impl api::Handler for OwnerAPIHandlerV2 +impl api::Handler for OwnerAPIHandlerV2 where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { @@ -266,37 +267,33 @@ where } /// V2 API Handler/Wrapper for foreign functions -pub struct ForeignAPIHandlerV2 +pub struct ForeignAPIHandlerV2 where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { /// Wallet instance - pub wallet: Arc>, - phantom: PhantomData, - phantom_c: PhantomData, + pub wallet: Arc + 'static>>>, } -impl ForeignAPIHandlerV2 +impl ForeignAPIHandlerV2 where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { /// Create a new foreign API handler for GET methods - pub fn new(wallet: Arc>) -> ForeignAPIHandlerV2 { - ForeignAPIHandlerV2 { - wallet, - phantom: PhantomData, - phantom_c: PhantomData, - } + pub fn new( + wallet: Arc + 'static>>>, + ) -> ForeignAPIHandlerV2 { + ForeignAPIHandlerV2 { wallet } } fn call_api( &self, req: Request, - api: Foreign, + api: Foreign<'static, L, C, K>, ) -> Box + Send> { Box::new(parse_body(req).and_then(move |val: serde_json::Value| { let foreign_api = &api as &dyn ForeignRpc; @@ -320,9 +317,9 @@ where } } -impl api::Handler for ForeignAPIHandlerV2 +impl api::Handler for ForeignAPIHandlerV2 where - T: WalletBackend + Send + Sync + 'static, + L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index e773d9a1..198dfa1e 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -19,47 +19,43 @@ extern crate grin_wallet_impls as impls; use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; use self::core::global; -use self::core::global::ChainTypes; use self::keychain::{ExtKeychain, Keychain}; use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use impls::test_framework::{self, LocalWalletClient}; use libwallet::InitTxArgs; -use std::fs; use std::thread; use std::time::Duration; -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); -} +#[macro_use] +mod common; +use common::{create_wallet_proxy, setup}; /// Various tests on accounts within the same wallet -fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn accounts_test_impl(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - // Create a new wallet test client, and set its queues to communicate with the - // proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - 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 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -103,20 +99,20 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Default wallet 2 to listen on that account { - let mut w = wallet2.lock(); + wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("listener_account")?; } // Mine into two different accounts in the same wallet { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account1")?; assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); } let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 7, false); { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account2")?; assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); } @@ -136,7 +132,9 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { })?; // now check second account { - let mut w = wallet1.lock(); + let mut w_lock = wallet1.lock(); + let lc = w_lock.lc_provider()?; + let w = lc.wallet_inst()?; w.set_parent_key_id_by_name("account1")?; } wallet::controller::owner_single_use(wallet1.clone(), |api| { @@ -156,7 +154,7 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // should be nothing in default account { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("default")?; } wallet::controller::owner_single_use(wallet1.clone(), |api| { @@ -175,7 +173,7 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Send a tx to another wallet { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account1")?; } @@ -208,7 +206,7 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // other account should be untouched { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account2")?; } wallet::controller::owner_single_use(wallet1.clone(), |api| { @@ -233,7 +231,7 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { })?; // Default account on wallet 2 should be untouched { - let mut w = wallet2.lock(); + wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("default")?; } wallet::controller::owner_single_use(wallet2.clone(), |api| { diff --git a/controller/tests/check.rs b/controller/tests/check.rs index cd17ad94..53e024de 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -18,70 +18,60 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; use grin_wallet_util::grin_util as util; use self::core::consensus; use self::core::global; -use self::core::global::ChainTypes; -use self::keychain::ExtKeychain; use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; -use impls::FileWalletCommAdapter; -use libwallet::{InitTxArgs, WalletInst}; -use std::fs; +use impls::test_framework::{self, LocalWalletClient}; +use impls::{PathToSlate, SlatePutter as _}; +use libwallet::InitTxArgs; use std::thread; use std::time::Duration; +use util::ZeroingString; + +#[macro_use] +mod common; +use common::{create_wallet_proxy, setup}; macro_rules! send_to_dest { ($a:expr, $b:expr, $c:expr, $d:expr) => { - test_framework::send_to_dest::< - WalletInst, - LocalWalletClient, - ExtKeychain, - >($a, $b, $c, $d, false) + test_framework::send_to_dest($a, $b, $c, $d, false) }; } macro_rules! wallet_info { ($a:expr) => { - test_framework::wallet_info::< - WalletInst, - LocalWalletClient, - ExtKeychain, - >($a) + test_framework::wallet_info($a) }; } -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); -} - /// Various tests on checking functionality -fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn check_repair_impl(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); // Create a new wallet test client, and set its queues to communicate with the // proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - 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 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -138,8 +128,7 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { let w1_outputs: Vec = w1_outputs_commits.into_iter().map(|m| m.output).collect(); { - let mut w = wallet1.lock(); - w.open_with_credentials()?; + wallet_inst!(wallet1, w); { let mut batch = w.batch()?; batch.delete(&w1_outputs[4].key_id, &None)?; @@ -149,7 +138,6 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { batch.save(accidental_spent)?; batch.commit()?; } - w.close()?; } // check we have a problem now @@ -187,11 +175,10 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api.init_send_tx(args)?; + let slate = api.init_send_tx(args)?; // output tx file - let file_adapter = FileWalletCommAdapter::new(); let send_file = format!("{}/part_tx_1.tx", test_dir); - file_adapter.send_tx_async(&send_file, &mut slate)?; + PathToSlate(send_file.into()).put_tx(&slate)?; api.tx_lock_outputs(&slate, 0)?; Ok(()) })?; @@ -221,107 +208,107 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { Ok(()) } -fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { setup(test_dir); let seed_phrase = "affair pistol cancel crush garment candy ancient flag work \ market crush dry stand focus mutual weapon offer ceiling rival turn team spring \ where swift"; + let seed_phrase = Some(ZeroingString::from(seed_phrase)); // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy: WalletProxy = WalletProxy::new(test_dir); + let mut wallet_proxy = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); // Create a new wallet test client, and set its queues to communicate with the // proxy - let m_client = LocalWalletClient::new("miner", wallet_proxy.tx.clone()); - let miner = - test_framework::create_wallet(&format!("{}/miner", test_dir), m_client.clone(), None); - wallet_proxy.add_wallet("miner", m_client.get_send_instance(), miner.clone()); + create_wallet_and_add!(m_client, miner, test_dir, "miner", None, &mut wallet_proxy); // non-mining recipient wallets - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = test_framework::create_wallet( - &format!("{}/wallet1", test_dir), - client1.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = test_framework::create_wallet( - &format!("{}/wallet2", test_dir), - client2.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); - // we'll restore into here - let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone()); - let wallet3 = test_framework::create_wallet( - &format!("{}/wallet3", test_dir), - client3.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client3, + wallet3, + test_dir, + "wallet3", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone()); - // also restore into here - let client4 = LocalWalletClient::new("wallet4", wallet_proxy.tx.clone()); - let wallet4 = test_framework::create_wallet( - &format!("{}/wallet4", test_dir), - client4.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client4, + wallet4, + test_dir, + "wallet4", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet4", client4.get_send_instance(), wallet4.clone()); - // Simulate a recover from seed without restore into here - let client5 = LocalWalletClient::new("wallet5", wallet_proxy.tx.clone()); - let wallet5 = test_framework::create_wallet( - &format!("{}/wallet5", test_dir), - client5.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client5, + wallet5, + test_dir, + "wallet5", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet5", client5.get_send_instance(), wallet5.clone()); - //simulate a recover from seed without restore into here - let client6 = LocalWalletClient::new("wallet6", wallet_proxy.tx.clone()); - let wallet6 = test_framework::create_wallet( - &format!("{}/wallet6", test_dir), - client6.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client6, + wallet6, + test_dir, + "wallet6", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet6", client6.get_send_instance(), wallet6.clone()); - let client7 = LocalWalletClient::new("wallet7", wallet_proxy.tx.clone()); - let wallet7 = test_framework::create_wallet( - &format!("{}/wallet7", test_dir), - client7.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client7, + wallet7, + test_dir, + "wallet7", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet7", client7.get_send_instance(), wallet7.clone()); - - let client8 = LocalWalletClient::new("wallet8", wallet_proxy.tx.clone()); - let wallet8 = test_framework::create_wallet( - &format!("{}/wallet8", test_dir), - client8.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client8, + wallet8, + test_dir, + "wallet8", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet8", client8.get_send_instance(), wallet8.clone()); - - let client9 = LocalWalletClient::new("wallet9", wallet_proxy.tx.clone()); - let wallet9 = test_framework::create_wallet( - &format!("{}/wallet9", test_dir), - client9.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client9, + wallet9, + test_dir, + "wallet9", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet9", client9.get_send_instance(), wallet9.clone()); - - let client10 = LocalWalletClient::new("wallet10", wallet_proxy.tx.clone()); - let wallet10 = test_framework::create_wallet( - &format!("{}/wallet10", test_dir), - client10.clone(), - Some(seed_phrase), + create_wallet_and_add!( + client10, + wallet10, + test_dir, + "wallet10", + seed_phrase, + &mut wallet_proxy ); - wallet_proxy.add_wallet("wallet10", client10.get_send_instance(), wallet10.clone()); // Set the wallet proxy listener running thread::spawn(move || { diff --git a/controller/tests/common/mod.rs b/controller/tests/common/mod.rs new file mode 100644 index 00000000..5f40ae7f --- /dev/null +++ b/controller/tests/common/mod.rs @@ -0,0 +1,138 @@ +// Copyright 2019 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. + +//! common functions for tests (instantiating wallet and proxy, mostly) +extern crate grin_wallet_controller as wallet; +extern crate grin_wallet_impls as impls; +extern crate grin_wallet_libwallet as libwallet; + +use grin_wallet_util::grin_core as core; +use grin_wallet_util::grin_keychain as keychain; +use grin_wallet_util::grin_util as util; + +use self::core::global; +use self::core::global::ChainTypes; +use self::keychain::ExtKeychain; +use self::libwallet::WalletInst; +use impls::test_framework::{LocalWalletClient, WalletProxy}; +use impls::{DefaultLCProvider, DefaultWalletImpl}; +use std::fs; +use std::sync::Arc; +use util::{Mutex, ZeroingString}; + +#[macro_export] +macro_rules! wallet_inst { + ($wallet:ident, $w: ident) => { + let mut w_lock = $wallet.lock(); + let lc = w_lock.lc_provider()?; + let $w = lc.wallet_inst()?; + }; +} + +#[macro_export] +macro_rules! create_wallet_and_add { + ($client:ident, $wallet: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr) => { + let $client = LocalWalletClient::new($name, $proxy.tx.clone()); + let $wallet = + common::create_local_wallet($test_dir, $name, $seed_phrase.clone(), $client.clone()); + $proxy.add_wallet($name, $client.get_send_instance(), $wallet.clone()); + }; +} + +#[macro_export] +macro_rules! open_wallet_and_add { + ($client:ident, $wallet: ident, $test_dir: expr, $name: expr, $proxy: expr) => { + let $client = LocalWalletClient::new($name, $proxy.tx.clone()); + let $wallet = common::open_local_wallet($test_dir, $name, $client.clone()); + $proxy.add_wallet($name, $client.get_send_instance(), $wallet.clone()); + }; +} +pub fn clean_output_dir(test_dir: &str) { + let _ = fs::remove_dir_all(test_dir); +} + +pub fn setup(test_dir: &str) { + util::init_test_logger(); + clean_output_dir(test_dir); + global::set_mining_mode(ChainTypes::AutomatedTesting); +} + +pub fn create_wallet_proxy( + test_dir: &str, +) -> WalletProxy, LocalWalletClient, ExtKeychain> +{ + WalletProxy::new(test_dir) +} + +pub fn create_local_wallet( + test_dir: &str, + name: &str, + mnemonic: Option, + client: LocalWalletClient, +) -> Arc< + Mutex< + Box< + WalletInst< + 'static, + DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + LocalWalletClient, + ExtKeychain, + >, + >, + >, +> { + let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) + as Box< + WalletInst< + DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet.lc_provider().unwrap(); + lc.set_wallet_directory(&format!("{}/{}", test_dir, name)); + lc.create_wallet(None, mnemonic, 32, ZeroingString::from("")) + .unwrap(); + lc.open_wallet(None, ZeroingString::from("")).unwrap(); + Arc::new(Mutex::new(wallet)) +} + +pub fn open_local_wallet( + test_dir: &str, + name: &str, + client: LocalWalletClient, +) -> Arc< + Mutex< + Box< + WalletInst< + 'static, + DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + LocalWalletClient, + ExtKeychain, + >, + >, + >, +> { + let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) + as Box< + WalletInst< + DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet.lc_provider().unwrap(); + lc.set_wallet_directory(&format!("{}/{}", test_dir, name)); + lc.open_wallet(None, ZeroingString::from("")).unwrap(); + Arc::new(Mutex::new(wallet)) +} diff --git a/controller/tests/file.rs b/controller/tests/file.rs index 0ac74381..d0747d5d 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -17,17 +17,11 @@ extern crate log; extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; -use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; - -use self::core::global; -use self::core::global::ChainTypes; -use self::keychain::ExtKeychain; use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; -use impls::FileWalletCommAdapter; -use std::fs; +use grin_wallet_util::grin_core as core; + +use impls::test_framework::{self, LocalWalletClient}; +use impls::{PathToSlate, SlateGetter as _, SlatePutter as _}; use std::thread; use std::time::Duration; @@ -35,32 +29,36 @@ use grin_wallet_libwallet::InitTxArgs; use serde_json; -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); -} +#[macro_use] +mod common; +use common::{create_wallet_proxy, setup}; /// self send impl -fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn file_exchange_test_impl(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + // Create a new wallet test client, and set its queues to communicate with the + // proxy + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -88,7 +86,7 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Get some mining done { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; } let mut bh = 10u64; @@ -119,20 +117,18 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { }; let mut slate = api.init_send_tx(args)?; // output tx file - let file_adapter = FileWalletCommAdapter::new(); - file_adapter.send_tx_async(&send_file, &mut slate)?; + PathToSlate((&send_file).into()).put_tx(&mut slate)?; api.tx_lock_outputs(&slate, 0)?; Ok(()) })?; // Get some mining done { - let mut w = wallet2.lock(); + wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("account1")?; } - let adapter = FileWalletCommAdapter::new(); - let mut slate = adapter.receive_tx_async(&send_file)?; + let mut slate = PathToSlate((&send_file).into()).get_tx()?; let mut naughty_slate = slate.clone(); naughty_slate.participant_data[0].message = Some("I changed the message".to_owned()); @@ -148,14 +144,13 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // wallet 2 receives file, completes, sends file back wallet::controller::foreign_single_use(wallet2.clone(), |api| { slate = api.receive_tx(&slate, None, Some(sender2_message.clone()))?; - adapter.send_tx_async(&receive_file, &mut slate)?; + PathToSlate((&receive_file).into()).put_tx(&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)?; + let mut slate = PathToSlate(receive_file.into()).get_tx()?; api.verify_slate_messages(&slate)?; slate = api.finalize_tx(&slate)?; api.post_tx(&slate.tx, false)?; diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 7d31e2d3..de959880 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -17,54 +17,43 @@ extern crate log; extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; -use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; - -use self::core::global; -use self::core::global::ChainTypes; -use self::keychain::ExtKeychain; use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use grin_wallet_util::grin_core as core; + +use impls::test_framework::{self, LocalWalletClient}; use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; -use std::fs; use std::thread; use std::time::Duration; -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); -} - -fn teardown(test_dir: &str) { - clean_output_dir(test_dir); -} +#[macro_use] +mod common; +use common::{clean_output_dir, create_wallet_proxy, setup}; /// self send impl -fn invoice_tx_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn invoice_tx_impl(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - // Create a new wallet test client, and set its queues to communicate with the proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - - // wallet 2, will be recipient - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -85,7 +74,7 @@ fn invoice_tx_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Get some mining done { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; } let mut bh = 10u64; @@ -211,7 +200,7 @@ fn invoice_tx_impl(test_dir: &str) -> Result<(), libwallet::Error> { thread::sleep(Duration::from_millis(200)); } - teardown(test_dir); + clean_output_dir(test_dir); Ok(()) } diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 856dc472..006a4427 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -19,45 +19,43 @@ extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; -use self::core::global; -use self::core::global::ChainTypes; -use self::keychain::ExtKeychain; use self::libwallet::{InitTxArgs, Slate}; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; -use impls::FileWalletCommAdapter; -use std::fs; +use impls::test_framework::{self, LocalWalletClient}; +use impls::{PathToSlate, SlateGetter as _, SlatePutter as _}; use std::thread; use std::time::Duration; -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); -} +#[macro_use] +mod common; +use common::{create_wallet_proxy, setup}; /// self send impl -fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn file_repost_test_impl(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + // Create a new wallet test client, and set its queues to communicate with the + // proxy + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -85,7 +83,7 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Get some mining done { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; } let mut bh = 10u64; @@ -112,10 +110,8 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; - let mut slate = api.init_send_tx(args)?; - // output tx file - let file_adapter = FileWalletCommAdapter::new(); - file_adapter.send_tx_async(&send_file, &mut slate)?; + let slate = api.init_send_tx(args)?; + PathToSlate((&send_file).into()).put_tx(&slate)?; api.tx_lock_outputs(&slate, 0)?; Ok(()) })?; @@ -125,28 +121,26 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // wallet 1 receives file to different account, completes { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } wallet::controller::foreign_single_use(wallet1.clone(), |api| { - let adapter = FileWalletCommAdapter::new(); - slate = adapter.receive_tx_async(&send_file)?; + slate = PathToSlate((&send_file).into()).get_tx()?; slate = api.receive_tx(&slate, None, None)?; - adapter.send_tx_async(&receive_file, &mut slate)?; + PathToSlate((&receive_file).into()).put_tx(&slate)?; Ok(()) })?; // wallet 1 receives file to different account, completes { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; } // wallet 1 finalize wallet::controller::owner_single_use(wallet1.clone(), |api| { - let adapter = FileWalletCommAdapter::new(); - slate = adapter.receive_tx_async(&receive_file)?; + slate = PathToSlate((&receive_file).into()).get_tx()?; slate = api.finalize_tx(&slate)?; Ok(()) })?; @@ -173,7 +167,7 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { })?; { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } @@ -187,11 +181,11 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // as above, but syncronously { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; } { - let mut w = wallet2.lock(); + wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("account1")?; } diff --git a/controller/tests/restore.rs b/controller/tests/restore.rs index b4261a54..a826efc2 100644 --- a/controller/tests/restore.rs +++ b/controller/tests/restore.rs @@ -18,44 +18,51 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; -use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; -use self::core::global; -use self::core::global::ChainTypes; use self::keychain::{ExtKeychain, Identifier, Keychain}; use self::libwallet::{AcctPathMapping, InitTxArgs, Slate}; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use impls::test_framework::{self, LocalWalletClient}; use std::fs; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -fn clean_output_dir(test_dir: &str) { - let _ = fs::remove_dir_all(test_dir); -} +#[macro_use] +mod common; +use common::{create_wallet_proxy, setup}; -fn setup(test_dir: &str) { - util::init_test_logger(); - clean_output_dir(test_dir); - global::set_mining_mode(ChainTypes::AutomatedTesting); -} +fn restore_wallet(base_dir: &'static str, wallet_dir: &str) -> Result<(), libwallet::Error> { + let source_seed = format!("{}/{}/wallet_data/wallet.seed", base_dir, wallet_dir); + let dest_wallet_name = format!("{}_restore", wallet_dir); + let dest_dir = format!("{}/{}/wallet_data", base_dir, dest_wallet_name); + + let mut wallet_proxy = create_wallet_proxy(base_dir); + create_wallet_and_add!( + client, + wallet, + base_dir, + &dest_wallet_name, + None, + &mut wallet_proxy + ); + // close created wallet + let mut w_lock = wallet.lock(); + let lc = w_lock.lc_provider()?; + lc.close_wallet(None)?; -fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Error> { - let source_seed = format!("{}/{}/wallet.seed", base_dir, wallet_dir); - let dest_dir = format!("{}/{}_restore", base_dir, wallet_dir); - fs::create_dir_all(dest_dir.clone())?; let dest_seed = format!("{}/wallet.seed", dest_dir); println!("Source: {}, Dest: {}", source_seed, dest_seed); fs::copy(source_seed, dest_seed)?; - let mut wallet_proxy: WalletProxy = WalletProxy::new(base_dir); - let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone()); - - let wallet = test_framework::create_wallet(&dest_dir, client.clone(), None); - - wallet_proxy.add_wallet(wallet_dir, client.get_send_instance(), wallet.clone()); + // reopen with new seed + open_wallet_and_add!( + client, + wallet, + &base_dir, + &dest_wallet_name, + &mut wallet_proxy + ); // Set the wallet proxy listener running let wp_running = wallet_proxy.running.clone(); @@ -79,39 +86,35 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err } fn compare_wallet_restore( - base_dir: &str, + base_dir: &'static str, wallet_dir: &str, account_path: &Identifier, ) -> Result<(), libwallet::Error> { let restore_name = format!("{}_restore", wallet_dir); - let source_dir = format!("{}/{}", base_dir, wallet_dir); - let dest_dir = format!("{}/{}", base_dir, restore_name); + let mut wallet_proxy = create_wallet_proxy(base_dir); - let mut wallet_proxy: WalletProxy = WalletProxy::new(base_dir); - - let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone()); - let wallet_source = test_framework::create_wallet(&source_dir, client.clone(), None); - wallet_proxy.add_wallet( + open_wallet_and_add!( + client, + wallet_source, + &base_dir, &wallet_dir, - client.get_send_instance(), - wallet_source.clone(), + &mut wallet_proxy ); - - let client = LocalWalletClient::new(&restore_name, wallet_proxy.tx.clone()); - let wallet_dest = test_framework::create_wallet(&dest_dir, client.clone(), None); - wallet_proxy.add_wallet( + open_wallet_and_add!( + client, + wallet_dest, + &base_dir, &restore_name, - client.get_send_instance(), - wallet_dest.clone(), + &mut wallet_proxy ); { - let mut w = wallet_source.lock(); + wallet_inst!(wallet_source, w); w.set_parent_key_id(account_path.clone()); } { - let mut w = wallet_dest.lock(); + wallet_inst!(wallet_dest, w); w.set_parent_key_id(account_path.clone()); } @@ -181,24 +184,29 @@ fn compare_wallet_restore( /// Build up 2 wallets, perform a few transactions on them /// Then attempt to restore them in separate directories and check contents are the same -fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { +fn setup_restore(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - // Create a new wallet test client, and set its queues to communicate with the - // proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - - // define recipient wallet, add to proxy - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // wallet 2 will use another account wallet::controller::owner_single_use(wallet2.clone(), |api| { @@ -209,15 +217,19 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Default wallet 2 to listen on that account { - let mut w = wallet2.lock(); + wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("account1")?; } // Another wallet - let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone()); - let wallet3 = - test_framework::create_wallet(&format!("{}/wallet3", test_dir), client3.clone(), None); - wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone()); + create_wallet_and_add!( + client3, + wallet3, + test_dir, + "wallet3", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running let wp_running = wallet_proxy.running.clone(); @@ -301,7 +313,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Another listener account on wallet 2 { - let mut w = wallet2.lock(); + wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("account2")?; } @@ -350,7 +362,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { Ok(()) } -fn perform_restore(test_dir: &str) -> Result<(), libwallet::Error> { +fn perform_restore(test_dir: &'static str) -> Result<(), libwallet::Error> { restore_wallet(test_dir, "wallet1")?; compare_wallet_restore( test_dir, diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index f7efd2f2..fd572a36 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -18,42 +18,34 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; -use self::core::global; -use self::core::global::ChainTypes; -use self::keychain::ExtKeychain; use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use impls::test_framework::{self, LocalWalletClient}; use libwallet::InitTxArgs; -use std::fs; use std::thread; use std::time::Duration; -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); -} +#[macro_use] +mod common; +use common::{create_wallet_proxy, setup}; /// self send impl -fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { +fn self_send_test_impl(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); // Create a new wallet test client, and set its queues to communicate with the // proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -74,7 +66,7 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Get some mining done { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; } let mut bh = 10u64; @@ -123,7 +115,7 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Check total in 'listener' account { - let mut w = wallet1.lock(); + wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } wallet::controller::owner_single_use(wallet1.clone(), |api| { diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 336a8577..f3a17a57 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -19,50 +19,42 @@ extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; use self::core::core::transaction; use self::core::global; -use self::core::global::ChainTypes; -use self::keychain::ExtKeychain; use self::libwallet::{InitTxArgs, OutputStatus, Slate}; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; -use std::fs; +use impls::test_framework::{self, LocalWalletClient}; use std::thread; use std::time::Duration; -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); -} +mod common; +use common::{create_wallet_proxy, setup}; /// 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> { +fn basic_transaction_api(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - // Create a new wallet test client, and set its queues to communicate with the - // proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - 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 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { @@ -349,24 +341,28 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { /// Test rolling back transactions and outputs when a transaction is never /// posted to a chain -fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> { +fn tx_rollback(test_dir: &'static 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 = create_wallet_proxy(test_dir); let chain = wallet_proxy.chain.clone(); - // Create a new wallet test client, and set its queues to communicate with the - // proxy - let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); - let wallet1 = - test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None); - wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone()); - - // define recipient wallet, add to proxy - let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); - let wallet2 = - test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None); - wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone()); + create_wallet_and_add!( + client1, + wallet1, + test_dir, + "wallet1", + None, + &mut wallet_proxy + ); + create_wallet_and_add!( + client2, + wallet2, + test_dir, + "wallet2", + None, + &mut wallet_proxy + ); // Set the wallet proxy listener running thread::spawn(move || { diff --git a/impls/Cargo.toml b/impls/Cargo.toml index c6cf7a5a..7bfd9d2a 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_impls" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Concrete types derived from libwallet traits" license = "Apache-2.0" @@ -25,8 +25,8 @@ tokio-core = "0.1" tokio-retry = "0.1" uuid = { version = "0.7", features = ["serde", "v4"] } chrono = { version = "0.4.4", features = ["serde"] } +url = "1.7.2" -grin_wallet_util = { path = "../util", version = "2.0.1-beta.1" } - -grin_wallet_libwallet = { path = "../libwallet", version = "2.0.1-beta.1" } -grin_wallet_config = { path = "../config", version = "2.0.1-beta.1" } +grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" } +grin_wallet_config = { path = "../config", version = "2.1.0-beta.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "2.1.0-beta.1" } diff --git a/impls/src/adapters/file.rs b/impls/src/adapters/file.rs index d63024f0..04a9031b 100644 --- a/impls/src/adapters/file.rs +++ b/impls/src/adapters/file.rs @@ -16,32 +16,16 @@ use std::fs::File; use std::io::{Read, Write}; -use crate::config::WalletConfig; use crate::libwallet::{Error, ErrorKind, Slate}; -use crate::WalletCommAdapter; -use std::collections::HashMap; +use crate::{SlateGetter, SlatePutter}; +use std::path::PathBuf; #[derive(Clone)] -pub struct FileWalletCommAdapter {} +pub struct PathToSlate(pub PathBuf); -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)?; +impl SlatePutter for PathToSlate { + fn put_tx(&self, slate: &Slate) -> Result<(), Error> { + let mut pub_tx = File::create(&self.0)?; pub_tx.write_all( serde_json::to_string(slate) .map_err(|_| ErrorKind::SlateSer)? @@ -50,22 +34,13 @@ impl WalletCommAdapter for FileWalletCommAdapter { pub_tx.sync_all()?; Ok(()) } +} - fn receive_tx_async(&self, params: &str) -> Result { - let mut pub_tx_f = File::open(params)?; +impl SlateGetter for PathToSlate { + fn get_tx(&self) -> Result { + let mut pub_tx_f = File::open(&self.0)?; let mut content = String::new(); pub_tx_f.read_to_string(&mut content)?; Ok(Slate::deserialize_upgrade(&content)?) } - - fn listen( - &self, - _params: HashMap, - _config: WalletConfig, - _passphrase: &str, - _account: &str, - _node_api_secret: Option, - ) -> Result<(), Error> { - unimplemented!(); - } } diff --git a/impls/src/adapters/http.rs b/impls/src/adapters/http.rs index fbefcc89..32799073 100644 --- a/impls/src/adapters/http.rs +++ b/impls/src/adapters/http.rs @@ -15,23 +15,28 @@ /// HTTP Wallet 'plugin' implementation use crate::api; use crate::libwallet::{Error, ErrorKind, Slate}; -use crate::WalletCommAdapter; -use config::WalletConfig; +use crate::SlateSender; use serde::Serialize; use serde_json::{json, Value}; -use std::collections::HashMap; +use url::Url; #[derive(Clone)] -pub struct HTTPWalletCommAdapter {} +pub struct HttpSlateSender { + base_url: Url, +} -impl HTTPWalletCommAdapter { - /// Create - pub fn new() -> Box { - Box::new(HTTPWalletCommAdapter {}) +impl HttpSlateSender { + /// Create, return Err if scheme is not "http" + pub fn new(base_url: Url) -> Result { + if base_url.scheme() != "http" { + Err(SchemeNotHttp) + } else { + Ok(HttpSlateSender { base_url }) + } } - /// Check version of the other wallet - fn check_other_version(&self, url: &str) -> Result<(), Error> { + /// Check version of the listening wallet + fn check_other_version(&self) -> Result<(), Error> { let req = json!({ "jsonrpc": "2.0", "method": "check_version", @@ -39,7 +44,7 @@ impl HTTPWalletCommAdapter { "params": [] }); - let res: String = post(url, None, &req).map_err(|e| { + let res: String = post(&self.base_url, None, &req).map_err(|e| { let mut report = format!("Performing version check (is recipient listening?): {}", e); let err_string = format!("{}", e); if err_string.contains("404") { @@ -88,24 +93,15 @@ impl 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!("{}/v2/foreign", dest); +impl SlateSender for HttpSlateSender { + fn send_tx(&self, slate: &Slate) -> Result { + let url: Url = self + .base_url + .join("/v2/foreign") + .expect("/v2/foreign is an invalid url path"); debug!("Posting transaction slate to {}", url); - self.check_other_version(&url)?; + self.check_other_version()?; // Note: not using easy-jsonrpc as don't want the dependencies in this crate let req = json!({ @@ -120,7 +116,7 @@ impl WalletCommAdapter for HTTPWalletCommAdapter { }); trace!("Sending receive_tx request: {}", req); - let res: String = post(url.as_str(), None, &req).map_err(|e| { + let res: String = post(&url, None, &req).map_err(|e| { let report = format!("Posting transaction slate (is recipient listening?): {}", e); error!("{}", report); ErrorKind::ClientCallback(report) @@ -144,32 +140,24 @@ impl WalletCommAdapter for HTTPWalletCommAdapter { Ok(slate) } +} - fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> { - unimplemented!(); - } +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct SchemeNotHttp; - 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!(); +impl Into for SchemeNotHttp { + fn into(self) -> Error { + let err_str = format!("url scheme must be http",); + ErrorKind::GenericError(err_str).into() } } -pub fn post(url: &str, api_secret: Option, input: &IN) -> Result +pub fn post(url: &Url, api_secret: Option, input: &IN) -> Result where IN: Serialize, { - let req = api::client::create_post_request(url, api_secret, input)?; + // TODO: change create_post_request to accept a url instead of a &str + let req = api::client::create_post_request(url.as_str(), api_secret, input)?; let res = api::client::send_request(req)?; Ok(res) } diff --git a/impls/src/adapters/keybase.rs b/impls/src/adapters/keybase.rs index 79cf25bf..5b091952 100644 --- a/impls/src/adapters/keybase.rs +++ b/impls/src/adapters/keybase.rs @@ -14,11 +14,13 @@ // Keybase Wallet Plugin +use crate::adapters::{SlateReceiver, SlateSender}; use crate::config::WalletConfig; +use crate::keychain::ExtKeychain; use crate::libwallet::api_impl::foreign; -use crate::libwallet::{Error, ErrorKind, Slate}; -use crate::{instantiate_wallet, HTTPNodeClient, WalletCommAdapter}; -use failure::ResultExt; +use crate::libwallet::{Error, ErrorKind, Slate, WalletInst}; +use crate::util::ZeroingString; +use crate::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; use serde::Serialize; use serde_json::{from_str, json, to_string, Value}; use std::collections::{HashMap, HashSet}; @@ -36,25 +38,40 @@ const SLATE_NEW: &str = "grin_slate_new"; const SLATE_SIGNED: &str = "grin_slate_signed"; #[derive(Clone)] -pub struct KeybaseWalletCommAdapter {} +pub struct KeybaseChannel(String); -impl KeybaseWalletCommAdapter { +impl KeybaseChannel { /// Check if keybase is installed and return an adapter object. - pub fn new() -> Box { - let mut proc = if cfg!(target_os = "windows") { - Command::new("where") - } else { - Command::new("which") - }; - proc.arg("keybase") - .stdout(Stdio::null()) - .status() - .expect("Keybase executable not found, make sure it is installed and in your PATH"); + pub fn new(channel: String) -> Result { + // Limit only one recipient + if channel.matches(",").count() > 0 { + return Err( + ErrorKind::GenericError("Only one recipient is supported!".to_owned()).into(), + ); + } - Box::new(KeybaseWalletCommAdapter {}) + if !keybase_installed() { + return Err(ErrorKind::GenericError( + "Keybase executable not found, make sure it is installed and in your PATH" + .to_owned(), + ) + .into()); + } + + Ok(KeybaseChannel(channel)) } } +/// Check if keybase executable exists in path +fn keybase_installed() -> bool { + let mut proc = if cfg!(target_os = "windows") { + Command::new("where") + } else { + Command::new("which") + }; + proc.arg("keybase").stdout(Stdio::null()).status().is_ok() +} + /// Send a json object to the keybase process. Type `keybase chat api --help` for a list of available methods. fn api_send(payload: &str) -> Result { let mut proc = Command::new("keybase"); @@ -280,23 +297,13 @@ fn poll(nseconds: u64, channel: &str) -> Option { None } -impl WalletCommAdapter for KeybaseWalletCommAdapter { - fn supports_sync(&self) -> bool { - true - } - - // Send a slate to a keybase username then wait for a response for TTL seconds. - fn send_tx_sync(&self, addr: &str, slate: &Slate) -> Result { - // Limit only one recipient - if addr.matches(",").count() > 0 { - error!("Only one recipient is supported!"); - return Err(ErrorKind::GenericError("Tx rejected".to_owned()))?; - } - +impl SlateSender for KeybaseChannel { + /// Send a slate to a keybase username then wait for a response for TTL seconds. + fn send_tx(&self, slate: &Slate) -> Result { let id = slate.id; // Send original slate to recipient with the SLATE_NEW topic - match send(&slate, addr, SLATE_NEW, TTL) { + match send(&slate, &self.0, SLATE_NEW, TTL) { true => (), false => { return Err(ErrorKind::ClientCallback( @@ -304,9 +311,9 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter { ))?; } } - info!("tx request has been sent to @{}, tx uuid: {}", addr, id); + info!("tx request has been sent to @{}, tx uuid: {}", &self.0, id); // Wait for response from recipient with SLATE_SIGNED topic - match poll(TTL as u64, addr) { + match poll(TTL as u64, &self.0) { Some(slate) => return Ok(slate), None => { return Err(ErrorKind::ClientCallback( @@ -315,30 +322,55 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter { } } } +} - /// Send a transaction asynchronously (result will be returned via the listener) - fn send_tx_async(&self, _addr: &str, _slate: &Slate) -> Result<(), Error> { - unimplemented!(); - } - - /// Receive a transaction async. (Actually just read it from wherever and return the slate) - fn receive_tx_async(&self, _params: &str) -> Result { - unimplemented!(); +/// Receives slates on all channels with topic SLATE_NEW +pub struct KeybaseAllChannels { + _priv: (), // makes KeybaseAllChannels unconstructable without checking for existence of keybase executable +} + +impl KeybaseAllChannels { + /// Create a KeybaseAllChannels, return error if keybase executable is not present + pub fn new() -> Result { + if !keybase_installed() { + Err(ErrorKind::GenericError( + "Keybase executable not found, make sure it is installed and in your PATH" + .to_owned(), + ) + .into()) + } else { + Ok(KeybaseAllChannels { _priv: () }) + } } +} +impl SlateReceiver for KeybaseAllChannels { /// Start a listener, passing received messages to the wallet api directly #[allow(unreachable_code)] fn listen( &self, - _params: HashMap, config: WalletConfig, - passphrase: &str, + passphrase: ZeroingString, account: &str, node_api_secret: Option, ) -> Result<(), Error> { let node_client = HTTPNodeClient::new(&config.check_node_api_http_addr, node_api_secret); - let wallet = instantiate_wallet(config.clone(), node_client, passphrase, account) - .context(ErrorKind::WalletSeedDecryption)?; + let mut wallet = Box::new( + DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), + ) + as Box< + WalletInst< + 'static, + DefaultLCProvider, + HTTPNodeClient, + ExtKeychain, + >, + >; + let lc = wallet.lc_provider().unwrap(); + lc.set_wallet_directory(&config.data_file_dir); + lc.open_wallet(None, passphrase)?; + let wallet_inst = lc.wallet_inst()?; + wallet_inst.set_parent_key_id_by_name(account)?; info!("Listening for transactions on keybase ..."); loop { @@ -379,10 +411,8 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter { return Err(e); } let res = { - let mut w = wallet.lock(); - w.open_with_credentials()?; - let r = foreign::receive_tx(&mut *w, &slate, None, None, false); - w.close()?; + let r = + foreign::receive_tx(&mut **wallet_inst, &slate, None, None, false); r }; match res { diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index fa4ce0fd..369b50f8 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -15,41 +15,81 @@ mod file; mod http; mod keybase; -mod null; -pub use self::file::FileWalletCommAdapter; -pub use self::http::HTTPWalletCommAdapter; -pub use self::keybase::KeybaseWalletCommAdapter; -pub use self::null::NullWalletCommAdapter; +pub use self::file::PathToSlate; +pub use self::http::HttpSlateSender; +pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; use crate::config::WalletConfig; -use crate::libwallet::{Error, Slate}; -use std::collections::HashMap; - -/// Encapsulate wallet to wallet communication functions -pub trait WalletCommAdapter { - /// Whether this adapter supports sync mode - fn supports_sync(&self) -> bool; +use crate::libwallet::{Error, ErrorKind, Slate}; +use crate::util::ZeroingString; +/// Sends transactions to a corresponding SlateReceiver +pub trait SlateSender { /// 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; + fn send_tx(&self, slate: &Slate) -> Result; +} +pub trait SlateReceiver { /// 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, + passphrase: ZeroingString, account: &str, node_api_secret: Option, ) -> Result<(), Error>; } + +/// Posts slates to be read later by a corresponding getter +pub trait SlatePutter { + /// Send a transaction asynchronously + fn put_tx(&self, slate: &Slate) -> Result<(), Error>; +} + +/// Checks for a transaction from a corresponding SlatePutter, returns the transaction if it exists +pub trait SlateGetter { + /// Receive a transaction async. (Actually just read it from wherever and return the slate) + fn get_tx(&self) -> Result; +} + +/// select a SlateSender based on method and dest fields from, e.g., SendArgs +pub fn create_sender(method: &str, dest: &str) -> Result, Error> { + use url::Url; + + let invalid = || { + ErrorKind::WalletComms(format!( + "Invalid wallet comm type and destination. method: {}, dest: {}", + method, dest + )) + }; + Ok(match method { + "http" => { + let url: Url = dest.parse().map_err(|_| invalid())?; + Box::new(HttpSlateSender::new(url).map_err(|_| invalid())?) + } + "keybase" => Box::new(KeybaseChannel::new(dest.to_owned())?), + "self" => { + return Err(ErrorKind::WalletComms( + "No sender implementation for \"self\".".to_string(), + ) + .into()); + } + "file" => { + return Err(ErrorKind::WalletComms( + "File based transactions must be performed asynchronously.".to_string(), + ) + .into()); + } + _ => { + return Err(ErrorKind::WalletComms(format!( + "Wallet comm method \"{}\" does not exist.", + method + )) + .into()); + } + }) +} diff --git a/impls/src/adapters/null.rs b/impls/src/adapters/null.rs deleted file mode 100644 index 376133c0..00000000 --- a/impls/src/adapters/null.rs +++ /dev/null @@ -1,59 +0,0 @@ -// 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. - -use crate::config::WalletConfig; -/// Null Output 'plugin' implementation -use crate::libwallet::{Error, Slate}; -use crate::WalletCommAdapter; - -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 { - Ok(slate.clone()) - } - - fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> { - Ok(()) - } - - 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/impls/src/backends/lmdb.rs b/impls/src/backends/lmdb.rs index a0e8ebe2..fcfaf1ee 100644 --- a/impls/src/backends/lmdb.rs +++ b/impls/src/backends/lmdb.rs @@ -15,9 +15,10 @@ use std::cell::RefCell; use std::{fs, path}; -// for writing storedtransaction files +// for writing stored transaction files use std::fs::File; use std::io::{Read, Write}; +use std::marker::PhantomData; use std::path::Path; use failure::ResultExt; @@ -29,7 +30,7 @@ use crate::keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchComm use crate::store::{self, option_to_not_found, to_key, to_key_u64}; use crate::core::core::Transaction; -use crate::core::{global, ser}; +use crate::core::ser; use crate::libwallet::{check_repair, restore}; use crate::libwallet::{ AcctPathMapping, Context, Error, ErrorKind, NodeClient, OutputData, TxLogEntry, WalletBackend, @@ -37,9 +38,6 @@ use crate::libwallet::{ }; use crate::util; use crate::util::secp::constants::SECRET_KEY_SIZE; -use crate::util::ZeroingString; -use crate::WalletSeed; -use config::WalletConfig; pub const DB_DIR: &'static str = "db"; pub const TX_SAVE_DIR: &'static str = "saved_txs"; @@ -54,8 +52,8 @@ const ACCOUNT_PATH_MAPPING_PREFIX: u8 = 'a' as u8; /// test to see if database files exist in the current directory. If so, /// use a DB backend for all operations -pub fn wallet_db_exists(config: WalletConfig) -> bool { - let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR); +pub fn wallet_db_exists(data_file_dir: &str) -> bool { + let db_path = path::Path::new(data_file_dir).join(DB_DIR); db_path.exists() } @@ -92,25 +90,33 @@ where Ok((ret_blind, ret_nonce)) } -pub struct LMDBBackend { +pub struct LMDBBackend<'ck, C, K> +where + C: NodeClient + 'ck, + K: Keychain + 'ck, +{ db: store::Store, - config: WalletConfig, - /// passphrase: TODO better ways of dealing with this other than storing - passphrase: ZeroingString, + data_file_dir: String, /// Keychain pub keychain: Option, /// Parent path to use by default for output operations parent_key_id: Identifier, /// wallet to node client w2n_client: C, + ///phantom + _phantom: &'ck PhantomData, } -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); +impl<'ck, C, K> LMDBBackend<'ck, C, K> +where + C: NodeClient + 'ck, + K: Keychain + 'ck, +{ + pub fn new(data_file_dir: &str, n_client: C) -> Result { + let db_path = path::Path::new(data_file_dir).join(DB_DIR); fs::create_dir_all(&db_path).expect("Couldn't create wallet backend directory!"); - let stored_tx_path = path::Path::new(&config.data_file_dir).join(TX_SAVE_DIR); + let stored_tx_path = path::Path::new(data_file_dir).join(TX_SAVE_DIR); fs::create_dir_all(&stored_tx_path) .expect("Couldn't create wallet backend tx storage directory!"); @@ -136,11 +142,11 @@ impl LMDBBackend { let res = LMDBBackend { db: store, - config: config.clone(), - passphrase: ZeroingString::from(passphrase), + data_file_dir: data_file_dir.to_owned(), keychain: None, parent_key_id: LMDBBackend::::default_path(), w2n_client: n_client, + _phantom: &PhantomData, }; Ok(res) } @@ -154,38 +160,36 @@ impl LMDBBackend { /// Just test to see if database files exist in the current directory. If /// so, use a DB backend for all operations - pub fn exists(config: WalletConfig) -> bool { - let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR); + pub fn exists(data_file_dir: &str) -> bool { + let db_path = path::Path::new(data_file_dir).join(DB_DIR); db_path.exists() } } -impl WalletBackend for LMDBBackend +impl<'ck, C, K> WalletBackend<'ck, C, K> for LMDBBackend<'ck, C, K> where - C: NodeClient, - K: Keychain, + C: NodeClient + 'ck, + K: Keychain + 'ck, { - /// Initialise with whatever stored credentials we have - fn open_with_credentials(&mut self) -> Result<(), Error> { - let wallet_seed = WalletSeed::from_file(&self.config, &self.passphrase) - .context(ErrorKind::CallbackImpl("Error opening wallet"))?; - self.keychain = Some( - wallet_seed - .derive_keychain(global::is_floonet()) - .context(ErrorKind::CallbackImpl("Error deriving keychain"))?, - ); - Ok(()) + /// Set the keychain, which should already have been opened + fn set_keychain(&mut self, k: Box) { + self.keychain = Some(*k); } - /// Close wallet and remove any stored credentials (TBD) + /// Close wallet fn close(&mut self) -> Result<(), Error> { + //TODO: Ensure this is zeroed? self.keychain = None; Ok(()) } /// Return the keychain being used - fn keychain(&mut self) -> &mut K { - self.keychain.as_mut().unwrap() + fn keychain(&mut self) -> Result<&mut K, Error> { + if self.keychain.is_some() { + Ok(self.keychain.as_mut().unwrap()) + } else { + Err(ErrorKind::KeychainDoesntExist.into()) + } } /// Return the node client being used @@ -199,16 +203,18 @@ where amount: u64, id: &Identifier, ) -> Result, Error> { - if self.config.no_commit_cache == Some(true) { + //TODO: Check if this is really necessary, it's the only thing + //preventing removing the need for config in the wallet backend + /*if self.config.no_commit_cache == Some(true) { Ok(None) - } else { - Ok(Some(util::to_hex( - self.keychain() - .commit(amount, &id, &SwitchCommitmentType::Regular)? - .0 - .to_vec(), // TODO: proper support for different switch commitment schemes - ))) - } + } else {*/ + Ok(Some(util::to_hex( + self.keychain()? + .commit(amount, &id, &SwitchCommitmentType::Regular)? + .0 + .to_vec(), // TODO: proper support for different switch commitment schemes + ))) + /*}*/ } /// Set parent path by account name @@ -263,7 +269,7 @@ where &mut slate_id.to_vec(), participant_id as u64, ); - let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?; + let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain()?, slate_id)?; let mut ctx: Context = option_to_not_found( self.db.get_ser(&ctx_key), @@ -294,7 +300,7 @@ where fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error> { let filename = format!("{}.grintx", uuid); - let path = path::Path::new(&self.config.data_file_dir) + let path = path::Path::new(&self.data_file_dir) .join(TX_SAVE_DIR) .join(filename); let path_buf = Path::new(&path).to_path_buf(); @@ -310,7 +316,7 @@ where Some(f) => f, None => return Ok(None), }; - let path = path::Path::new(&self.config.data_file_dir) + let path = path::Path::new(&self.data_file_dir) .join(TX_SAVE_DIR) .join(filename); let tx_file = Path::new(&path).to_path_buf(); @@ -382,7 +388,7 @@ where C: NodeClient, K: Keychain, { - _store: &'a LMDBBackend, + _store: &'a LMDBBackend<'a, C, K>, db: RefCell>>, /// Keychain keychain: Option, diff --git a/impls/src/lib.rs b/impls/src/lib.rs index ee846562..e28bab22 100644 --- a/impls/src/lib.rs +++ b/impls/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2018 The Grin Developers +// Copyright 2019 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. @@ -29,40 +29,59 @@ use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_keychain as keychain; use grin_wallet_util::grin_store as store; use grin_wallet_util::grin_util as util; -extern crate grin_wallet_config as config; + +use grin_wallet_config as config; mod adapters; mod backends; mod error; +mod lifecycle; mod node_clients; -mod seed; pub mod test_framework; pub use crate::adapters::{ - FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter, NullWalletCommAdapter, - WalletCommAdapter, + create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, SlateGetter, + SlatePutter, SlateReceiver, SlateSender, }; pub use crate::backends::{wallet_db_exists, LMDBBackend}; pub use crate::error::{Error, ErrorKind}; +pub use crate::lifecycle::DefaultLCProvider; pub use crate::node_clients::HTTPNodeClient; -pub use crate::seed::{EncryptedWalletSeed, WalletSeed, SEED_FILE}; -use crate::util::Mutex; -use std::sync::Arc; +use crate::keychain::{ExtKeychain, Keychain}; -use libwallet::{NodeClient, WalletBackend, WalletInst}; +use libwallet::{NodeClient, WalletInst, WalletLCProvider}; -/// Helper to create an instance of the LMDB wallet -pub fn instantiate_wallet( - wallet_config: config::WalletConfig, - node_client: impl NodeClient + 'static, - passphrase: &str, - account: &str, -) -> Result>>, Error> { - // First test decryption, so we can abort early if we have the wrong password - let _ = WalletSeed::from_file(&wallet_config, passphrase)?; - let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, node_client)?; - db_wallet.set_parent_key_id_by_name(account)?; - info!("Using LMDB Backend for wallet"); - Ok(Arc::new(Mutex::new(db_wallet))) +/// Main wallet instance +pub struct DefaultWalletImpl<'a, C> +where + C: NodeClient + 'a, +{ + lc_provider: DefaultLCProvider<'a, C, ExtKeychain>, +} + +impl<'a, C> DefaultWalletImpl<'a, C> +where + C: NodeClient + 'a, +{ + pub fn new(node_client: C) -> Result { + let lc_provider = DefaultLCProvider::new(node_client); + Ok(DefaultWalletImpl { + lc_provider: lc_provider, + }) + } +} + +impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl<'a, C> +where + DefaultLCProvider<'a, C, ExtKeychain>: WalletLCProvider<'a, C, K>, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + fn lc_provider( + &mut self, + ) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), libwallet::Error> { + Ok(&mut self.lc_provider) + } } diff --git a/impls/src/lifecycle/default.rs b/impls/src/lifecycle/default.rs new file mode 100644 index 00000000..4341e24d --- /dev/null +++ b/impls/src/lifecycle/default.rs @@ -0,0 +1,230 @@ +// Copyright 2019 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. + +//! Default wallet lifecycle provider + +use crate::config::{config, GlobalWalletConfig, GRIN_WALLET_DIR}; +use crate::core::global; +use crate::keychain::Keychain; +use crate::libwallet::{Error, ErrorKind, NodeClient, WalletBackend, WalletLCProvider}; +use crate::lifecycle::seed::WalletSeed; +use crate::util::ZeroingString; +use crate::LMDBBackend; +use failure::ResultExt; +use std::path::PathBuf; + +pub struct DefaultLCProvider<'a, C, K> +where + C: NodeClient + 'a, + K: Keychain + 'a, +{ + data_dir: String, + node_client: C, + backend: Option + 'a>>, +} + +impl<'a, C, K> DefaultLCProvider<'a, C, K> +where + C: NodeClient + 'a, + K: Keychain + 'a, +{ + /// Create new provider + pub fn new(node_client: C) -> Self { + DefaultLCProvider { + node_client, + data_dir: "default".to_owned(), + backend: None, + } + } +} + +impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider<'a, C, K> +where + C: NodeClient + 'a, + K: Keychain + 'a, +{ + fn set_wallet_directory(&mut self, dir: &str) { + self.data_dir = dir.to_owned(); + } + + fn create_config(&self, chain_type: &global::ChainTypes, file_name: &str) -> Result<(), Error> { + let mut default_config = GlobalWalletConfig::for_chain(chain_type); + let mut config_file_name = PathBuf::from(self.data_dir.clone()); + config_file_name.push(file_name); + + let mut data_dir_name = PathBuf::from(self.data_dir.clone()); + data_dir_name.push(GRIN_WALLET_DIR); + + if config_file_name.exists() && data_dir_name.exists() { + let msg = format!( + "{} already exists in the target directory ({}). Please remove it first", + file_name, + config_file_name.to_str().unwrap() + ); + return Err(ErrorKind::Lifecycle(msg).into()); + } + + // just leave as is if file exists but there's no data dir + if config_file_name.exists() { + return Ok(()); + } + + default_config.update_paths(&PathBuf::from(self.data_dir.clone())); + let res = default_config.write_to_file(config_file_name.to_str().unwrap()); + if let Err(e) = res { + let msg = format!( + "Error creating config file as ({}): {}", + config_file_name.to_str().unwrap(), + e + ); + return Err(ErrorKind::Lifecycle(msg).into()); + } + + info!( + "File {} configured and created", + config_file_name.to_str().unwrap(), + ); + + let mut api_secret_path = PathBuf::from(self.data_dir.clone()); + api_secret_path.push(PathBuf::from(config::API_SECRET_FILE_NAME)); + if !api_secret_path.exists() { + config::init_api_secret(&api_secret_path).unwrap(); + } else { + config::check_api_secret(&api_secret_path).unwrap(); + } + + Ok(()) + } + + fn create_wallet( + &mut self, + _name: Option<&str>, + mnemonic: Option, + mnemonic_length: usize, + password: ZeroingString, + ) -> Result<(), Error> { + let mut data_dir_name = PathBuf::from(self.data_dir.clone()); + data_dir_name.push(GRIN_WALLET_DIR); + let data_dir_name = data_dir_name.to_str().unwrap(); + let _ = WalletSeed::init_file(&data_dir_name, mnemonic_length, mnemonic, password); + info!("Wallet seed file created"); + let _wallet: LMDBBackend<'a, C, K> = + match LMDBBackend::new(&data_dir_name, self.node_client.clone()) { + Err(e) => { + let msg = format!("Error creating wallet: {}, Data Dir: {}", e, &data_dir_name); + return Err(ErrorKind::Lifecycle(msg).into()); + } + Ok(d) => d, + }; + info!("Wallet database backend created at {}", data_dir_name); + Ok(()) + } + + fn open_wallet(&mut self, _name: Option<&str>, password: ZeroingString) -> Result<(), Error> { + let mut data_dir_name = PathBuf::from(self.data_dir.clone()); + data_dir_name.push(GRIN_WALLET_DIR); + let data_dir_name = data_dir_name.to_str().unwrap(); + let mut wallet: LMDBBackend<'a, C, K> = + match LMDBBackend::new(&data_dir_name, self.node_client.clone()) { + Err(e) => { + let msg = format!("Error opening wallet: {}, Data Dir: {}", e, &data_dir_name); + return Err(ErrorKind::Lifecycle(msg).into()); + } + Ok(d) => d, + }; + let wallet_seed = WalletSeed::from_file(&data_dir_name, password) + .context(ErrorKind::Lifecycle("Error opening wallet".into()))?; + let keychain = wallet_seed + .derive_keychain(global::is_floonet()) + .context(ErrorKind::Lifecycle("Error deriving keychain".into()))?; + wallet.set_keychain(Box::new(keychain)); + self.backend = Some(Box::new(wallet)); + Ok(()) + } + + fn close_wallet(&mut self, _name: Option<&str>) -> Result<(), Error> { + match self.backend.as_mut() { + Some(b) => b.close()?, + None => {} + }; + self.backend = None; + Ok(()) + } + + fn wallet_exists(&self, _name: Option<&str>) -> Result { + let mut data_dir_name = PathBuf::from(self.data_dir.clone()); + data_dir_name.push(GRIN_WALLET_DIR); + let data_dir_name = data_dir_name.to_str().unwrap(); + let res = WalletSeed::seed_file_exists(&data_dir_name).context(ErrorKind::CallbackImpl( + "Error checking for wallet existence", + ))?; + Ok(res) + } + + fn get_mnemonic( + &self, + _name: Option<&str>, + password: ZeroingString, + ) -> Result { + let mut data_dir_name = PathBuf::from(self.data_dir.clone()); + data_dir_name.push(GRIN_WALLET_DIR); + let data_dir_name = data_dir_name.to_str().unwrap(); + let wallet_seed = WalletSeed::from_file(&data_dir_name, password).context( + ErrorKind::Lifecycle("Error opening wallet seed file".into()), + )?; + let res = wallet_seed + .to_mnemonic() + .context(ErrorKind::Lifecycle("Error recovering wallet seed".into()))?; + Ok(ZeroingString::from(res)) + } + + fn validate_mnemonic(&self, mnemonic: ZeroingString) -> Result<(), Error> { + match WalletSeed::from_mnemonic(mnemonic) { + Ok(_) => Ok(()), + Err(_) => Err(ErrorKind::GenericError("Validating mnemonic".into()))?, + } + } + + fn recover_from_mnemonic( + &self, + mnemonic: ZeroingString, + password: ZeroingString, + ) -> Result<(), Error> { + let mut data_dir_name = PathBuf::from(self.data_dir.clone()); + data_dir_name.push(GRIN_WALLET_DIR); + let data_dir_name = data_dir_name.to_str().unwrap(); + WalletSeed::recover_from_phrase(data_dir_name, mnemonic, password).context( + ErrorKind::Lifecycle("Error recovering from mnemonic".into()), + )?; + Ok(()) + } + + fn change_password(&self, _old: String, _new: String) -> Result<(), Error> { + unimplemented!() + } + + fn delete_wallet(&self, _name: Option, _password: String) -> Result<(), Error> { + unimplemented!() + } + + fn wallet_inst(&mut self) -> Result<&mut Box + 'a>, Error> { + match self.backend.as_mut() { + None => { + let msg = "Wallet has not been opened".into(); + Err(ErrorKind::Lifecycle(msg).into()) + } + Some(_) => Ok(&mut *self.backend.as_mut().unwrap()), + } + } +} diff --git a/impls/src/lifecycle/mod.rs b/impls/src/lifecycle/mod.rs new file mode 100644 index 00000000..511d876d --- /dev/null +++ b/impls/src/lifecycle/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2019 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 default; +mod seed; + +pub use self::default::DefaultLCProvider; diff --git a/impls/src/seed.rs b/impls/src/lifecycle/seed.rs similarity index 69% rename from impls/src/seed.rs rename to impls/src/lifecycle/seed.rs index 485f5e60..fc272060 100644 --- a/impls/src/seed.rs +++ b/impls/src/lifecycle/seed.rs @@ -27,7 +27,6 @@ use ring::{digest, pbkdf2}; use crate::keychain::{mnemonic, Keychain}; use crate::util; use crate::{Error, ErrorKind}; -use config::WalletConfig; use failure::ResultExt; pub const SEED_FILE: &'static str = "wallet.seed"; @@ -40,21 +39,21 @@ impl WalletSeed { WalletSeed(bytes.to_vec()) } - pub fn from_mnemonic(word_list: &str) -> Result { - let res = mnemonic::to_entropy(word_list); + pub fn from_mnemonic(word_list: util::ZeroingString) -> Result { + let res = mnemonic::to_entropy(&word_list); match res { Ok(s) => Ok(WalletSeed::from_bytes(&s)), Err(_) => Err(ErrorKind::Mnemonic.into()), } } - pub fn from_hex(hex: &str) -> Result { + pub fn _from_hex(hex: &str) -> Result { let bytes = util::from_hex(hex.to_string()) .context(ErrorKind::GenericError("Invalid hex".to_owned()))?; Ok(WalletSeed::from_bytes(&bytes)) } - pub fn to_hex(&self) -> String { + pub fn _to_hex(&self) -> String { util::to_hex(self.0.to_vec()) } @@ -66,7 +65,7 @@ impl WalletSeed { } } - pub fn derive_keychain_old(old_wallet_seed: [u8; 32], password: &str) -> Vec { + pub fn _derive_keychain_old(old_wallet_seed: [u8; 32], password: &str) -> Vec { let seed = blake2::blake2b::blake2b(64, password.as_bytes(), &old_wallet_seed); seed.as_bytes().to_vec() } @@ -85,35 +84,27 @@ impl WalletSeed { WalletSeed(seed) } - pub fn seed_file_exists(wallet_config: &WalletConfig) -> Result<(), Error> { - let seed_file_path = &format!( - "{}{}{}", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, - ); + pub fn seed_file_exists(data_file_dir: &str) -> Result { + let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,); + println!("Seed file path: {}", seed_file_path); if Path::new(seed_file_path).exists() { - return Err(ErrorKind::WalletSeedExists(seed_file_path.to_owned()))?; + Ok(true) + } else { + Ok(false) } - Ok(()) } - pub fn backup_seed(wallet_config: &WalletConfig) -> Result<(), Error> { - let seed_file_name = &format!( - "{}{}{}", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, - ); + pub fn backup_seed(data_file_dir: &str) -> Result<(), Error> { + let seed_file_name = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,); let mut path = Path::new(seed_file_name).to_path_buf(); path.pop(); - let mut backup_seed_file_name = format!( - "{}{}{}.bak", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE - ); + let mut backup_seed_file_name = + format!("{}{}{}.bak", data_file_dir, MAIN_SEPARATOR, SEED_FILE); let mut i = 1; while Path::new(&backup_seed_file_name).exists() { - backup_seed_file_name = format!( - "{}{}{}.bak.{}", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, i - ); + backup_seed_file_name = + format!("{}{}{}.bak.{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE, i); i += 1; } path.push(backup_seed_file_name.clone()); @@ -127,20 +118,19 @@ impl WalletSeed { } pub fn recover_from_phrase( - wallet_config: &WalletConfig, - word_list: &str, - password: &str, + data_file_dir: &str, + word_list: util::ZeroingString, + password: util::ZeroingString, ) -> Result<(), Error> { - let seed_file_path = &format!( - "{}{}{}", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, - ); - if WalletSeed::seed_file_exists(wallet_config).is_err() { - WalletSeed::backup_seed(wallet_config)?; + let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,); + println!("data file dir: {}", data_file_dir); + if let Ok(true) = WalletSeed::seed_file_exists(data_file_dir) { + println!("seed file exists"); + WalletSeed::backup_seed(data_file_dir)?; } - if !Path::new(&wallet_config.data_file_dir).exists() { + if !Path::new(&data_file_dir).exists() { return Err(ErrorKind::WalletDoesntExist( - wallet_config.data_file_dir.clone(), + data_file_dir.to_owned(), "To create a new wallet from a recovery phrase, use 'grin-wallet init -r'" .to_owned(), ))?; @@ -155,34 +145,22 @@ impl WalletSeed { Ok(()) } - pub fn show_recovery_phrase(&self) -> Result<(), Error> { - println!("Your recovery phrase is:"); - println!(); - println!("{}", self.to_mnemonic()?); - println!(); - println!("Please back-up these words in a non-digital format."); - Ok(()) - } - pub fn init_file( - wallet_config: &WalletConfig, + data_file_dir: &str, seed_length: usize, recovery_phrase: Option, - password: &str, + password: util::ZeroingString, ) -> Result { // create directory if it doesn't exist - fs::create_dir_all(&wallet_config.data_file_dir).context(ErrorKind::IO)?; + fs::create_dir_all(data_file_dir).context(ErrorKind::IO)?; - let seed_file_path = &format!( - "{}{}{}", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, - ); + let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,); warn!("Generating wallet seed file at: {}", seed_file_path); - let _ = WalletSeed::seed_file_exists(wallet_config)?; + let _ = WalletSeed::seed_file_exists(data_file_dir)?; let seed = match recovery_phrase { - Some(p) => WalletSeed::from_mnemonic(&p)?, + Some(p) => WalletSeed::from_mnemonic(p)?, None => WalletSeed::init_new(seed_length), }; @@ -191,18 +169,18 @@ impl WalletSeed { let mut file = File::create(seed_file_path).context(ErrorKind::IO)?; file.write_all(&enc_seed_json.as_bytes()) .context(ErrorKind::IO)?; - seed.show_recovery_phrase()?; Ok(seed) } - pub fn from_file(wallet_config: &WalletConfig, password: &str) -> Result { + pub fn from_file( + data_file_dir: &str, + password: util::ZeroingString, + ) -> Result { + // TODO: Is this desirable any more? // create directory if it doesn't exist - fs::create_dir_all(&wallet_config.data_file_dir).context(ErrorKind::IO)?; + fs::create_dir_all(data_file_dir).context(ErrorKind::IO)?; - let seed_file_path = &format!( - "{}{}{}", - wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, - ); + let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,); debug!("Using wallet seed file at: {}", seed_file_path); @@ -212,7 +190,7 @@ impl WalletSeed { file.read_to_string(&mut buffer).context(ErrorKind::IO)?; let enc_seed: EncryptedWalletSeed = serde_json::from_str(&buffer).context(ErrorKind::Format)?; - let wallet_seed = enc_seed.decrypt(password)?; + let wallet_seed = enc_seed.decrypt(&password)?; Ok(wallet_seed) } else { error!( @@ -240,7 +218,10 @@ pub struct EncryptedWalletSeed { impl EncryptedWalletSeed { /// Create a new encrypted seed from the given seed + password - pub fn from_seed(seed: &WalletSeed, password: &str) -> Result { + pub fn from_seed( + seed: &WalletSeed, + password: util::ZeroingString, + ) -> Result { let salt: [u8; 8] = thread_rng().gen(); let nonce: [u8; 12] = thread_rng().gen(); let password = password.as_bytes(); @@ -289,3 +270,28 @@ impl EncryptedWalletSeed { Ok(WalletSeed::from_bytes(&decrypted_data)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::ZeroingString; + #[test] + fn wallet_seed_encrypt() { + let password = ZeroingString::from("passwoid"); + let wallet_seed = WalletSeed::init_new(32); + let mut enc_wallet_seed = + EncryptedWalletSeed::from_seed(&wallet_seed, password.clone()).unwrap(); + println!("EWS: {:?}", enc_wallet_seed); + let decrypted_wallet_seed = enc_wallet_seed.decrypt(&password).unwrap(); + assert_eq!(wallet_seed, decrypted_wallet_seed); + + // Wrong password + let decrypted_wallet_seed = enc_wallet_seed.decrypt(""); + assert!(decrypted_wallet_seed.is_err()); + + // Wrong nonce + enc_wallet_seed.nonce = "wrongnonce".to_owned(); + let decrypted_wallet_seed = enc_wallet_seed.decrypt(&password); + assert!(decrypted_wallet_seed.is_err()); + } +} diff --git a/impls/src/test_framework/mod.rs b/impls/src/test_framework/mod.rs index 948f9a0e..b45e02e4 100644 --- a/impls/src/test_framework/mod.rs +++ b/impls/src/test_framework/mod.rs @@ -15,7 +15,6 @@ use crate::api; use crate::chain; use crate::chain::Chain; -use crate::config::WalletConfig; use crate::core; use crate::core::core::{OutputFeatures, OutputIdentifier, Transaction}; use crate::core::{consensus, global, pow}; @@ -23,13 +22,10 @@ use crate::keychain; use crate::libwallet; use crate::libwallet::api_impl::{foreign, owner}; use crate::libwallet::{ - BlockFees, CbData, InitTxArgs, NodeClient, WalletBackend, WalletInfo, WalletInst, + BlockFees, CbData, InitTxArgs, NodeClient, WalletInfo, WalletInst, WalletLCProvider, }; -use crate::util; use crate::util::secp::pedersen; use crate::util::Mutex; -use crate::LMDBBackend; -use crate::WalletSeed; use chrono::Duration; use std::sync::Arc; use std::thread; @@ -105,14 +101,15 @@ 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<'a, L, C, K>( chain: &Chain, txs: Vec<&Transaction>, - wallet: Arc>>, + wallet: Arc + 'a>>>, ) -> Result<(), libwallet::Error> where - C: NodeClient, - K: keychain::Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, { // build block fees let prev = chain.head_header().unwrap(); @@ -124,10 +121,9 @@ where }; // build coinbase (via api) and add block let coinbase_tx = { - let mut w = wallet.lock(); - w.open_with_credentials()?; - let res = foreign::build_coinbase(&mut *w, &block_fees, false)?; - w.close()?; + let mut w_lock = wallet.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let res = foreign::build_coinbase(&mut **w, &block_fees, false)?; res }; add_block_with_reward(chain, txs, coinbase_tx.clone()); @@ -135,15 +131,16 @@ where } /// Award a blocks to a wallet directly -pub fn award_blocks_to_wallet( +pub fn award_blocks_to_wallet<'a, L, C, K>( chain: &Chain, - wallet: Arc>>, + wallet: Arc + 'a>>>, number: usize, pause_between: bool, ) -> Result<(), libwallet::Error> where - C: NodeClient, - K: keychain::Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, { for _ in 0..number { award_block_to_wallet(chain, vec![], wallet.clone())?; @@ -154,50 +151,22 @@ where Ok(()) } -/// dispatch a db wallet -pub fn create_wallet( - dir: &str, - n_client: C, - rec_phrase: Option<&str>, -) -> Arc>> -where - C: NodeClient + 'static, - K: keychain::Keychain + 'static, -{ - let z_string = match rec_phrase { - Some(s) => Some(util::ZeroingString::from(s)), - None => None, - }; - let mut wallet_config = WalletConfig::default(); - wallet_config.data_file_dir = String::from(dir); - let _ = WalletSeed::init_file(&wallet_config, 32, z_string, ""); - 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!( - "Error initializing wallet: {:?} Config: {:?}", - e, wallet_config - ) - }); - Arc::new(Mutex::new(wallet)) -} - /// send an amount to a destination -pub fn send_to_dest( - wallet: Arc>>, +pub fn send_to_dest<'a, L, C, K>( + wallet: Arc>>>, client: LocalWalletClient, dest: &str, amount: u64, test_mode: bool, ) -> Result<(), libwallet::Error> where - T: WalletBackend, - C: NodeClient, - K: keychain::Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, { let slate = { - let mut w = wallet.lock(); - w.open_with_credentials()?; + let mut w_lock = wallet.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; let args = InitTxArgs { src_acct_name: None, amount, @@ -207,15 +176,15 @@ where selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = owner::init_send_tx(&mut *w, args, test_mode)?; + let slate_i = owner::init_send_tx(&mut **w, args, test_mode)?; let slate = client.send_tx_slate_direct(dest, &slate_i)?; - owner::tx_lock_outputs(&mut *w, &slate, 0)?; - let slate = owner::finalize_tx(&mut *w, &slate)?; - w.close()?; + owner::tx_lock_outputs(&mut **w, &slate, 0)?; + let slate = owner::finalize_tx(&mut **w, &slate)?; slate }; let client = { - let mut w = wallet.lock(); + let mut w_lock = wallet.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; w.w2n_client().clone() }; owner::post_tx(&client, &slate.tx, false)?; // mines a block @@ -223,18 +192,17 @@ where } /// get wallet info totals -pub fn wallet_info( - wallet: Arc>>, +pub fn wallet_info<'a, L, C, K>( + wallet: Arc>>>, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: keychain::Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: keychain::Keychain + 'a, { - let mut w = wallet.lock(); - w.open_with_credentials()?; - let (wallet_refreshed, wallet_info) = owner::retrieve_summary_info(&mut *w, true, 1)?; + let mut w_lock = wallet.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let (wallet_refreshed, wallet_info) = owner::retrieve_summary_info(&mut **w, true, 1)?; assert!(wallet_refreshed); - w.close()?; Ok(wallet_info) } diff --git a/impls/src/test_framework/testclient.rs b/impls/src/test_framework/testclient.rs index d23f139b..b63fcfe0 100644 --- a/impls/src/test_framework/testclient.rs +++ b/impls/src/test_framework/testclient.rs @@ -19,23 +19,23 @@ use crate::api; use crate::chain::types::NoopAdapter; use crate::chain::Chain; -use crate::config::WalletConfig; use crate::core::core::verifier_cache::LruVerifierCache; use crate::core::core::Transaction; use crate::core::global::{set_mining_mode, ChainTypes}; use crate::core::{pow, ser}; use crate::keychain::Keychain; +use crate::libwallet; use crate::libwallet::api_impl::foreign; -use crate::libwallet::{NodeClient, NodeVersionInfo, Slate, TxWrapper, WalletInst}; +use crate::libwallet::{ + NodeClient, NodeVersionInfo, Slate, TxWrapper, WalletInst, WalletLCProvider, +}; use crate::util; use crate::util::secp::pedersen; use crate::util::secp::pedersen::Commitment; use crate::util::{Mutex, RwLock}; -use crate::{libwallet, WalletCommAdapter}; use failure::ResultExt; use serde_json; use std::collections::HashMap; -use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::Arc; @@ -57,10 +57,11 @@ pub struct WalletProxyMessage { /// communicates with a chain instance or other wallet /// listener APIs via message queues -pub struct WalletProxy +pub struct WalletProxy<'a, L, C, K> where - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { /// directory to create the chain in pub chain_dir: String, @@ -71,7 +72,7 @@ where String, ( Sender, - Arc>>, + Arc + 'a>>>, ), >, /// simulate json send to another client @@ -81,16 +82,13 @@ where pub rx: Receiver, /// queue control pub running: Arc, - /// Phantom - phantom_c: PhantomData, - /// Phantom - phantom_k: PhantomData, } -impl WalletProxy +impl<'a, L, C, K> WalletProxy<'a, L, C, K> where - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { /// Create a new client that will communicate with the given grin node pub fn new(chain_dir: &str) -> Self { @@ -115,8 +113,6 @@ where rx: rx, wallets: HashMap::new(), running: Arc::new(AtomicBool::new(false)), - phantom_c: PhantomData, - phantom_k: PhantomData, }; retval } @@ -126,7 +122,7 @@ where &mut self, addr: &str, tx: Sender, - wallet: Arc>>, + wallet: Arc + 'a>>>, ) { self.wallets.insert(addr.to_owned(), (tx, wallet)); } @@ -212,11 +208,10 @@ where )?; ; { - let mut w = wallet.1.lock(); - w.open_with_credentials()?; + let mut w_lock = wallet.1.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; // receive tx - slate = foreign::receive_tx(&mut *w, &slate, None, None, false)?; - w.close()?; + slate = foreign::receive_tx(&mut **w, &slate, None, None, false)?; } Ok(WalletProxyMessage { @@ -344,55 +339,6 @@ impl 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".to_owned(), - ))?; - } - 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".to_owned(), - ))?, - ) - } - - 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" @@ -535,3 +481,10 @@ impl NodeClient for LocalWalletClient { Ok((o.highest_index, o.last_retrieved_index, api_outputs)) } } +unsafe impl<'a, L, C, K> Send for WalletProxy<'a, L, C, K> +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ +} diff --git a/impls/tests/encryption.rs b/impls/tests/encryption.rs deleted file mode 100644 index 78a1b388..00000000 --- a/impls/tests/encryption.rs +++ /dev/null @@ -1,36 +0,0 @@ -// 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. - -//! Wallet seed encryption tests -extern crate grin_wallet_impls as impls; - -use impls::{EncryptedWalletSeed, WalletSeed}; - -#[test] -fn wallet_seed_encrypt() { - let password = "passwoid"; - let wallet_seed = WalletSeed::init_new(32); - let mut enc_wallet_seed = EncryptedWalletSeed::from_seed(&wallet_seed, password).unwrap(); - println!("EWS: {:?}", enc_wallet_seed); - let decrypted_wallet_seed = enc_wallet_seed.decrypt(password).unwrap(); - assert_eq!(wallet_seed, decrypted_wallet_seed); - - // Wrong password - let decrypted_wallet_seed = enc_wallet_seed.decrypt(""); - assert!(decrypted_wallet_seed.is_err()); - - // Wrong nonce - enc_wallet_seed.nonce = "wrongnonce".to_owned(); - let decrypted_wallet_seed = enc_wallet_seed.decrypt(password); - assert!(decrypted_wallet_seed.is_err()); -} diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index 68011722..04429311 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_libwallet" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -25,7 +25,7 @@ lazy_static = "1" strum = "0.15" strum_macros = "0.15" -grin_wallet_util = { path = "../util", version = "2.0.1-beta.1" } +grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" } [dev-dependencies] -grin_wallet_config = { path = "../config", version = "2.0.1-beta.1" } +grin_wallet_config = { path = "../config", version = "2.1.0-beta.1" } diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index e49d1ecd..dc2a0512 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -35,15 +35,15 @@ pub fn check_version() -> VersionInfo { } /// Build a coinbase transaction -pub fn build_coinbase( +pub fn build_coinbase<'a, T: ?Sized, C, K>( w: &mut T, block_fees: &BlockFees, test_mode: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { updater::build_coinbase(&mut *w, block_fees, test_mode) } @@ -54,7 +54,7 @@ pub fn verify_slate_messages(slate: &Slate) -> Result<(), Error> { } /// Receive a tx as recipient -pub fn receive_tx( +pub fn receive_tx<'a, T: ?Sized, C, K>( w: &mut T, slate: &Slate, dest_acct_name: Option<&str>, @@ -62,9 +62,9 @@ pub fn receive_tx( use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut ret_slate = slate.clone(); let parent_key_id = match dest_acct_name { @@ -113,11 +113,11 @@ where } /// Receive an tx that this wallet has issued -pub fn finalize_invoice_tx(w: &mut T, slate: &Slate) -> Result +pub fn finalize_invoice_tx<'a, T: ?Sized, C, K>(w: &mut T, slate: &Slate) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut sl = slate.clone(); let context = w.get_private_context(sl.id.as_bytes(), 1)?; diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index c580ecc6..3cf38df6 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -33,46 +33,46 @@ use crate::{ const USER_MESSAGE_MAX_LEN: usize = 256; /// List of accounts -pub fn accounts(w: &mut T) -> Result, Error> +pub fn accounts<'a, T: ?Sized, C, K>(w: &mut T) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { keys::accounts(&mut *w) } /// new account path -pub fn create_account_path(w: &mut T, label: &str) -> Result +pub fn create_account_path<'a, T: ?Sized, C, K>(w: &mut T, label: &str) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { keys::new_acct_path(&mut *w, label) } /// set active account -pub fn set_active_account(w: &mut T, label: &str) -> Result<(), Error> +pub fn set_active_account<'a, T: ?Sized, C, K>(w: &mut T, label: &str) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { w.set_parent_key_id_by_name(label) } /// retrieve outputs -pub fn retrieve_outputs( +pub fn retrieve_outputs<'a, T: ?Sized, C, K>( w: &mut T, include_spent: bool, refresh_from_node: bool, tx_id: Option, ) -> Result<(bool, Vec), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = w.parent_key_id(); @@ -88,16 +88,16 @@ where } /// Retrieve txs -pub fn retrieve_txs( +pub fn retrieve_txs<'a, T: ?Sized, C, K>( w: &mut T, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, ) -> Result<(bool, Vec), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = w.parent_key_id(); @@ -113,15 +113,15 @@ where } /// Retrieve summary info -pub fn retrieve_summary_info( +pub fn retrieve_summary_info<'a, T: ?Sized, C, K>( w: &mut T, refresh_from_node: bool, minimum_confirmations: u64, ) -> Result<(bool, WalletInfo), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = w.parent_key_id(); @@ -135,15 +135,15 @@ where } /// Initiate tx as sender -pub fn init_send_tx( +pub fn init_send_tx<'a, T: ?Sized, C, K>( w: &mut T, args: InitTxArgs, use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = match args.src_acct_name { Some(d) => { @@ -211,15 +211,15 @@ where } /// Initiate a transaction as the recipient (invoicing) -pub fn issue_invoice_tx( +pub fn issue_invoice_tx<'a, T: ?Sized, C, K>( w: &mut T, args: IssueInvoiceTxArgs, use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = match args.dest_acct_name { Some(d) => { @@ -268,16 +268,16 @@ where /// Receive an invoice tx, essentially adding inputs to whatever /// output was specified -pub fn process_invoice_tx( +pub fn process_invoice_tx<'a, T: ?Sized, C, K>( w: &mut T, slate: &Slate, args: InitTxArgs, use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut ret_slate = slate.clone(); let parent_key_id = match args.src_acct_name { @@ -345,26 +345,26 @@ where } /// Lock sender outputs -pub fn tx_lock_outputs( +pub fn tx_lock_outputs<'a, T: ?Sized, C, K>( w: &mut T, slate: &Slate, participant_id: usize, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let context = w.get_private_context(slate.id.as_bytes(), participant_id)?; selection::lock_tx_context(&mut *w, slate, &context) } /// Finalize slate -pub fn finalize_tx(w: &mut T, slate: &Slate) -> Result +pub fn finalize_tx<'a, T: ?Sized, C, K>(w: &mut T, slate: &Slate) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut sl = slate.clone(); let context = w.get_private_context(sl.id.as_bytes(), 0)?; @@ -380,15 +380,15 @@ where } /// cancel tx -pub fn cancel_tx( +pub fn cancel_tx<'a, T: ?Sized, C, K>( w: &mut T, tx_id: Option, tx_slate_id: Option, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = w.parent_key_id(); if !update_outputs(w, false) { @@ -400,23 +400,23 @@ where } /// get stored tx -pub fn get_stored_tx( +pub fn get_stored_tx<'a, T: ?Sized, C, K>( w: &T, entry: &TxLogEntry, ) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { w.get_stored_tx(entry) } /// Posts a transaction to the chain /// take a client impl instead of wallet so as not to have to lock the wallet -pub fn post_tx(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error> +pub fn post_tx<'a, C>(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error> where - C: NodeClient, + C: NodeClient + 'a, { let tx_hex = grin_util::to_hex(ser::ser_vec(tx).unwrap()); let res = client.post_tx(&TxWrapper { tx_hex: tx_hex }, fluff); @@ -439,32 +439,32 @@ pub fn verify_slate_messages(slate: &Slate) -> Result<(), Error> { } /// Attempt to restore contents of wallet -pub fn restore(w: &mut T) -> Result<(), Error> +pub fn restore<'a, T: ?Sized, C, K>(w: &mut T) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { w.restore() } /// check repair -pub fn check_repair(w: &mut T, delete_unconfirmed: bool) -> Result<(), Error> +pub fn check_repair<'a, T: ?Sized, C, K>(w: &mut T, delete_unconfirmed: bool) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { update_outputs(w, true); w.check_repair(delete_unconfirmed) } /// node height -pub fn node_height(w: &mut T) -> Result +pub fn node_height<'a, T: ?Sized, C, K>(w: &mut T) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let res = w.w2n_client().get_chain_height(); match res { @@ -487,11 +487,11 @@ where } /// Attempt to update outputs in wallet, return whether it was successful -fn update_outputs(w: &mut T, update_all: bool) -> bool +fn update_outputs<'a, T: ?Sized, C, K>(w: &mut T, update_all: bool) -> bool where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = w.parent_key_id(); match updater::refresh_outputs(&mut *w, &parent_key_id, update_all) { diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 52f1aeb8..a7595d61 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -197,6 +197,14 @@ pub enum ErrorKind { #[fail(display = "Compatibility Error: {}", _0)] Compatibility(String), + /// Keychain doesn't exist (wallet not openend) + #[fail(display = "Keychain doesn't exist (has wallet been opened?)")] + KeychainDoesntExist, + + /// Lifecycle Error + #[fail(display = "Lifecycle Error: {}", _0)] + Lifecycle(String), + /// Other #[fail(display = "Generic error: {}", _0)] GenericError(String), diff --git a/libwallet/src/internal/keys.rs b/libwallet/src/internal/keys.rs index bc226f0c..1466b9bf 100644 --- a/libwallet/src/internal/keys.rs +++ b/libwallet/src/internal/keys.rs @@ -18,26 +18,26 @@ use crate::grin_keychain::{ChildNumber, ExtKeychain, Identifier, Keychain}; use crate::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<'a, T: ?Sized, C, K>(wallet: &mut T) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let child = wallet.next_child()?; Ok(child) } /// Retrieve an existing key from a wallet -pub fn retrieve_existing_key( +pub fn retrieve_existing_key<'a, T: ?Sized, C, K>( wallet: &T, key_id: Identifier, mmr_index: Option, ) -> Result<(Identifier, u32), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let existing = wallet.get(&key_id, &mmr_index)?; let key_id = existing.key_id.clone(); @@ -46,21 +46,21 @@ where } /// Returns a list of account to BIP32 path mappings -pub fn accounts(wallet: &mut T) -> Result, Error> +pub fn accounts<'a, T: ?Sized, C, K>(wallet: &mut T) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { 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<'a, T: ?Sized, C, K>(wallet: &mut T, label: &str) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let label = label.to_owned(); if let Some(_) = wallet.acct_path_iter().find(|l| l.label == label) { @@ -97,15 +97,15 @@ where } /// Adds/sets a particular account path with a given label -pub fn set_acct_path( +pub fn set_acct_path<'a, T: ?Sized, C, K>( wallet: &mut T, label: &str, path: &Identifier, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let label = label.to_owned(); let save_path = AcctPathMapping { diff --git a/libwallet/src/internal/restore.rs b/libwallet/src/internal/restore.rs index 0461f494..cc71ceb1 100644 --- a/libwallet/src/internal/restore.rs +++ b/libwallet/src/internal/restore.rs @@ -58,14 +58,14 @@ struct RestoredTxStats { pub num_outputs: usize, } -fn identify_utxo_outputs( +fn identify_utxo_outputs<'a, T, C, K>( wallet: &mut T, outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, ) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut wallet_outputs: Vec = Vec::new(); @@ -74,7 +74,7 @@ where outputs.len(), ); - let keychain = wallet.keychain(); + let keychain = wallet.keychain()?; let legacy_builder = proof::LegacyProofBuilder::new(keychain); let builder = proof::ProofBuilder::new(keychain); let legacy_version = HeaderVersion(1); @@ -136,11 +136,11 @@ where Ok(wallet_outputs) } -fn collect_chain_outputs(wallet: &mut T) -> Result, Error> +fn collect_chain_outputs<'a, T, C, K>(wallet: &mut T) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let batch_size = 1000; let mut start_index = 1; @@ -167,16 +167,16 @@ where } /// -fn restore_missing_output( +fn restore_missing_output<'a, T, C, K>( wallet: &mut T, output: OutputResult, found_parents: &mut HashMap, tx_stats: &mut Option<&mut HashMap>, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let commit = wallet.calc_commit_for_cache(output.value, &output.key_id)?; let mut batch = wallet.batch()?; @@ -250,11 +250,11 @@ where } /// -fn cancel_tx_log_entry(wallet: &mut T, output: &OutputData) -> Result<(), Error> +fn cancel_tx_log_entry<'a, T, C, K>(wallet: &mut T, output: &OutputData) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let parent_key_id = output.key_id.parent_path(); let updated_tx_entry = if output.tx_log_entry.is_some() { @@ -290,11 +290,11 @@ where /// Check / repair wallet contents /// assume wallet contents have been freshly updated with contents /// of latest block -pub fn check_repair(wallet: &mut T, delete_unconfirmed: bool) -> Result<(), Error> +pub fn check_repair<'a, T, C, K>(wallet: &mut T, delete_unconfirmed: bool) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // First, get a definitive list of outputs we own from the chain warn!("Starting wallet check."); @@ -412,11 +412,11 @@ where } /// Restore a wallet -pub fn restore(wallet: &mut T) -> Result<(), Error> +pub fn restore<'a, T, C, K>(wallet: &mut T) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // Don't proceed if wallet_data has anything in it let is_empty = wallet.iter().next().is_none(); diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 46cc9e05..09e1c0bd 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -32,7 +32,7 @@ use std::collections::HashMap; /// and saves the private wallet identifiers of our selected outputs /// into our transaction context -pub fn build_send_tx( +pub fn build_send_tx<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &mut Slate, minimum_confirmations: u64, @@ -43,9 +43,9 @@ pub fn build_send_tx( use_test_nonce: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let (elems, inputs, change_amounts_derivations, fee) = select_send_tx( wallet, @@ -58,7 +58,7 @@ where selection_strategy_is_use_all, &parent_key_id, )?; - let keychain = wallet.keychain(); + let keychain = wallet.keychain()?; let blinding = slate.add_transaction_elements(keychain, &ProofBuilder::new(keychain), elems)?; slate.fee = fee; @@ -95,15 +95,15 @@ where /// Locks all corresponding outputs in the context, creates /// change outputs and tx log entry -pub fn lock_tx_context( +pub fn lock_tx_context<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &Slate, context: &Context, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut output_commits: HashMap, u64)> = HashMap::new(); // Store cached commits before locking wallet @@ -172,20 +172,20 @@ where /// Creates a new output in the wallet for the recipient, /// returning the key of the fresh output /// Also creates a new transaction containing the output -pub fn build_recipient_output( +pub fn build_recipient_output<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &mut Slate, parent_key_id: Identifier, use_test_rng: bool, ) -> Result<(Identifier, Context), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // Create a potential output for this transaction let key_id = keys::next_available_key(wallet).unwrap(); - let keychain = wallet.keychain().clone(); + let keychain = wallet.keychain()?.clone(); let key_id_inner = key_id.clone(); let amount = slate.amount; let height = slate.height; @@ -201,7 +201,7 @@ where let mut context = Context::new( keychain.secp(), blinding - .secret_key(wallet.keychain().clone().secp()) + .secret_key(wallet.keychain()?.clone().secp()) .unwrap(), &parent_key_id, use_test_rng, @@ -240,7 +240,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<'a, T: ?Sized, C, K, B>( wallet: &mut T, amount: u64, current_height: u64, @@ -260,9 +260,9 @@ pub fn select_send_tx( Error, > where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, B: ProofBuild, { let (coins, _total, amount, fee) = select_coins_and_fee( @@ -288,7 +288,7 @@ where } /// Select outputs and calculating fee. -pub fn select_coins_and_fee( +pub fn select_coins_and_fee<'a, T: ?Sized, C, K>( wallet: &mut T, amount: u64, current_height: u64, @@ -307,9 +307,9 @@ pub fn select_coins_and_fee( Error, > where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // select some spendable coins from the wallet let (max_outputs, mut coins) = select_coins( @@ -393,7 +393,7 @@ where } /// Selects inputs and change for a transaction -pub fn inputs_and_change( +pub fn inputs_and_change<'a, T: ?Sized, C, K, B>( coins: &Vec, wallet: &mut T, amount: u64, @@ -407,9 +407,9 @@ pub fn inputs_and_change( Error, > where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, B: ProofBuild, { let mut parts = vec![]; @@ -471,7 +471,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<'a, T: ?Sized, C, K>( wallet: &mut T, amount: u64, current_height: u64, @@ -482,9 +482,9 @@ pub fn select_coins( ) -> (usize, Vec) // max_outputs_available, Outputs where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // first find all eligible outputs based on number of confirmations let mut eligible = wallet diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 2b5e040c..546243da 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -32,16 +32,16 @@ lazy_static! { /// Creates a new slate for a transaction, can be called by anyone involved in /// the transaction (sender(s), receiver(s)) -pub fn new_tx_slate( +pub fn new_tx_slate<'a, T: ?Sized, C, K>( wallet: &mut T, amount: u64, num_participants: usize, use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let current_height = wallet.w2n_client().get_chain_height()?; let mut slate = Slate::blank(num_participants); @@ -68,7 +68,7 @@ where } /// Estimates locked amount and fee for the transaction without creating one -pub fn estimate_send_tx( +pub fn estimate_send_tx<'a, T: ?Sized, C, K>( wallet: &mut T, amount: u64, minimum_confirmations: u64, @@ -84,9 +84,9 @@ pub fn estimate_send_tx( Error, > where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // Get lock height let current_height = wallet.w2n_client().get_chain_height()?; @@ -114,7 +114,7 @@ where } /// Add inputs to the slate (effectively becoming the sender) -pub fn add_inputs_to_slate( +pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &mut Slate, minimum_confirmations: u64, @@ -128,9 +128,9 @@ pub fn add_inputs_to_slate( use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // sender should always refresh outputs updater::refresh_outputs(wallet, parent_key_id, false)?; @@ -157,7 +157,7 @@ where // the offset in the slate's transaction kernel, and adds our public key // information to the slate let _ = slate.fill_round_1( - wallet.keychain(), + wallet.keychain()?, &mut context.sec_key, &context.sec_nonce, participant_id, @@ -168,7 +168,7 @@ where if !is_initator { // perform partial sig let _ = slate.fill_round_2( - wallet.keychain(), + wallet.keychain()?, &context.sec_key, &context.sec_nonce, participant_id, @@ -179,7 +179,7 @@ where } /// Add receiver output to the slate -pub fn add_output_to_slate( +pub fn add_output_to_slate<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &mut Slate, parent_key_id: &Identifier, @@ -189,9 +189,9 @@ pub fn add_output_to_slate( use_test_rng: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // create an output using the amount in the slate let (_, mut context) = @@ -199,7 +199,7 @@ where // fill public keys let _ = slate.fill_round_1( - wallet.keychain(), + wallet.keychain()?, &mut context.sec_key, &context.sec_nonce, 1, @@ -210,7 +210,7 @@ where if !is_initiator { // perform partial sig let _ = slate.fill_round_2( - wallet.keychain(), + wallet.keychain()?, &context.sec_key, &context.sec_nonce, participant_id, @@ -221,40 +221,40 @@ where } /// Complete a transaction -pub fn complete_tx( +pub fn complete_tx<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &mut Slate, participant_id: usize, context: &Context, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let _ = slate.fill_round_2( - wallet.keychain(), + wallet.keychain()?, &context.sec_key, &context.sec_nonce, participant_id, )?; // Final transaction can be built by anyone at this stage - slate.finalize(wallet.keychain())?; + slate.finalize(wallet.keychain()?)?; Ok(()) } /// Rollback outputs associated with a transaction in the wallet -pub fn cancel_tx( +pub fn cancel_tx<'a, T: ?Sized, C, K>( wallet: &mut T, parent_key_id: &Identifier, tx_id: Option, tx_slate_id: Option, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut tx_id_string = String::new(); if let Some(tx_id) = tx_id { @@ -281,15 +281,15 @@ where } /// Update the stored transaction (this update needs to happen when the TX is finalised) -pub fn update_stored_tx( +pub fn update_stored_tx<'a, T: ?Sized, C, K>( wallet: &mut T, slate: &Slate, is_invoiced: bool, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // finalize command let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?; @@ -314,11 +314,11 @@ where } /// Update the transaction participant messages -pub fn update_message(wallet: &mut T, slate: &Slate) -> Result<(), Error> +pub fn update_message<'a, T: ?Sized, C, K>(wallet: &mut T, slate: &Slate) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?; if tx_vec.is_empty() { diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 2b22fe0d..33f4afbd 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -34,16 +34,16 @@ use crate::types::{ use crate::{BlockFees, CbData, OutputCommitMapping}; /// Retrieve all of the outputs (doesn't attempt to update from node) -pub fn retrieve_outputs( +pub fn retrieve_outputs<'a, T: ?Sized, C, K>( wallet: &mut T, show_spent: bool, tx_id: Option, parent_key_id: Option<&Identifier>, ) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // just read the wallet here, no need for a write lock let mut outputs = wallet @@ -68,7 +68,7 @@ where } outputs.sort_by_key(|out| out.n_child); - let keychain = wallet.keychain().clone(); + let keychain = wallet.keychain()?.clone(); let res = outputs .into_iter() @@ -87,7 +87,7 @@ where /// Retrieve all of the transaction entries, or a particular entry /// if `parent_key_id` is set, only return entries from that key -pub fn retrieve_txs( +pub fn retrieve_txs<'a, T: ?Sized, C, K>( wallet: &mut T, tx_id: Option, tx_slate_id: Option, @@ -95,9 +95,9 @@ pub fn retrieve_txs( outstanding_only: bool, ) -> Result, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut txs: Vec = wallet .tx_log_iter() @@ -131,15 +131,15 @@ where /// Refreshes the outputs in a wallet with the latest information /// from a node -pub fn refresh_outputs( +pub fn refresh_outputs<'a, T: ?Sized, C, K>( wallet: &mut T, parent_key_id: &Identifier, update_all: bool, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let height = wallet.w2n_client().get_chain_height()?; refresh_output_state(wallet, height, parent_key_id, update_all)?; @@ -148,19 +148,19 @@ 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<'a, T: ?Sized, C, K>( wallet: &mut T, parent_key_id: &Identifier, update_all: bool, ) -> Result)>, Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut wallet_outputs: HashMap)> = HashMap::new(); - let keychain = wallet.keychain().clone(); + let keychain = wallet.keychain()?.clone(); let unspents: Vec = wallet .iter() .filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent) @@ -199,16 +199,16 @@ where } /// Cancel transaction and associated outputs -pub fn cancel_tx_and_outputs( +pub fn cancel_tx_and_outputs<'a, T: ?Sized, C, K>( wallet: &mut T, tx: TxLogEntry, outputs: Vec, parent_key_id: &Identifier, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let mut batch = wallet.batch()?; @@ -235,7 +235,7 @@ where } /// Apply refreshed API output data to the wallet -pub fn apply_api_outputs( +pub fn apply_api_outputs<'a, T: ?Sized, C, K>( wallet: &mut T, wallet_outputs: &HashMap)>, api_outputs: &HashMap, @@ -243,9 +243,9 @@ pub fn apply_api_outputs( parent_key_id: &Identifier, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { // now for each commit, find the output in the wallet and the corresponding // api output (if it exists) and refresh it in-place in the wallet. @@ -315,16 +315,16 @@ 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<'a, T: ?Sized, C, K>( wallet: &mut T, height: u64, parent_key_id: &Identifier, update_all: bool, ) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { debug!("Refreshing wallet outputs"); @@ -342,11 +342,11 @@ where Ok(()) } -fn clean_old_unconfirmed(wallet: &mut T, height: u64) -> Result<(), Error> +fn clean_old_unconfirmed<'a, T: ?Sized, C, K>(wallet: &mut T, height: u64) -> Result<(), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { if height < 50 { return Ok(()); @@ -371,15 +371,15 @@ where /// Retrieve summary info about the wallet /// caller should refresh first if desired -pub fn retrieve_info( +pub fn retrieve_info<'a, T: ?Sized, C, K>( wallet: &mut T, parent_key_id: &Identifier, minimum_confirmations: u64, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let current_height = wallet.last_confirmed_height()?; let outputs = wallet @@ -434,15 +434,15 @@ where } /// Build a coinbase output and insert into wallet -pub fn build_coinbase( +pub fn build_coinbase<'a, T: ?Sized, C, K>( wallet: &mut T, block_fees: &BlockFees, test_mode: bool, ) -> Result where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let (out, kern, block_fees) = receive_coinbase(wallet, block_fees, test_mode)?; @@ -455,15 +455,15 @@ 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<'a, T: ?Sized, C, K>( wallet: &mut T, block_fees: &BlockFees, test_mode: bool, ) -> Result<(Output, TxKernel, BlockFees), Error> where - T: WalletBackend, - C: NodeClient, - K: Keychain, + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, { let height = block_fees.height; let lock_height = height + global::coinbase_maturity(); @@ -510,7 +510,7 @@ where debug!("receive_coinbase: {:?}", block_fees); - let keychain = wallet.keychain(); + let keychain = wallet.keychain()?; let (out, kern) = reward::output( keychain, &ProofBuilder::new(keychain), diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index d940f5e1..d605ab07 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -63,5 +63,5 @@ pub use internal::restore::{check_repair, restore}; pub use types::{ AcctPathMapping, BlockIdentifier, Context, NodeClient, NodeVersionInfo, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, TxWrapper, WalletBackend, WalletInfo, WalletInst, - WalletOutputBatch, + WalletLCProvider, WalletOutputBatch, }; diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index f10d5eb1..c843fd04 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -19,10 +19,11 @@ use crate::error::{Error, ErrorKind}; use crate::grin_core::core::hash::Hash; use crate::grin_core::core::Transaction; use crate::grin_core::libtx::{aggsig, secp_ser}; -use crate::grin_core::ser; +use crate::grin_core::{global, ser}; use crate::grin_keychain::{Identifier, Keychain}; use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::{self, pedersen, Secp256k1}; +use crate::grin_util::ZeroingString; use crate::slate::ParticipantMessages; use chrono::prelude::*; use failure::ResultExt; @@ -33,37 +34,92 @@ use std::fmt; use uuid::Uuid; /// Combined trait to allow dynamic wallet dispatch -pub trait WalletInst: WalletBackend + Send + Sync + 'static +pub trait WalletInst<'a, L, C, K>: Send + Sync where - C: NodeClient, - K: Keychain, + L: WalletLCProvider<'a, C, K> + Send + Sync, + C: NodeClient + 'a, + K: Keychain + 'a, { + /// Return the stored instance + fn lc_provider(&mut self) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), Error>; } -impl WalletInst for T + +/// Trait for a provider of wallet lifecycle methods +pub trait WalletLCProvider<'a, C, K>: Send + Sync where - T: WalletBackend + Send + Sync + 'static, - C: NodeClient, - K: Keychain, + C: NodeClient + 'a, + K: Keychain + 'a, { + /// Sets the top level system wallet directory + /// default is assumed to be ~/.grin/main/wallet_data (or floonet equivalent) + fn set_wallet_directory(&mut self, dir: &str); + + /// Output a grin-wallet.toml file into the current top-level system wallet directory + fn create_config(&self, chain_type: &global::ChainTypes, file_name: &str) -> Result<(), Error>; + + /// + fn create_wallet( + &mut self, + name: Option<&str>, + mnemonic: Option, + mnemonic_length: usize, + password: ZeroingString, + ) -> Result<(), Error>; + + /// + fn open_wallet(&mut self, name: Option<&str>, password: ZeroingString) -> Result<(), Error>; + + /// + fn close_wallet(&mut self, name: Option<&str>) -> Result<(), Error>; + + /// whether a wallet exists at the given directory + fn wallet_exists(&self, name: Option<&str>) -> Result; + + /// return mnemonic of given wallet + fn get_mnemonic( + &self, + name: Option<&str>, + password: ZeroingString, + ) -> Result; + + /// Check whether a provided mnemonic string is valid + fn validate_mnemonic(&self, mnemonic: ZeroingString) -> Result<(), Error>; + + /// Recover a seed from phrase, without destroying existing data + /// should back up seed + fn recover_from_mnemonic( + &self, + mnemonic: ZeroingString, + password: ZeroingString, + ) -> Result<(), Error>; + + /// changes password + fn change_password(&self, old: String, new: String) -> Result<(), Error>; + + /// deletes wallet + fn delete_wallet(&self, name: Option, password: String) -> Result<(), Error>; + + /// return wallet instance + fn wallet_inst(&mut self) -> Result<&mut Box + 'a>, Error>; } /// TODO: /// 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<'ck, C, K>: Send + Sync where - C: NodeClient, - K: Keychain, + C: NodeClient + 'ck, + K: Keychain + 'ck, { - /// Initialize with whatever stored credentials we have - fn open_with_credentials(&mut self) -> Result<(), Error>; + /// Set the keychain, which should already be initialized + fn set_keychain(&mut self, k: Box); /// Close wallet and remove any stored credentials (TBD) fn close(&mut self) -> Result<(), Error>; /// Return the keychain being used - fn keychain(&mut self) -> &mut K; + fn keychain(&mut self) -> Result<&mut K, Error>; /// Return the client being used to communicate with the node fn w2n_client(&mut self) -> &mut C; @@ -205,7 +261,7 @@ where /// Encapsulate all wallet-node communication functions. No functions within libwallet /// should care about communication details -pub trait NodeClient: Sync + Send + Clone { +pub trait NodeClient: Send + Sync + Clone { /// Return the URL of the check node fn node_url(&self) -> &str; diff --git a/src/bin/cmd/config.rs b/src/bin/cmd/config.rs deleted file mode 100644 index d7c39fa3..00000000 --- a/src/bin/cmd/config.rs +++ /dev/null @@ -1,63 +0,0 @@ -// 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. - -/// Grin configuration file output command -use crate::config::{config, GlobalWalletConfig, GRIN_WALLET_DIR}; -use crate::core::global; -use std::env; - -/// Create a config file in the current directory -pub fn config_command_wallet(chain_type: &global::ChainTypes, file_name: &str) { - let mut default_config = GlobalWalletConfig::for_chain(chain_type); - let current_dir = env::current_dir().unwrap_or_else(|e| { - panic!("Error creating config file: {}", e); - }); - let mut config_file_name = current_dir.clone(); - config_file_name.push(file_name); - - let mut data_dir_name = current_dir.clone(); - data_dir_name.push(GRIN_WALLET_DIR); - - if config_file_name.exists() && data_dir_name.exists() { - panic!( - "{} already exists in the target directory. Please remove it first", - file_name - ); - } - - // just leave as is if file exists but there's no data dir - if config_file_name.exists() { - return; - } - - default_config.update_paths(¤t_dir); - default_config - .write_to_file(config_file_name.to_str().unwrap()) - .unwrap_or_else(|e| { - panic!("Error creating config file: {}", e); - }); - - println!( - "File {} configured and created", - config_file_name.to_str().unwrap(), - ); - - let mut api_secret_path = current_dir.clone(); - api_secret_path.push(config::API_SECRET_FILE_NAME); - if !api_secret_path.exists() { - config::init_api_secret(&api_secret_path).unwrap(); - } else { - config::check_api_secret(&api_secret_path).unwrap(); - } -} diff --git a/src/bin/cmd/mod.rs b/src/bin/cmd/mod.rs index 26e932c8..f5f6f04f 100644 --- a/src/bin/cmd/mod.rs +++ b/src/bin/cmd/mod.rs @@ -12,10 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod config; mod wallet; mod wallet_args; mod wallet_tests; -pub use self::config::config_command_wallet; -pub use self::wallet::{seed_exists, wallet_command}; +pub use self::wallet::wallet_command; diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs index 8d331fe9..05e81bf1 100644 --- a/src/bin/cmd/wallet.rs +++ b/src/bin/cmd/wallet.rs @@ -15,34 +15,14 @@ use crate::cmd::wallet_args; use crate::config::GlobalWalletConfig; use clap::ArgMatches; -use grin_wallet_config::WalletConfig; -use grin_wallet_impls::{HTTPNodeClient, WalletSeed, SEED_FILE}; +use grin_wallet_impls::HTTPNodeClient; use grin_wallet_libwallet::NodeClient; use semver::Version; -use std::path::PathBuf; use std::thread; use std::time::Duration; const MIN_COMPAT_NODE_VERSION: &str = "2.0.0-beta.1"; -pub fn _init_wallet_seed(wallet_config: WalletConfig, password: &str) { - if let Err(_) = WalletSeed::from_file(&wallet_config, password) { - WalletSeed::init_file(&wallet_config, 32, None, password) - .expect("Failed to create wallet seed file."); - }; -} - -pub fn seed_exists(wallet_config: WalletConfig) -> bool { - let mut data_file_dir = PathBuf::new(); - data_file_dir.push(wallet_config.data_file_dir); - data_file_dir.push(SEED_FILE); - if data_file_dir.exists() { - true - } else { - false - } -} - pub fn wallet_command(wallet_args: &ArgMatches<'_>, config: GlobalWalletConfig) -> i32 { // just get defaults from the global config let wallet_config = config.members.unwrap().wallet; diff --git a/src/bin/cmd/wallet_args.rs b/src/bin/cmd/wallet_args.rs index 24444e0c..bb4f1286 100644 --- a/src/bin/cmd/wallet_args.rs +++ b/src/bin/cmd/wallet_args.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::api::TLSConfig; +use crate::config::GRIN_WALLET_DIR; use crate::util::file::get_first_line; use crate::util::{Mutex, ZeroingString}; /// Argument parsing and error handling for wallet commands @@ -21,24 +22,20 @@ use failure::Fail; use grin_wallet_config::WalletConfig; use grin_wallet_controller::command; use grin_wallet_controller::{Error, ErrorKind}; -use grin_wallet_impls::{instantiate_wallet, WalletSeed}; -use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst}; +use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; +use grin_wallet_impls::{PathToSlate, SlateGetter as _}; +use grin_wallet_libwallet::Slate; +use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; use grin_wallet_util::grin_core as core; +use grin_wallet_util::grin_core::core::amount_to_hr_string; +use grin_wallet_util::grin_core::global; use grin_wallet_util::grin_keychain as keychain; use linefeed::terminal::Signal; use linefeed::{Interface, ReadResult}; use rpassword; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; -// shut up test compilation warnings -#[cfg(not(test))] -use grin_wallet_impls::FileWalletCommAdapter; -#[cfg(not(test))] -use grin_wallet_libwallet::Slate; -#[cfg(not(test))] -use grin_wallet_util::grin_core::core::amount_to_hr_string; - // define what to do on argument error macro_rules! arg_parse { ( $r:expr ) => { @@ -116,7 +113,15 @@ fn prompt_replace_seed() -> Result { } } -fn prompt_recovery_phrase() -> Result { +fn prompt_recovery_phrase( + wallet: Arc>>>, +) -> Result +where + DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ let interface = Arc::new(Interface::new("recover")?); let mut phrase = ZeroingString::from(""); interface.set_report_signal(Signal::Interrupt, true); @@ -133,7 +138,11 @@ fn prompt_recovery_phrase() -> Result { } } ReadResult::Input(line) => { - if WalletSeed::from_mnemonic(&line).is_ok() { + let mut w_lock = wallet.lock(); + let p = w_lock.lc_provider().unwrap(); + if p.validate_mnemonic(ZeroingString::from(line.clone())) + .is_ok() + { phrase = ZeroingString::from(line); break; } else { @@ -148,7 +157,6 @@ fn prompt_recovery_phrase() -> Result { Ok(phrase) } -#[cfg(not(test))] fn prompt_pay_invoice(slate: &Slate, method: &str, dest: &str) -> Result { let interface = Arc::new(Interface::new("pay")?); let amount = amount_to_hr_string(slate.amount, false); @@ -207,27 +215,21 @@ fn prompt_pay_invoice(slate: &Slate, method: &str, dest: &str) -> Result( config: WalletConfig, - g_args: &command::GlobalArgs, - node_client: impl NodeClient + 'static, -) -> Result>>, ParseError> { - let passphrase = prompt_password(&g_args.password); - let res = instantiate_wallet(config.clone(), node_client, &passphrase, &g_args.account); - match res { - Ok(p) => Ok(p), - Err(e) => { - let msg = { - match e.kind() { - grin_wallet_impls::ErrorKind::Encryption => { - format!("Error decrypting wallet seed (check provided password)") - } - _ => format!("Error instantiating wallet: {}", e), - } - }; - Err(ParseError::ArgumentError(msg)) - } - } + node_client: C, +) -> Result>>>, ParseError> +where + DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client.clone()).unwrap()) + as Box>; + let lc = wallet.lc_provider().unwrap(); + lc.set_wallet_directory(&config.data_file_dir); + Ok(Arc::new(Mutex::new(wallet))) } // parses a required value, or throws error with message otherwise @@ -283,30 +285,42 @@ pub fn parse_global_args( } }; + let chain_type = match config.chain_type.clone() { + None => { + let param_ref = global::CHAIN_TYPE.read(); + param_ref.clone() + } + Some(c) => c, + }; + Ok(command::GlobalArgs { account: account.to_owned(), show_spent: show_spent, + chain_type: chain_type, node_api_secret: node_api_secret, password: password, tls_conf: tls_conf, }) } -pub fn parse_init_args( +pub fn parse_init_args( + wallet: Arc>>>, config: &WalletConfig, g_args: &command::GlobalArgs, args: &ArgMatches, -) -> Result { - if let Err(e) = WalletSeed::seed_file_exists(config) { - let msg = format!("Not creating wallet - {}", e.inner); - return Err(ParseError::ArgumentError(msg)); - } +) -> Result +where + DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ let list_length = match args.is_present("short_wordlist") { false => 32, true => 16, }; let recovery_phrase = match args.is_present("recover") { - true => Some(prompt_recovery_phrase()?), + true => Some(prompt_recovery_phrase(wallet)?), false => None, }; @@ -330,17 +344,25 @@ pub fn parse_init_args( }) } -pub fn parse_recover_args( - config: &WalletConfig, +pub fn parse_recover_args( + wallet: Arc>>>, g_args: &command::GlobalArgs, args: &ArgMatches, -) -> Result { +) -> Result +where + DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ let (passphrase, recovery_phrase) = { match args.is_present("display") { true => (prompt_password(&g_args.password), None), false => { let cont = { - if command::wallet_seed_exists(config).is_err() { + let mut w_lock = wallet.lock(); + let p = w_lock.lc_provider().unwrap(); + if p.wallet_exists(None).unwrap() { prompt_replace_seed()? } else { true @@ -349,7 +371,7 @@ pub fn parse_recover_args( if !cont { return Err(ParseError::CancelledError); } - let phrase = prompt_recovery_phrase()?; + let phrase = prompt_recovery_phrase(wallet.clone())?; println!("Please provide a new password for the recovered wallet"); (prompt_password_confirm(), Some(phrase.to_owned())) } @@ -363,15 +385,8 @@ pub fn parse_recover_args( pub fn parse_listen_args( config: &mut WalletConfig, - g_args: &mut command::GlobalArgs, args: &ArgMatches, ) -> Result { - // listen args - let pass = match g_args.password.clone() { - Some(p) => Some(p.to_owned()), - None => Some(prompt_password(&None)), - }; - g_args.password = pass; if let Some(port) = args.value_of("port") { config.api_listen_port = port.parse().unwrap(); } @@ -621,18 +636,17 @@ pub fn parse_process_invoice_args( // file input only let tx_file = parse_required(args, "input")?; - // Now we need to prompt the user whether they want to do this, - // which requires reading the slate - #[cfg(not(test))] - let adapter = FileWalletCommAdapter::new(); - #[cfg(not(test))] - let slate = match adapter.receive_tx_async(&tx_file) { - Ok(s) => s, - Err(e) => return Err(ParseError::ArgumentError(format!("{}", e))), - }; + if cfg!(not(test)) { + // Now we need to prompt the user whether they want to do this, + // which requires reading the slate - #[cfg(not(test))] // don't prompt during automated testing - prompt_pay_invoice(&slate, method, dest)?; + let slate = match PathToSlate((&tx_file).into()).get_tx() { + Ok(s) => s, + Err(e) => return Err(ParseError::ArgumentError(format!("{}", e))), + }; + + prompt_pay_invoice(&slate, method, dest)?; + } Ok(command::ProcessInvoiceArgs { message: message, @@ -736,11 +750,14 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result( wallet_args: &ArgMatches, mut wallet_config: WalletConfig, - mut node_client: impl NodeClient + 'static, -) -> Result { + mut node_client: C, +) -> Result +where + C: NodeClient + 'static + Clone, +{ if let Some(t) = wallet_config.chain_type.clone() { core::global::set_mining_mode(t); } @@ -762,68 +779,104 @@ pub fn wallet_command( node_client.set_node_url(&wallet_config.check_node_api_http_addr); node_client.set_node_api_secret(global_wallet_args.node_api_secret.clone()); - // closure to instantiate wallet as needed by each subcommand - let inst_wallet = || { - let res = inst_wallet(wallet_config.clone(), &global_wallet_args, node_client); - res.unwrap_or_else(|e| { + // legacy hack to avoid the need for changes in existing grin-wallet.toml files + // remove `wallet_data` from end of path as + // new lifecycle provider assumes grin_wallet.toml is in root of data directory + let mut top_level_wallet_dir = PathBuf::from(wallet_config.clone().data_file_dir); + if top_level_wallet_dir.ends_with(GRIN_WALLET_DIR) { + top_level_wallet_dir.pop(); + wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into(); + } + + // Instantiate wallet (doesn't open the wallet) + let wallet = + inst_wallet::, C, keychain::ExtKeychain>( + wallet_config.clone(), + node_client, + ) + .unwrap_or_else(|e| { println!("{}", e); std::process::exit(1); - }) - }; + }); + + { + let mut wallet_lock = wallet.lock(); + let lc = wallet_lock.lc_provider().unwrap(); + lc.set_wallet_directory(&wallet_config.data_file_dir); + } + + // don't open wallet for certain lifecycle commands + match wallet_args.subcommand() { + ("init", Some(_)) => {} + ("recover", _) => {} + _ => { + let mut wallet_lock = wallet.lock(); + let lc = wallet_lock.lc_provider().unwrap(); + lc.open_wallet(None, prompt_password(&global_wallet_args.password))?; + if let Some(account) = wallet_args.value_of("account") { + let wallet_inst = lc.wallet_inst()?; + wallet_inst.set_parent_key_id_by_name(account)?; + } + } + } let res = match wallet_args.subcommand() { ("init", Some(args)) => { - let a = arg_parse!(parse_init_args(&wallet_config, &global_wallet_args, &args)); - command::init(&global_wallet_args, a) - } - ("recover", Some(args)) => { - let a = arg_parse!(parse_recover_args( + let a = arg_parse!(parse_init_args( + wallet.clone(), &wallet_config, &global_wallet_args, &args )); - command::recover(&wallet_config, a) + command::init(wallet, &global_wallet_args, a) + } + ("recover", Some(args)) => { + let a = arg_parse!(parse_recover_args( + wallet.clone(), + &global_wallet_args, + &args + )); + command::recover(wallet, a) } ("listen", Some(args)) => { let mut c = wallet_config.clone(); - let mut g = global_wallet_args.clone(); - let a = arg_parse!(parse_listen_args(&mut c, &mut g, &args)); - command::listen(&c, &a, &g) + let a = arg_parse!(parse_listen_args(&mut c, &args)); + command::listen(wallet, &c, &a, &global_wallet_args.clone()) } ("owner_api", Some(_)) => { let mut g = global_wallet_args.clone(); g.tls_conf = None; - command::owner_api(inst_wallet(), &wallet_config, &g) + command::owner_api(wallet, &wallet_config, &g) } - ("web", Some(_)) => command::owner_api(inst_wallet(), &wallet_config, &global_wallet_args), + ("web", Some(_)) => command::owner_api(wallet, &wallet_config, &global_wallet_args), ("account", Some(args)) => { let a = arg_parse!(parse_account_args(&args)); - command::account(inst_wallet(), a) + command::account(wallet, a) } ("send", Some(args)) => { let a = arg_parse!(parse_send_args(&args)); command::send( - inst_wallet(), + wallet, a, wallet_config.dark_background_color_scheme.unwrap_or(true), ) } ("receive", Some(args)) => { let a = arg_parse!(parse_receive_args(&args)); - command::receive(inst_wallet(), &global_wallet_args, a) + command::receive(wallet, &global_wallet_args, a) } ("finalize", Some(args)) => { let a = arg_parse!(parse_finalize_args(&args)); - command::finalize(inst_wallet(), a) + command::finalize(wallet, a) } ("invoice", Some(args)) => { let a = arg_parse!(parse_issue_invoice_args(&args)); - command::issue_invoice_tx(inst_wallet(), a) + command::issue_invoice_tx(wallet, a) } ("pay", Some(args)) => { let a = arg_parse!(parse_process_invoice_args(&args)); command::process_invoice( - inst_wallet(), + wallet, a, wallet_config.dark_background_color_scheme.unwrap_or(true), ) @@ -831,21 +884,21 @@ pub fn wallet_command( ("info", Some(args)) => { let a = arg_parse!(parse_info_args(&args)); command::info( - inst_wallet(), + wallet, &global_wallet_args, a, wallet_config.dark_background_color_scheme.unwrap_or(true), ) } ("outputs", Some(_)) => command::outputs( - inst_wallet(), + wallet, &global_wallet_args, wallet_config.dark_background_color_scheme.unwrap_or(true), ), ("txs", Some(args)) => { let a = arg_parse!(parse_txs_args(&args)); command::txs( - inst_wallet(), + wallet, &global_wallet_args, a, wallet_config.dark_background_color_scheme.unwrap_or(true), @@ -853,16 +906,16 @@ pub fn wallet_command( } ("repost", Some(args)) => { let a = arg_parse!(parse_repost_args(&args)); - command::repost(inst_wallet(), a) + command::repost(wallet, a) } ("cancel", Some(args)) => { let a = arg_parse!(parse_cancel_args(&args)); - command::cancel(inst_wallet(), a) + command::cancel(wallet, a) } - ("restore", Some(_)) => command::restore(inst_wallet()), + ("restore", Some(_)) => command::restore(wallet), ("check", Some(args)) => { let a = arg_parse!(parse_check_args(&args)); - command::check_repair(inst_wallet(), a) + command::check_repair(wallet, a) } _ => { let msg = format!("Unknown wallet command, use 'grin help wallet' for details"); diff --git a/src/bin/cmd/wallet_tests.rs b/src/bin/cmd/wallet_tests.rs index aed55495..8b8a8d7e 100644 --- a/src/bin/cmd/wallet_tests.rs +++ b/src/bin/cmd/wallet_tests.rs @@ -20,15 +20,16 @@ mod wallet_tests { use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; use clap::{App, ArgMatches}; + use std::path::PathBuf; use std::sync::Arc; use std::thread; use std::time::Duration; use std::{env, fs}; - use util::Mutex; + use util::{Mutex, ZeroingString}; - use grin_wallet_config::{GlobalWalletConfig, WalletConfig}; - use grin_wallet_impls::{LMDBBackend, WalletSeed}; - use grin_wallet_libwallet::{WalletBackend, WalletInst}; + use grin_wallet_config::{GlobalWalletConfig, WalletConfig, GRIN_WALLET_DIR}; + use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; + use grin_wallet_libwallet::WalletInst; use grin_wallet_util::grin_core::global::{self, ChainTypes}; use grin_wallet_util::grin_keychain::ExtKeychain; @@ -121,15 +122,45 @@ mod wallet_tests { node_client: LocalWalletClient, passphrase: &str, account: &str, - ) -> Result>>, grin_wallet_controller::Error> - { + ) -> Result< + Arc< + Mutex< + Box< + WalletInst< + 'static, + DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + LocalWalletClient, + ExtKeychain, + >, + >, + >, + >, + grin_wallet_controller::Error, + > { wallet_config.chain_type = None; - // First test decryption, so we can abort early if we have the wrong password - let _ = WalletSeed::from_file(&wallet_config, passphrase)?; - let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, node_client)?; - db_wallet.set_parent_key_id_by_name(account)?; - info!("Using LMDB Backend for wallet"); - Ok(Arc::new(Mutex::new(db_wallet))) + let mut wallet = Box::new(DefaultWalletImpl::::new(node_client).unwrap()) + as Box< + WalletInst< + DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + LocalWalletClient, + ExtKeychain, + >, + >; + let lc = wallet.lc_provider().unwrap(); + // legacy hack to avoid the need for changes in existing grin-wallet.toml files + // remove `wallet_data` from end of path as + // new lifecycle provider assumes grin_wallet.toml is in root of data directory + let mut top_level_wallet_dir = PathBuf::from(wallet_config.clone().data_file_dir); + if top_level_wallet_dir.ends_with(GRIN_WALLET_DIR) { + top_level_wallet_dir.pop(); + wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into(); + } + lc.set_wallet_directory(&wallet_config.data_file_dir); + lc.open_wallet(None, ZeroingString::from(passphrase)) + .unwrap(); + let wallet_inst = lc.wallet_inst()?; + wallet_inst.set_parent_key_id_by_name(account)?; + Ok(Arc::new(Mutex::new(wallet))) } fn execute_command( @@ -151,8 +182,11 @@ mod wallet_tests { fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::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< + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + > = WalletProxy::new(test_dir); let chain = wallet_proxy.chain.clone(); // load app yaml. If it don't exist, just say so and exit diff --git a/src/bin/grin-wallet.rs b/src/bin/grin-wallet.rs index 62da7aee..84389074 100644 --- a/src/bin/grin-wallet.rs +++ b/src/bin/grin-wallet.rs @@ -26,7 +26,7 @@ use grin_wallet_config as config; use grin_wallet_util::grin_api as api; use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_util as util; -use std::process::exit; +use std::env; mod cmd; @@ -79,43 +79,42 @@ fn real_main() -> i32 { global::ChainTypes::Mainnet }; - // Deal with configuration file creation + let mut current_dir = None; + + // special cases for certain lifecycle commands match args.subcommand() { - // wallet init command should spit out its config file then continue - // (if desired) ("init", Some(init_args)) => { if init_args.is_present("here") { - cmd::config_command_wallet(&chain_type, config::WALLET_CONFIG_FILE_NAME); + current_dir = Some(env::current_dir().unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + })); } } + ("recover", _) => {} _ => {} } // Load relevant config, try and load a wallet config file - let mut w = config::initial_setup_wallet(&chain_type).unwrap_or_else(|e| { + // Use defaults for configuration if config file not found anywhere + let mut config = config::initial_setup_wallet(&chain_type, current_dir).unwrap_or_else(|e| { panic!("Error loading wallet configuration: {}", e); }); - if !cmd::seed_exists(w.members.as_ref().unwrap().wallet.clone()) { - if "init" == args.subcommand().0 || "recover" == args.subcommand().0 { - } else { - println!("Wallet seed file doesn't exist. Run `grin-wallet init` first"); - exit(1); - } - } + config.members.as_mut().unwrap().wallet.chain_type = Some(chain_type); // Load logging config - let l = w.members.as_mut().unwrap().logging.clone().unwrap(); + let l = config.members.as_mut().unwrap().logging.clone().unwrap(); init_logger(Some(l)); info!( "Using wallet configuration file at {}", - w.config_file_path.as_ref().unwrap().to_str().unwrap() + config.config_file_path.as_ref().unwrap().to_str().unwrap() ); log_build_info(); global::set_mining_mode( - w.members + config + .members .as_ref() .unwrap() .wallet @@ -125,5 +124,5 @@ fn real_main() -> i32 { .clone(), ); - cmd::wallet_command(&args, w) + cmd::wallet_command(&args, config) } diff --git a/util/Cargo.toml b/util/Cargo.toml index d59386c7..ca404693 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_util" -version = "2.0.1-beta.1" +version = "2.1.0-beta.1" authors = ["Grin Developers "] description = "Util, for generic utilities and to re-export grin crates" license = "Apache-2.0"