diff --git a/Cargo.lock b/Cargo.lock index 92f7f4d..45c1b1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2540,10 +2540,13 @@ dependencies = [ "dirs 2.0.2", "grin_core", "grin_util", + "lazy_static", "rand 0.6.5", "serde", "serde_derive", + "tempfile", "toml 0.5.11", + "toml_edit 0.22.27", ] [[package]] @@ -2588,6 +2591,7 @@ dependencies = [ "chrono", "ed25519-dalek", "fs-mistrust", + "fslock-guard", "futures 0.3.32", "grin_api", "grin_chain", diff --git a/api/src/foreign.rs b/api/src/foreign.rs index d24ce02..a7b0027 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -14,9 +14,9 @@ //! Foreign API External Definition -use crate::config::TorConfig; use crate::keychain::Keychain; use crate::libwallet::api_impl::foreign; +use crate::libwallet::api_impl::types::update_tx_slate_state; use crate::libwallet::{ BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst, WalletLCProvider, @@ -24,7 +24,7 @@ use crate::libwallet::{ use crate::try_slatepack_sync_workflow; use crate::util::secp::key::SecretKey; use crate::util::Mutex; -use libwallet::api_impl::types::update_tx_slate_state; + use std::sync::Arc; /// ForeignAPI Middleware Check callback @@ -67,15 +67,14 @@ where { /// Wallet instance pub wallet_inst: Arc>>>, + /// Wallet configuration path + config_path: crate::ConfigPath, /// Flag to normalize some output during testing. Can mostly be ignored. pub doctest_mode: bool, /// foreign check middleware middleware: Option, /// Stored keychain mask (in case the stored wallet seed is tokenized) keychain_mask: Option, - /// Optional TOR configuration, holding address of sender and - /// data directory - tor_config: Mutex>, } impl<'a, L, C, K> Foreign<'a, L, C, K> @@ -95,6 +94,7 @@ where /// # Arguments /// * `wallet_in` - A reference-counted mutex containing an implementation of the /// [`WalletBackend`](../grin_wallet_libwallet/types/trait.WalletBackend.html) trait. + /// * `config_path` - Path to the wallet configuration file /// * `keychain_mask` - Mask value stored internally to use when calling a wallet /// whose seed has been XORed with a token value (such as when running the foreign /// and owner listeners in the same instance) @@ -106,6 +106,7 @@ where /// /// # Example /// ``` + /// use std::path::PathBuf; /// use grin_keychain as keychain; /// use grin_util as util; /// use grin_core; @@ -164,39 +165,30 @@ where /// // 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, None, false); + /// let api_foreign = Foreign::new(wallet.clone(), PathBuf::from(dir).join("grin-wallet.toml"), None, None, false); /// // .. perform wallet operations /// /// ``` - pub fn new( + pub fn new

( wallet_inst: Arc>>>, + config_path: P, keychain_mask: Option, middleware: Option, doctest_mode: bool, - ) -> Self { + ) -> Self + where + P: Into, + { Foreign { wallet_inst, + config_path: config_path.into(), doctest_mode, middleware, keychain_mask, - tor_config: Mutex::new(None), } } - /// Set the TOR configuration for this instance of the ForeignAPI, used during - /// `recieve_tx` when a return address is specified - /// - /// # Arguments - /// * `tor_config` - The optional [TorConfig](#) to use - /// # Returns - /// * Nothing - - pub fn set_tor_config(&self, tor_config: Option) { - let mut lock = self.tor_config.lock(); - *lock = tor_config; - } - /// Return the version capabilities of the running ForeignApi Node /// # Arguments /// None @@ -207,7 +199,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); + /// let mut api_foreign = Foreign::new(wallet.clone(), std::path::PathBuf::from("grin-wallet.toml"), None, None, false); /// /// let version_info = api_foreign.check_version(); /// // check and proceed accordingly @@ -259,7 +251,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); + /// let mut api_foreign = Foreign::new(wallet.clone(), std::path::PathBuf::from("grin-wallet.toml"), None, None, false); /// /// let block_fees = BlockFees { /// fees: 800000, @@ -334,7 +326,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); + /// let mut api_foreign = Foreign::new(wallet.clone(), std::path::PathBuf::from("grin-wallet.toml"), None, None, false); /// # let slate = Slate::blank(2, false); /// /// // . . . @@ -354,6 +346,10 @@ where r_addr: Option, ) -> Result { let mut w_lock = self.wallet_inst.lock(); + let tor_config = r_addr + .as_ref() + .map(|_| crate::tor_config::load(&self.config_path.get())) + .transpose()?; let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { m( @@ -371,16 +367,14 @@ where )?; match r_addr { Some(a) => { - let tc = self.tor_config.lock(); - let can_send = if let Some(tc) = tc.as_ref() { - tc.send_tor(None) - } else { - false - }; + let tc = tor_config.ok_or_else(|| { + Error::TorConfig("Tor config was not loaded with a return address".into()) + })?; + let can_send = tc.send_tor(None); if self.doctest_mode || !can_send { return Ok(ret_slate); } - let res = try_slatepack_sync_workflow(&ret_slate, &a, tc.clone(), None, true); + let res = try_slatepack_sync_workflow(&ret_slate, &a, Some(tc), None, true); match res { Ok(s) => { let parent_key_id = w.parent_key_id(); @@ -433,8 +427,8 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); + /// let mut api_foreign = Foreign::new(wallet.clone(), std::path::PathBuf::from("grin-wallet.toml"), None, None, false); /// /// // . . . /// // Issue the invoice tx via the owner API diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 55004a9..5cf0832 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -21,6 +21,8 @@ use crate::libwallet::{ }; use crate::{Foreign, ForeignCheckMiddlewareFn}; use easy_jsonrpc_mw; +use grin_wallet_config::initial_setup_wallet; +use std::path::PathBuf; /// Public definition used to generate Foreign jsonrpc api. /// * When running `grin-wallet listen` with defaults, the V2 api is available at @@ -364,6 +366,14 @@ pub fn run_doctest_foreign( let _ = fs::remove_dir_all(test_dir); global::set_local_chain_type(ChainTypes::AutomatedTesting); + let _ = fs::create_dir_all(test_dir); + let config = initial_setup_wallet( + &ChainTypes::AutomatedTesting, + Some(PathBuf::from(test_dir)), + false, + ) + .unwrap(); + let mut wallet_proxy: WalletProxy< DefaultLCProvider, LocalWalletClient, @@ -512,8 +522,20 @@ pub fn run_doctest_foreign( } let mut api_foreign = match init_invoice_tx { - false => Foreign::new(wallet1, mask1, Some(test_check_middleware), true), - true => Foreign::new(wallet2, mask2, Some(test_check_middleware), true), + false => Foreign::new( + wallet1, + config.config_file_path, + mask1, + Some(test_check_middleware), + true, + ), + true => Foreign::new( + wallet2, + config.config_file_path, + mask2, + Some(test_check_middleware), + true, + ), }; 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 1b79491..8827d5f 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -42,12 +42,15 @@ mod foreign_rpc; mod owner; mod owner_rpc; +mod tor_config; mod types; pub use crate::foreign::{Foreign, ForeignCheckMiddleware, ForeignCheckMiddlewareFn}; pub use crate::foreign_rpc::ForeignRpc; pub use crate::owner::{try_slatepack_sync_workflow, Owner}; pub use crate::owner_rpc::OwnerRpc; +#[doc(hidden)] +pub use crate::tor_config::ConfigPath; pub use crate::foreign_rpc::foreign_rpc as foreign_rpc_client; pub use crate::foreign_rpc::run_doctest_foreign; diff --git a/api/src/owner.rs b/api/src/owner.rs index d3e672a..96b801c 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -14,12 +14,6 @@ //! Owner API External Definition -use chrono::prelude::*; -use ed25519_dalek::SigningKey as DalekSecretKey; -use grin_wallet_libwallet::mwixnet::{MixnetReqCreationParams, SwapReq}; -use grin_wallet_libwallet::RetrieveTxQueryArgs; -use uuid::Uuid; - use crate::config::{TorConfig, WalletConfig}; use crate::core::core::OutputFeatures; use crate::core::global; @@ -27,6 +21,7 @@ use crate::impls::SlateSender as _; use crate::impls::TorSlateSender; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage}; +use crate::libwallet::api_impl::types::update_tx_slate_state; use crate::libwallet::api_impl::{owner, owner_updater}; use crate::libwallet::{ AcctPathMapping, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, @@ -36,16 +31,25 @@ use crate::libwallet::{ use crate::util::logger::LoggingConfig; use crate::util::secp::{key::SecretKey, pedersen::Commitment}; use crate::util::{from_hex, static_secp_instance, Mutex, ZeroingString}; +use grin_wallet_config::config::{ + reload_global_config, update_global_config, WALLET_CONFIG_FILE_NAME, +}; +use grin_wallet_libwallet::mwixnet::{MixnetReqCreationParams, SwapReq}; +use grin_wallet_libwallet::RetrieveTxQueryArgs; use grin_wallet_util::OnionV3Address; -use libwallet::api_impl::types::update_tx_slate_state; + +use chrono::prelude::*; +use ed25519_dalek::SigningKey as DalekSecretKey; use std::convert::TryFrom; use std::fs::File; use std::io::Write; +use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; use std::thread; use std::time::Duration; +use uuid::Uuid; /// Main interface into all wallet API functions. /// Wallet APIs are split into two seperate blocks of functionality @@ -67,11 +71,13 @@ where C: NodeClient + 'static, K: Keychain + 'static, { - /// contain all methods to manage the wallet + /// Contains all methods to manage the wallet pub wallet_inst: Arc>>>, + /// Wallet configuration path + config_path: crate::ConfigPath, /// Flag to normalize some output during testing. Can mostly be ignored. pub doctest_mode: bool, - /// retail TLD during doctest + /// Retail TLD during doctest pub doctest_retain_tld: bool, /// Share ECDH key pub shared_key: Arc>>, @@ -84,9 +90,6 @@ where /// Holds all update and status messages returned by the /// updater process updater_messages: Arc>>, - /// Optional TOR configuration, holding address of sender and - /// data directory - tor_config: Mutex>, } impl Owner @@ -105,15 +108,17 @@ where /// /// # Arguments /// * `wallet_in` - A reference-counted mutex containing an implementation of the + /// [`WalletBackend`](../grin_wallet_libwallet/types/trait.WalletBackend.html) trait. /// * `custom_channel` - A custom MPSC Tx/Rx pair to capture status /// updates - /// [`WalletBackend`](../grin_wallet_libwallet/types/trait.WalletBackend.html) trait. + /// * `config_path` - Path to the wallet configuration file /// /// # Returns /// * An instance of the OwnerApi holding a reference to the provided wallet /// /// # Example /// ``` + /// use std::path::PathBuf; /// use grin_keychain as keychain; /// use grin_util as util; /// use grin_core; @@ -171,15 +176,19 @@ where /// // 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(), None); + /// let api_owner = Owner::new(wallet.clone(), None, PathBuf::from(dir).join("grin-wallet.toml")); /// // .. perform wallet operations /// /// ``` - pub fn new( + pub fn new

( wallet_inst: Arc>>>, custom_channel: Option>, - ) -> Self { + config_path: P, + ) -> Self + where + P: Into, + { let updater_running = Arc::new(AtomicBool::new(false)); let updater = Arc::new(Mutex::new(owner_updater::Updater::new( wallet_inst.clone(), @@ -198,6 +207,7 @@ where Owner { wallet_inst, + config_path: config_path.into(), doctest_mode: false, doctest_retain_tld: false, shared_key: Arc::new(Mutex::new(None)), @@ -205,21 +215,49 @@ where updater_running, status_tx: Mutex::new(Some(tx)), updater_messages, - tor_config: Mutex::new(None), } } + /// Return the active wallet configuration path. + pub fn config_path(&self) -> PathBuf { + self.config_path.get() + } + + /// Return the shared wallet configuration path. + #[doc(hidden)] + pub fn shared_config_path(&self) -> crate::ConfigPath { + self.config_path.clone() + } + /// Set the TOR configuration for this instance of the OwnerAPI, used during - /// `init_send_tx` when send args are present and a TOR address is specified + /// `init_send_tx` when send args are present and a TOR address is specified, + /// this will also update persistent config and will restart foreign listener /// /// # Arguments /// * `tor_config` - The optional [TorConfig](#) to use /// # Returns - /// * Nothing + /// * Result Containing: + /// * `Ok(())` if the config was correctly saved + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// - pub fn set_tor_config(&self, tor_config: Option) { - let mut lock = self.tor_config.lock(); - *lock = tor_config; + pub fn set_tor_config(&self, tor_config: Option) -> Result<(), Error> { + let _wallet_lock = self.wallet_inst.lock(); + update_global_config(&self.config_path(), |config| { + if let Some(tor_config) = tor_config { + config.members.tor = Some(tor_config); + } else if let Some(tor) = config.members.tor.as_mut() { + tor.use_tor_listener = false; + tor.skip_send_attempt = Some(true); + } else { + let mut tor_config = config.tor_config(); + tor_config.use_tor_listener = false; + tor_config.skip_send_attempt = Some(true); + config.members.tor = Some(tor_config); + } + Ok(()) + }) + .map_err(|e| Error::TorConfig(format!("{}", e))) } /// Returns a list of accounts stored in the wallet (i.e. mappings between @@ -244,7 +282,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let result = api_owner.accounts(None); /// @@ -295,7 +333,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let result = api_owner.create_account_path(None, "account1"); /// @@ -342,7 +380,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let result = api_owner.create_account_path(None, "account1"); /// @@ -398,7 +436,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let show_spent = false; /// let update_from_node = true; /// let tx_id = None; @@ -469,7 +507,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let update_from_node = true; /// let tx_id = None; /// let tx_slate_id = None; @@ -548,7 +586,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let update_from_node = true; /// let minimum_confirmations=10; /// @@ -635,7 +673,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// // Attempt to create a transaction using the 'default' account /// let args = InitTxArgs { /// src_acct_name: None, @@ -665,26 +703,28 @@ where args: InitTxArgs, ) -> Result { let send_args = args.send_args.clone(); - let slate = { + let (slate, tor_config) = { let mut w_lock = self.wallet_inst.lock(); + let tor_config = send_args + .as_ref() + .map(|_| crate::tor_config::load(&self.config_path())) + .transpose()?; let w = w_lock.lc_provider()?.wallet_inst()?; - owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)? + let slate = owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?; + (slate, tor_config) }; // Helper functionality. If send arguments exist, attempt to send sync and // finalize match send_args { Some(sa) => { - let tor_config_lock = self.tor_config.lock(); - let tc = tor_config_lock.clone(); - let can_send = if let Some(tc) = tc.as_ref() { - tc.send_tor(sa.skip_tor) - } else { - false - }; + let tc = tor_config.ok_or_else(|| { + Error::TorConfig("Tor config was not loaded with send arguments".into()) + })?; + let can_send = tc.send_tor(sa.skip_tor); if self.doctest_mode || !can_send { return Ok(slate); } - let res = try_slatepack_sync_workflow(&slate, &sa.dest, tc, None, false); + let res = try_slatepack_sync_workflow(&slate, &sa.dest, Some(tc), None, false); match res { Ok(s) => { self.tx_lock_outputs(keychain_mask, &s)?; @@ -737,7 +777,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let args = IssueInvoiceTxArgs { /// amount: 60_000_000_000, @@ -798,7 +838,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// // . . . /// // The slate has been recieved from the invoicer, somehow @@ -828,25 +868,28 @@ where args: InitTxArgs, ) -> Result { let send_args = args.send_args.clone(); - let slate = { + let (slate, tor_config) = { let mut w_lock = self.wallet_inst.lock(); + let tor_config = send_args + .as_ref() + .map(|_| crate::tor_config::load(&self.config_path())) + .transpose()?; let w = w_lock.lc_provider()?.wallet_inst()?; - owner::process_invoice_tx(w, keychain_mask, slate, args, self.doctest_mode)? + let slate = + owner::process_invoice_tx(w, keychain_mask, slate, args, self.doctest_mode)?; + (slate, tor_config) }; // Helper functionality. If send arguments exist, attempt to send match send_args { Some(sa) => { - let tor_config_lock = self.tor_config.lock(); - let tc = tor_config_lock.clone(); - let can_send = if let Some(tc) = tc.as_ref() { - tc.send_tor(sa.skip_tor) - } else { - false - }; + let tc = tor_config.ok_or_else(|| { + Error::TorConfig("Tor config was not loaded with send arguments".into()) + })?; + let can_send = tc.send_tor(sa.skip_tor); if self.doctest_mode || !can_send { return Ok(slate); } - let res = try_slatepack_sync_workflow(&slate, &sa.dest, tc, None, true); + let res = try_slatepack_sync_workflow(&slate, &sa.dest, Some(tc), None, true); match res { Ok(s) => { // Update slate state. @@ -907,7 +950,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let args = InitTxArgs { /// src_acct_name: None, /// amount: 2_000_000_000, @@ -969,7 +1012,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let args = InitTxArgs { /// src_acct_name: None, /// amount: 2_000_000_000, @@ -1028,7 +1071,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let args = InitTxArgs { /// src_acct_name: None, /// amount: 2_000_000_000, @@ -1099,7 +1142,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let args = InitTxArgs { /// src_acct_name: None, /// amount: 2_000_000_000, @@ -1170,7 +1213,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let update_from_node = true; /// let tx_id = None; /// let tx_slate_id = None; @@ -1214,7 +1257,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let result = api_owner.scan( /// None, /// Some(20000), @@ -1254,7 +1297,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let result = api_owner.scan( /// None, /// Some(20000), @@ -1318,7 +1361,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let result = api_owner.scan( /// None, /// Some(20000), @@ -1376,7 +1419,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let result = api_owner.node_height(None); /// /// if let Ok(node_height_result) = result { @@ -1433,7 +1476,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let result = api_owner.get_top_level_directory(); /// /// if let Ok(dir) = result { @@ -1458,6 +1501,9 @@ where /// Set [`get_top_level_directory`](struct.Owner.html#method.get_top_level_directory) for a /// description of the top level directory and default paths. /// + /// The wallet must be closed and the updater stopped before changing this directory. + /// Open the wallet again afterwards to load its database from the new location. + /// /// # Arguments /// /// * `dir`: The new top-level directory path (either relative to current directory or @@ -1481,7 +1527,7 @@ where /// # .ok_or("Failed to convert tmpdir path to string.".to_owned()) /// # .unwrap(); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from(dir)); /// let result = api_owner.set_top_level_directory(dir); /// /// if let Ok(dir) = result { @@ -1490,9 +1536,25 @@ where /// ``` pub fn set_top_level_directory(&self, dir: &str) -> Result<(), Error> { + if self.updater_running.load(Ordering::Relaxed) { + return Err(Error::Lifecycle( + "Stop the updater before changing the top-level directory".into(), + )); + } let mut w_lock = self.wallet_inst.lock(); let lc = w_lock.lc_provider()?; - lc.set_top_level_directory(dir) + if lc.wallet_inst().is_ok() { + return Err(Error::Lifecycle( + "Close the wallet before changing the top-level directory".into(), + )); + } + let config_path = PathBuf::from(dir).join(WALLET_CONFIG_FILE_NAME); + if config_path.exists() { + reload_global_config(&config_path)?; + } + lc.set_top_level_directory(dir)?; + self.config_path.set(config_path); + Ok(()) } /// Create a `grin-wallet.toml` configuration file in the top-level directory for the @@ -1531,7 +1593,7 @@ where /// # .ok_or("Failed to convert tmpdir path to string.".to_owned()) /// # .unwrap(); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from(dir)); /// let _ = api_owner.set_top_level_directory(dir); /// /// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None, None); @@ -1600,7 +1662,7 @@ where /// # .to_str() /// # .ok_or("Failed to convert tmpdir path to string.".to_owned()) /// # .unwrap(); - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from(dir)); /// let _ = api_owner.set_top_level_directory(dir); /// /// // Create configuration @@ -1667,7 +1729,7 @@ where /// # .to_str() /// # .ok_or("Failed to convert tmpdir path to string.".to_owned()) /// # .unwrap(); - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from(dir)); /// let _ = api_owner.set_top_level_directory(dir); /// /// // Create configuration @@ -1724,7 +1786,7 @@ where /// use grin_core::global::ChainTypes; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.close_wallet(None); /// @@ -1760,7 +1822,7 @@ where /// use grin_core::global::ChainTypes; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let pw = ZeroingString::from("my_password"); /// let res = api_owner.get_mnemonic(None, pw); @@ -1805,7 +1867,7 @@ where /// use grin_core::global::ChainTypes; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let old = ZeroingString::from("my_password"); /// let new = ZeroingString::from("new_password"); @@ -1848,7 +1910,7 @@ where /// use grin_core::global::ChainTypes; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.delete_wallet(None); /// @@ -1904,7 +1966,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.start_updater(None, Duration::from_secs(60)); /// @@ -1959,7 +2021,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.start_updater(None, Duration::from_secs(60)); /// @@ -2001,7 +2063,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.start_updater(None, Duration::from_secs(60)); /// @@ -2067,7 +2129,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.get_slatepack_address(None, 0); /// @@ -2107,7 +2169,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// /// let res = api_owner.get_slatepack_secret_key(None, 0); /// @@ -2148,9 +2210,8 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// - /// let mut api_owner = Owner::new(wallet.clone(), None); /// let args = InitTxArgs { /// src_acct_name: None, /// amount: 2_000_000_000, @@ -2218,7 +2279,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// // ... receive a slatepack from somewhere /// # let slatepack_string = String::from(""); /// let res = api_owner.slate_from_slatepack_message( @@ -2268,7 +2329,7 @@ where /// use std::time::Duration; /// /// // Set up as above - /// # let api_owner = Owner::new(wallet.clone(), None); + /// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// # let slatepack_string = String::from(""); /// // .. receive a slatepack from somewhere /// let res = api_owner.decode_slatepack_message( @@ -2326,7 +2387,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let update_from_node = true; /// let tx_id = None; /// let tx_slate_id = Some(Uuid::parse_str("0436430c-2b02-624c-2032-570501212b00").unwrap()); @@ -2395,7 +2456,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let update_from_node = true; /// let tx_id = None; /// let tx_slate_id = Some(Uuid::parse_str("0436430c-2b02-624c-2032-570501212b00").unwrap()); @@ -2457,7 +2518,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// - /// let api_owner = Owner::new(wallet.clone(), None); + /// let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml")); /// let keychain_mask = None; /// let params = MixnetReqCreationParams { /// server_keys: vec![], // Public keys here in secret key representation @@ -2508,20 +2569,20 @@ pub fn try_slatepack_sync_workflow( send_to_finalize: bool, ) -> Result { let mut ret_slate = Slate::blank(2, false); - let mut send_sync = |mut sender: TorSlateSender, method_str: &str| match sender - .send_tx(&slate, send_to_finalize) - { - Ok(s) => { - ret_slate = s; - return Ok(()); - } - Err(e) => { - debug!( - "Send ({}): Could not send Slate via {}: {}", - method_str, method_str, e - ); - return Err(e); - } + let mut send_sync = |mut sender: TorSlateSender, method_str: &str| { + return match sender.send_tx(&slate, send_to_finalize) { + Ok(s) => { + ret_slate = s; + Ok(()) + } + Err(e) => { + debug!( + "Send ({}): Could not send Slate via {}: {}", + method_str, method_str, e + ); + Err(e) + } + }; }; // Try parsing Slatepack address. diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index d1c31f7..f5d3a49 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -36,6 +36,7 @@ use easy_jsonrpc_mw; use grin_wallet_util::OnionV3Address; use rand::thread_rng; use std::convert::TryFrom; +use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -1207,6 +1208,9 @@ pub trait OwnerRpc { /** Networked version of [Owner::set_top_level_directory](struct.Owner.html#method.set_top_level_directory). + + The wallet must be closed and the updater stopped before calling this method. Running listeners + should be restarted after opening the wallet from the new directory. ``` # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( # r#" @@ -2423,7 +2427,7 @@ where } fn set_tor_config(&self, tor_config: Option) -> Result<(), Error> { - Owner::set_tor_config(self, tor_config); + Owner::set_tor_config(self, tor_config)?; Ok(()) } @@ -2487,6 +2491,7 @@ pub fn run_doctest_owner( ) -> Result, String> { use easy_jsonrpc_mw::Handler; use grin_keychain::ExtKeychain; + use grin_wallet_config::initial_setup_wallet; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; use grin_wallet_libwallet::{api_impl, WalletInst}; @@ -2500,6 +2505,14 @@ pub fn run_doctest_owner( let _ = fs::remove_dir_all(test_dir); global::set_local_chain_type(ChainTypes::AutomatedTesting); + let _ = fs::create_dir_all(test_dir); + let config = initial_setup_wallet( + &ChainTypes::AutomatedTesting, + Some(PathBuf::from(test_dir)), + false, + ) + .unwrap(); + let mut wallet_proxy: WalletProxy< DefaultLCProvider, LocalWalletClient, @@ -2507,11 +2520,11 @@ pub fn run_doctest_owner( > = WalletProxy::new(test_dir); let chain = wallet_proxy.chain.clone(); - let rec_phrase_1 = util::ZeroingString::from( + let rec_phrase_1 = 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 empty_string = util::ZeroingString::from(""); + let empty_string = ZeroingString::from(""); let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); let mut wallet1 = @@ -2546,7 +2559,7 @@ pub fn run_doctest_owner( let mut slate_outer = Slate::blank(2, false); - let rec_phrase_2 = util::ZeroingString::from( + let rec_phrase_2 = 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", ); @@ -2669,8 +2682,11 @@ pub fn run_doctest_owner( ); } - let mut api_owner = Owner::new(wallet1, None); + let mut api_owner = Owner::new(wallet1, None, config.config_file_path); api_owner.doctest_mode = true; + if request["method"] == "set_top_level_directory" { + api_owner.close_wallet(None).unwrap(); + } let owner_api = &api_owner as &dyn OwnerRpc; let res = owner_api.handle_request(request).as_option(); let _ = fs::remove_dir_all(test_dir); diff --git a/api/src/tor_config.rs b/api/src/tor_config.rs new file mode 100644 index 0000000..3ef7a2f --- /dev/null +++ b/api/src/tor_config.rs @@ -0,0 +1,45 @@ +// Copyright 2026 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::TorConfig; +use crate::libwallet::Error; +use crate::util::Mutex; +use grin_wallet_config::config::get_global_config; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +/// Shared path to the active wallet configuration. +#[doc(hidden)] +#[derive(Clone)] +pub struct ConfigPath(Arc>); + +impl ConfigPath { + pub(crate) fn get(&self) -> PathBuf { + self.0.lock().clone() + } + + pub(crate) fn set(&self, path: PathBuf) { + *self.0.lock() = path; + } +} + +impl From for ConfigPath { + fn from(path: PathBuf) -> Self { + Self(Arc::new(Mutex::new(path))) + } +} + +pub(crate) fn load(config_path: &Path) -> Result { + Ok(get_global_config(config_path)?.tor_config()) +} diff --git a/config/Cargo.toml b/config/Cargo.toml index fa662cd..a50f1e4 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -14,7 +14,9 @@ rand = "0.6" serde = "1" serde_derive = "1" toml = "0.5" +toml_edit = "0.22" dirs = "2.0" +lazy_static = "1" ##### Grin Imports @@ -23,4 +25,6 @@ grin_util = { path = "../grin/util"} ##### +[dev-dependencies] +tempfile = "3.1" diff --git a/config/src/comments.rs b/config/src/comments.rs index 90e985f..a303c62 100644 --- a/config/src/comments.rs +++ b/config/src/comments.rs @@ -313,10 +313,10 @@ fn comments() -> HashMap { } fn get_key(line: &str) -> String { - if line.contains('[') && line.contains(']') { + if line.starts_with('[') && line.ends_with(']') { line.to_owned() - } else if line.contains('=') { - line.split('=').collect::>()[0].trim().to_owned() + } else if let Some((key, _)) = line.split_once('=') { + key.trim().to_owned() } else { "NOT_FOUND".to_owned() } @@ -340,91 +340,3 @@ pub fn insert_comments(orig: String) -> String { } ret_val } - -pub fn migrate_comments( - old_config: String, - new_config: String, - old_version: Option, -) -> String { - let comments = comments(); - // Prohibe the key we are basing on to introduce new comments for [tor.proxy] - let prohibited_key = match old_version { - None => vec!["[logging]"], - Some(_) => vec![], - }; - let mut vec_old_conf = vec![]; - let mut hm_key_cmt_old = HashMap::new(); - let old_conf: Vec<&str> = old_config.split_inclusive('\n').collect(); - // collect old key in a vec and insert old key/comments from the old conf in a hashmap - let vec_key_old = old_conf - .iter() - .filter_map(|line| { - let line_nospace = line.trim(); - let is_ascii_control = line_nospace.chars().all(|x| x.is_ascii_control()); - match line.contains("#") || is_ascii_control { - true => { - vec_old_conf.push(line.to_owned()); - None - } - false => { - let comments: String = vec_old_conf.iter().flat_map(|s| s.chars()).collect(); - let key = get_key(line_nospace); - match key != "NOT_FOUND" { - true => { - vec_old_conf.clear(); - hm_key_cmt_old.insert(key.clone(), comments); - Some(key) - } - false => None, - } - } - } - }) - .collect::>(); - - let new_conf: Vec<&str> = new_config.split_inclusive('\n').collect(); - // collect new key and the whole key line from the new config - let vec_key_cmt_new = new_conf - .iter() - .filter_map(|line| { - let line_nospace = line.trim(); - let is_ascii_control = line_nospace.chars().all(|x| x.is_ascii_control()); - match !line.contains("#") && !is_ascii_control { - true => { - let key = get_key(line_nospace); - match key != "NOT_FOUND" { - true => Some((key, line_nospace.to_string())), - false => None, - } - } - false => None, - } - }) - .collect::>(); - - let mut new_config_str = String::from(""); - // Merging old comments in the new config (except if the key is contained in the prohibited vec) with all new introduced key comments - for (key, key_line) in vec_key_cmt_new { - let old_key_exist = vec_key_old.iter().any(|old_key| *old_key == key); - let key_fmt = format!("{}\n", key_line); - if old_key_exist { - if prohibited_key.contains(&key.as_str()) { - // push new config key/comments - let value = comments.get(&key).unwrap(); - new_config_str.push_str(value); - new_config_str.push_str(&key_fmt); - } else { - // push old config key/comment - let value = hm_key_cmt_old.get(&key).unwrap(); - new_config_str.push_str(value); - new_config_str.push_str(&key_fmt); - } - } else { - // old key does not exist, we push new key/comments - let value = comments.get(&key).unwrap(); - new_config_str.push_str(value); - new_config_str.push_str(&key_fmt); - } - } - new_config_str -} diff --git a/config/src/config.rs b/config/src/config.rs index 2c37ae7..c32a5bb 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -14,24 +14,57 @@ //! Configuration file management -use crate::comments::{insert_comments, migrate_comments}; +use crate::comments::insert_comments; use crate::core::global; use crate::types::{ ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, TorBridgeConfig, TorProxyConfig, }; use crate::types::{TorConfig, WalletConfig}; use crate::util::logger::LoggingConfig; +use crate::util::{Mutex, RwLock}; + +use lazy_static::lazy_static; use rand::distributions::{Alphanumeric, Distribution}; -use rand::thread_rng; +use rand::{thread_rng, Rng}; +use std::collections::hash_map::Entry; +use std::collections::HashMap; use std::env; -use std::fs::{self, File}; +use std::fs::{self, File, OpenOptions}; use std::io::prelude::*; use std::io::BufReader; use std::path::{Path, PathBuf}; +use std::sync::mpsc::Sender; +use std::sync::Arc; use toml; +use toml_edit::{DocumentMut, Table}; + +type ConfigListeners = HashMap>; +type ConfigRegistry = HashMap; + +lazy_static! { + /// Global configuration instances and change listeners mapped to config path. + static ref CONFIG_INSTANCES: Arc> = + Arc::new(RwLock::new(HashMap::new())); + static ref CONFIG_SAVE_LOCK: Mutex<()> = Mutex::new(()); +} + +fn cache_loaded_config( + configs: &mut ConfigRegistry, + config_path: &Path, + config: GlobalWalletConfig, +) -> GlobalWalletConfig { + match configs.entry(config_path.to_path_buf()) { + Entry::Vacant(entry) => { + entry.insert((config.clone(), HashMap::new())); + config + } + Entry::Occupied(entry) => entry.get().0.clone(), + } +} /// Wallet configuration file name pub const WALLET_CONFIG_FILE_NAME: &str = "grin-wallet.toml"; +/// Wallet logging file name const WALLET_LOG_FILE_NAME: &str = "grin-wallet.log"; /// .grin folder, usually in home/.grin pub const GRIN_HOME: &str = ".grin"; @@ -42,6 +75,91 @@ pub const API_SECRET_FILE_NAME: &str = ".foreign_api_secret"; /// Owner API secret pub const OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret"; +fn set_global_config(config: GlobalWalletConfig) { + let mut configs = CONFIG_INSTANCES.write(); + let mut listeners = if let Some((_, l)) = configs.get(&config.config_file_path) { + l.clone() + } else { + HashMap::new() + }; + // Update config. + configs.insert( + config.config_file_path.clone(), + (config.clone(), listeners.clone()), + ); + // Notify listeners. + let mut failed = vec![]; + for l in listeners.clone() { + match l.1.send(()) { + Ok(_) => {} + Err(_) => { + failed.push(l.0.to_string()); + } + } + } + for f in &failed { + listeners.remove(f); + } + // Update listener list. + if !failed.is_empty() { + configs.insert(config.config_file_path.clone(), (config, listeners)); + } +} + +/// Reload configuration from disk and update the global instance. +pub fn reload_global_config(config_path: &Path) -> Result { + let _save_lock = CONFIG_SAVE_LOCK.lock(); + let config = GlobalWalletConfig::new(config_path.to_path_buf())?; + set_global_config(config.clone()); + Ok(config) +} + +/// Get global configuration using provided path. +pub fn get_global_config(config_path: &Path) -> Result { + { + let configs = CONFIG_INSTANCES.read(); + if let Some((config, _)) = configs.get(config_path) { + return Ok(config.clone()); + } + } + let config = GlobalWalletConfig::new(config_path.to_path_buf())?; + let mut configs = CONFIG_INSTANCES.write(); + Ok(cache_loaded_config(&mut configs, config_path, config)) +} + +/// Load, update and save a configuration as one operation. +pub fn update_global_config(config_path: &Path, update: F) -> Result<(), ConfigError> +where + F: FnOnce(&mut GlobalWalletConfig) -> Result<(), ConfigError>, +{ + let _save_lock = CONFIG_SAVE_LOCK.lock(); + let mut config = GlobalWalletConfig::new(config_path.to_path_buf())?; + update(&mut config)?; + config.save_locked() +} + +/// Add listener on config change. +pub fn add_global_config_listener(config_path: &PathBuf, listener_id: &str, tx: Sender<()>) { + let mut w_l = CONFIG_INSTANCES.write(); + match w_l.get_mut(config_path) { + None => {} + Some((_, listeners)) => { + listeners.insert(listener_id.to_string(), tx); + } + } +} + +/// Remove listener on config change. +pub fn remove_global_config_listener(config_path: &PathBuf, listener_id: &str) { + let mut w_l = CONFIG_INSTANCES.write(); + match w_l.get_mut(config_path) { + None => {} + Some((_, listeners)) => { + listeners.remove(listener_id); + } + } +} + /// Function to locate the wallet dir and grin-wallet.toml in the order /// a) config in top-dir if provided, b) in working dir, c) default dir /// Function to get wallet dir and create dirs if not existing @@ -50,7 +168,7 @@ pub fn get_wallet_path( create_path: bool, ) -> Result { // A - Detect grin-wallet.toml in working dir - let mut config_path = std::env::current_dir()?; + let mut config_path = env::current_dir()?; config_path.push(WALLET_CONFIG_FILE_NAME); if create_path == false && config_path.exists() { config_path.pop(); @@ -58,10 +176,7 @@ pub fn get_wallet_path( return Ok(config_path); }; // B - Select home directory - let mut wallet_path = match dirs::home_dir() { - Some(p) => p, - None => PathBuf::new(), - }; + let mut wallet_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new()); wallet_path.push(GRIN_HOME); wallet_path.push(chain_type.shortname()); // Create if the default path doesn't exist @@ -96,10 +211,7 @@ pub fn get_node_path( Ok(node_path) // 1) B If top dir exists, but no api_secret, return home dir } else { - let mut node_path = match dirs::home_dir() { - Some(p) => p, - None => PathBuf::new(), - }; + let mut node_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new()); node_path.push(GRIN_HOME); node_path.push(chain_type.shortname()); Ok(node_path) @@ -107,10 +219,7 @@ pub fn get_node_path( } // 2) If there is no top_dir provided, always return home dir None => { - let mut node_path = match dirs::home_dir() { - Some(p) => p, - None => PathBuf::new(), - }; + let mut node_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new()); node_path.push(GRIN_HOME); node_path.push(chain_type.shortname()); Ok(node_path) @@ -187,8 +296,11 @@ fn check_api_secret_file( } /// Initial wallet setup does the following -/// 1) Load wallet config if run without 'init' 2) create wallet if run with 'init'' -/// Try in thiss order a) current dir as template, b) in top path, or c) .grin home +/// 1) Load wallet config if run without 'init' 2) create wallet if run with 'init' +/// Try in this order: +/// a) current dir as template, +/// b) in top path, or +/// c) .grin home /// - load default config values /// - update the wallet and node dir to the correct paths /// - if grin-wallet.toml exists, but the wallet data dir does not, load config and continue wallet generation @@ -214,8 +326,7 @@ pub fn initial_setup_wallet( } else { env::current_dir()?.join(&fixed_path).canonicalize()? }; - let absolute_path = - std::path::PathBuf::from(absolute_path.to_str().unwrap().replace(r"\\?\", "")); + let absolute_path = PathBuf::from(absolute_path.to_str().unwrap().replace(r"\\?\", "")); data_path = Some(absolute_path); // Store the updated path } } @@ -234,12 +345,11 @@ pub fn initial_setup_wallet( config_path.push(WALLET_CONFIG_FILE_NAME); let mut data_dir = wallet_path.clone(); data_dir.push(GRIN_WALLET_DIR); - // Check if a config exists in theworking dir, if so load it + // Check if a config exists in the working dir, if so load it let (path, config) = match config_path.clone().exists() { // If the config does not exist, load default and updated node and wallet dir false => { - let mut default_config = GlobalWalletConfig::for_chain(chain_type); - default_config.config_file_path = Some(config_path.clone()); + let mut default_config = GlobalWalletConfig::for_chain(chain_type, &config_path); default_config.update_paths(&wallet_path, &node_path); // Write config file @@ -260,7 +370,7 @@ pub fn initial_setup_wallet( // Return config if not run with init true => { - // If run with init and seed does not yet exists, continue, else throw error + // If run with init and seed do not yet exists, continue, else throw error if data_dir.exists() && create_path == true { let msg = format!( "{} already exists in the target directory ({}). Please remove it first", @@ -269,12 +379,15 @@ pub fn initial_setup_wallet( ); return Err(ConfigError::SerializationError(msg)); } else { - let config = GlobalWalletConfig::new(config_path.to_str().unwrap())?; + let config = GlobalWalletConfig::new(config_path)?; (wallet_path, config) } } }; + // Set global config instance. + set_global_config(config.clone()); + // Check API secrets, if ok, return config check_api_secret_file(chain_type, Some(path.clone()), OWNER_API_SECRET_FILE_NAME)?; check_api_secret_file(chain_type, Some(path), API_SECRET_FILE_NAME)?; @@ -293,21 +406,15 @@ impl Default for GlobalWalletConfigMembers { } } -impl Default for GlobalWalletConfig { - fn default() -> GlobalWalletConfig { - GlobalWalletConfig { - config_file_path: None, - members: Some(GlobalWalletConfigMembers::default()), - } - } -} - impl GlobalWalletConfig { /// Same as GlobalConfig::default() but further tweaks parameters to /// apply defaults for each chain type - pub fn for_chain(chain_type: &global::ChainTypes) -> GlobalWalletConfig { - let mut defaults_conf = GlobalWalletConfig::default(); - let defaults = &mut defaults_conf.members.as_mut().unwrap().wallet; + pub fn for_chain(chain_type: &global::ChainTypes, file_path: &PathBuf) -> GlobalWalletConfig { + let mut defaults_conf = GlobalWalletConfig { + config_file_path: file_path.clone(), + members: GlobalWalletConfigMembers::default(), + }; + let defaults = &mut defaults_conf.members.wallet; defaults.chain_type = Some(*chain_type); match *chain_type { @@ -324,17 +431,29 @@ impl GlobalWalletConfig { } defaults_conf } + + /// Return the configured Tor settings, resolving the legacy default when the + /// config file does not yet contain a `[tor]` section. + pub fn tor_config(&self) -> TorConfig { + self.members.tor.clone().unwrap_or_else(|| TorConfig { + send_config_dir: self.members.wallet.data_file_dir.clone(), + ..TorConfig::default() + }) + } + /// Requires the path to a config file - pub fn new(file_path: &str) -> Result { - let mut return_value = GlobalWalletConfig::default(); - return_value.config_file_path = Some(PathBuf::from(&file_path)); + pub fn new(config_file_path: PathBuf) -> Result { + let return_value = GlobalWalletConfig { + config_file_path, + members: GlobalWalletConfigMembers::default(), + }; // Config file path is given but not valid - let config_file = return_value.config_file_path.clone().unwrap(); + let config_file = &return_value.config_file_path; if !config_file.exists() { - return Err(ConfigError::FileNotFoundError(String::from( - config_file.to_str().unwrap(), - ))); + return Err(ConfigError::FileNotFoundError( + config_file.display().to_string(), + )); } // Try to parse the config file if it exists, explode if it does exist but @@ -344,7 +463,7 @@ impl GlobalWalletConfig { /// Read config fn read_config(mut self) -> Result { - let config_file_path = self.config_file_path.as_mut().unwrap(); + let config_file_path = &self.config_file_path; let contents = fs::read_to_string(config_file_path.clone())?; let migrated = GlobalWalletConfig::migrate_config_file_version_none_to_2( contents, @@ -354,11 +473,11 @@ impl GlobalWalletConfig { let decoded: Result = toml::from_str(&fixed); match decoded { Ok(gc) => { - self.members = Some(gc); + self.members = gc; Ok(self) } Err(e) => Err(ConfigError::ParseError( - String::from(self.config_file_path.as_mut().unwrap().to_str().unwrap()), + self.config_file_path.display().to_string(), format!("{}", e), )), } @@ -375,32 +494,18 @@ impl GlobalWalletConfig { data_file_dir.push(GRIN_WALLET_DIR); secret_path.push(OWNER_API_SECRET_FILE_NAME); log_path.push(WALLET_LOG_FILE_NAME); - self.members.as_mut().unwrap().wallet.data_file_dir = - data_file_dir.to_str().unwrap().to_owned(); - self.members.as_mut().unwrap().wallet.node_api_secret_path = + self.members.wallet.data_file_dir = data_file_dir.to_str().unwrap().to_owned(); + self.members.wallet.node_api_secret_path = Some(node_secret_path.to_str().unwrap().to_owned()); - self.members.as_mut().unwrap().wallet.api_secret_path = - Some(secret_path.to_str().unwrap().to_owned()); - self.members - .as_mut() - .unwrap() - .logging - .as_mut() - .unwrap() - .log_file_path = log_path.to_str().unwrap().to_owned(); - self.members - .as_mut() - .unwrap() - .tor - .as_mut() - .unwrap() - .send_config_dir = tor_path.to_str().unwrap().to_owned(); + self.members.wallet.api_secret_path = Some(secret_path.to_str().unwrap().to_owned()); + self.members.logging.as_mut().unwrap().log_file_path = + log_path.to_str().unwrap().to_owned(); + self.members.tor.as_mut().unwrap().send_config_dir = tor_path.to_str().unwrap().to_owned(); } /// Serialize config pub fn ser_config(&mut self) -> Result { - let encoded: Result = - toml::to_string(self.members.as_mut().unwrap()); + let encoded: Result = toml::to_string(&self.members); match encoded { Ok(enc) => Ok(enc), Err(e) => Err(ConfigError::SerializationError(format!("{}", e))), @@ -415,17 +520,111 @@ impl GlobalWalletConfig { old_config: Option, old_version: Option, ) -> Result<(), ConfigError> { - let conf_out = self.ser_config()?; + self.write_to_path(Path::new(name), migration, old_config, old_version) + } + + fn write_to_path( + &mut self, + name: &Path, + migration: bool, + old_config: Option, + old_version: Option, + ) -> Result<(), ConfigError> { + let conf_out = GlobalWalletConfig::fix_log_level(self.ser_config()?); let commented_config = if migration { - migrate_comments(old_config.unwrap(), conf_out, old_version) + let old_config = old_config.unwrap(); + let new_config = insert_comments(conf_out); + GlobalWalletConfig::merge_config(&old_config, &new_config, old_version)? } else { - let fixed_config = GlobalWalletConfig::fix_log_level(conf_out); - insert_comments(fixed_config) + insert_comments(conf_out) }; let mut file = File::create(name)?; file.write_all(commented_config.as_bytes())?; Ok(()) } + + fn merge_config( + old_config: &str, + new_config: &str, + old_version: Option, + ) -> Result { + fn update( + current: &mut Table, + new: &Table, + known: Option<&Table>, + path: &str, + replace_logging_comments: bool, + ) { + if let Some(known) = known { + let removed = known + .iter() + .filter(|(key, _)| !new.contains_key(key)) + .map(|(key, _)| key.to_owned()) + .collect::>(); + for key in removed { + current.remove(&key); + } + } + + for (key, new_item) in new { + let item_path = if path.is_empty() { + key.to_owned() + } else { + format!("{}.{}", path, key) + }; + if let (Some(current_table), Some(new_table)) = ( + current.get_mut(key).and_then(|item| item.as_table_mut()), + new_item.as_table(), + ) { + if replace_logging_comments && item_path == "logging" { + *current_table.decor_mut() = new_table.decor().clone(); + } + update( + current_table, + new_table, + known + .and_then(|table| table.get(key)) + .and_then(|item| item.as_table()), + &item_path, + replace_logging_comments, + ); + } else if let Some(current_item) = current.get_mut(key) { + let mut replacement = new_item.clone(); + if let (Some(current_value), Some(new_value)) = + (current_item.as_value(), replacement.as_value_mut()) + { + *new_value.decor_mut() = current_value.decor().clone(); + } + *current_item = replacement; + } else { + current.insert_formatted(new.key(key).unwrap(), new_item.clone()); + } + } + } + + let mut current = old_config + .parse::() + .map_err(|e| ConfigError::SerializationError(format!("{}", e)))?; + let new = new_config + .parse::() + .map_err(|e| ConfigError::SerializationError(format!("{}", e)))?; + let known_members: GlobalWalletConfigMembers = toml::from_str( + &GlobalWalletConfig::fix_warning_level(old_config.to_owned()), + ) + .map_err(|e| ConfigError::SerializationError(format!("{}", e)))?; + let known = toml::to_string(&known_members) + .map_err(|e| ConfigError::SerializationError(format!("{}", e)))? + .parse::() + .map_err(|e| ConfigError::SerializationError(format!("{}", e)))?; + update( + current.as_table_mut(), + new.as_table(), + Some(known.as_table()), + "", + old_version.is_none(), + ); + Ok(current.to_string()) + } /// This migration does the following: /// - Adds "config_file_version = 2" /// - Introduce new key config_file_version, [tor.bridge] and [tor.proxy] @@ -449,31 +648,265 @@ impl GlobalWalletConfig { ..config }; let mut gc = GlobalWalletConfig { - members: Some(adjusted_config), - config_file_path: Some(config_file_path.clone()), + members: adjusted_config, + config_file_path: config_file_path.clone(), }; - let str_path = config_file_path.into_os_string().into_string().unwrap(); - gc.write_to_file( - &str_path, + gc.write_to_path( + &config_file_path, true, Some(config_str), config.config_file_version, )?; - let adjusted_config_str = fs::read_to_string(str_path.clone())?; + let adjusted_config_str = fs::read_to_string(config_file_path)?; Ok(adjusted_config_str) } // For forwards compatibility old config needs `Warning` log level changed to standard log::Level `WARN` fn fix_warning_level(conf: String) -> String { - conf.replace("Warning", "WARN") + GlobalWalletConfig::replace_log_levels(conf, &[("Warning", "WARN")]) } // For backwards compatibility only first letter of log level should be capitalised. fn fix_log_level(conf: String) -> String { - conf.replace("TRACE", "Trace") - .replace("DEBUG", "Debug") - .replace("INFO", "Info") - .replace("WARN", "Warning") - .replace("ERROR", "Error") + GlobalWalletConfig::replace_log_levels( + conf, + &[ + ("TRACE", "Trace"), + ("DEBUG", "Debug"), + ("INFO", "Info"), + ("WARN", "Warning"), + ("ERROR", "Error"), + ], + ) + } + + fn replace_log_levels(conf: String, replacements: &[(&str, &str)]) -> String { + conf.split_inclusive('\n') + .map(|line| { + let trimmed = line.trim_start(); + if trimmed.starts_with("stdout_log_level =") + || trimmed.starts_with("file_log_level =") + { + replacements + .iter() + .fold(line.to_owned(), |line, (from, to)| line.replace(from, to)) + } else { + line.to_owned() + } + }) + .collect() + } + + /// Save config to file and update global state after editing. + pub fn save(&mut self) -> Result<(), ConfigError> { + let _save_lock = CONFIG_SAVE_LOCK.lock(); + self.save_locked() + } + + fn save_locked(&mut self) -> Result<(), ConfigError> { + let path = self.config_file_path.clone(); + let mut tmp_name = path.as_os_str().to_os_string(); + tmp_name.push(format!("-{}.tmp", thread_rng().gen::())); + let tmp_path = PathBuf::from(tmp_name); + let contents = fs::read_to_string(&path)?; + let permissions = fs::metadata(&path)?.permissions(); + + let save_result = (|| { + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tmp_path)?; + fs::set_permissions(&tmp_path, permissions)?; + self.write_to_path( + &tmp_path, + true, + Some(contents), + self.members.config_file_version, + )?; + fs::rename(&tmp_path, &path)?; + Ok::<(), ConfigError>(()) + })(); + if save_result.is_err() { + let _ = fs::remove_file(&tmp_path); + } + save_result?; + + set_global_config(self.clone()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::core::global::ChainTypes; + use std::sync::{Arc, Barrier}; + use std::thread; + use tempfile::tempdir; + + #[test] + fn save_roundtrip() { + let dir = tempdir().unwrap(); + let path = dir.path().join(WALLET_CONFIG_FILE_NAME); + let mut config = GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &path); + config.members.tor.as_mut().unwrap().proxy.password = Some("secretERROR#[value]".into()); + config.write_to_path(&path, false, None, None).unwrap(); + + let contents = fs::read_to_string(&path) + .unwrap() + .replace( + "api_listen_port = 3415", + "api_listen_port = 3415\n# wallet future\nfuture_setting = \"wallet\"\n# future list\nfuture_values = [\n 1,\n 2,\n]", + ) + .replace( + "use_tor_listener = true", + "# custom key comment\nuse_tor_listener = true # custom inline comment\n# tor future\nfuture_setting = \"tor\"", + ); + fs::write(&path, format!("{contents}\n# custom trailing comment\n")).unwrap(); + let before = fs::read_to_string(&path).unwrap(); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&path, fs::Permissions::from_mode(0o600)).unwrap(); + } + + config.members.tor.as_mut().unwrap().use_tor_listener = false; + config.members.tor.as_mut().unwrap().proxy.password = Some("secretERROR#value".into()); + config.save().unwrap(); + + let contents = fs::read_to_string(&path).unwrap(); + assert_eq!( + contents, + before + .replace("use_tor_listener = true", "use_tor_listener = false") + .replace("secretERROR#[value]", "secretERROR#value") + ); + assert!(contents.contains("# custom trailing comment")); + assert!(contents.contains("# custom key comment")); + assert!(contents.contains("use_tor_listener = false # custom inline comment")); + let stored = GlobalWalletConfig::new(path.clone()).unwrap().tor_config(); + assert!(!stored.use_tor_listener); + assert_eq!(stored.proxy.password.as_deref(), Some("secretERROR#value")); + + config.members.tor.as_mut().unwrap().proxy.transport = Some("socks5".into()); + config.members.tor.as_mut().unwrap().bridge.client_option = Some("option [value]".into()); + config.save().unwrap(); + let stored = GlobalWalletConfig::new(path.clone()).unwrap().tor_config(); + assert_eq!(stored.proxy.transport.as_deref(), Some("socks5")); + assert_eq!( + stored.bridge.client_option.as_deref(), + Some("option [value]") + ); + + config.members.tor.as_mut().unwrap().proxy.transport = None; + config.members.tor.as_mut().unwrap().bridge.client_option = None; + config.save().unwrap(); + let stored = GlobalWalletConfig::new(path.clone()).unwrap().tor_config(); + assert_eq!(stored.proxy.transport, None); + assert_eq!(stored.bridge.client_option, None); + + let legacy = + fs::read_to_string(&path) + .unwrap() + .replacen("config_file_version = 2\n", "", 1); + let mut legacy = legacy.parse::().unwrap(); + let tor = legacy["tor"].as_table_mut().unwrap(); + tor.remove("bridge"); + tor.remove("proxy"); + fs::write(&path, legacy.to_string()).unwrap(); + let migrated = GlobalWalletConfig::new(path.clone()).unwrap(); + assert_eq!(migrated.members.config_file_version, Some(2)); + let migrated = fs::read_to_string(&path).unwrap(); + assert!(migrated.contains("# future list\nfuture_values = [\n 1,\n 2,\n]")); + assert!(migrated.contains("### TOR BRIDGE")); + assert!(migrated.contains("### TOR PROXY")); + assert!(fs::read_dir(dir.path()).unwrap().all(|entry| { + !entry + .unwrap() + .file_name() + .to_string_lossy() + .ends_with(".tmp") + })); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + assert_eq!( + fs::metadata(&path).unwrap().permissions().mode() & 0o777, + 0o600 + ); + } + } + + #[test] + fn legacy_tor() { + let path = PathBuf::from("legacy-wallet.toml"); + let mut config = GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &path); + config.members.wallet.data_file_dir = "legacy-wallet-data".into(); + config.members.tor = None; + + let resolved = config.tor_config(); + assert_eq!(resolved.send_config_dir, "legacy-wallet-data"); + assert!(resolved.use_tor_listener); + + let disabled = TorConfig { + use_tor_listener: false, + skip_send_attempt: Some(true), + ..TorConfig::default() + }; + config.members.tor = Some(disabled.clone()); + assert_eq!(config.tor_config(), disabled); + } + + #[test] + fn cached_config_wins() { + let path = PathBuf::from("concurrent-wallet.toml"); + let mut cached = GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &path); + cached.members.tor.as_mut().unwrap().use_tor_listener = false; + let loaded = GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &path); + let mut configs = HashMap::new(); + configs.insert(path.clone(), (cached.clone(), HashMap::new())); + + let returned = cache_loaded_config(&mut configs, &path, loaded); + + assert_eq!(returned, cached); + assert_eq!(configs.get(&path).unwrap().0, cached); + } + + #[test] + fn concurrent_update() { + let dir = tempdir().unwrap(); + let path = dir.path().join(WALLET_CONFIG_FILE_NAME); + let mut config = GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &path); + config.write_to_path(&path, false, None, None).unwrap(); + let barrier = Arc::new(Barrier::new(2)); + + let handles = [false, true].map(|update_tor| { + let path = path.clone(); + let barrier = barrier.clone(); + thread::spawn(move || { + barrier.wait(); + update_global_config(&path, |config| { + if update_tor { + config.members.tor.as_mut().unwrap().socks_proxy_addr = + "127.0.0.1:59051".into(); + } else { + config.members.wallet.api_listen_port = 3416; + } + Ok(()) + }) + .unwrap(); + }) + }); + for handle in handles { + handle.join().unwrap(); + } + + let stored = GlobalWalletConfig::new(path.clone()).unwrap(); + let cached = get_global_config(&path).unwrap(); + assert_eq!(cached, stored); + assert_eq!(stored.members.wallet.api_listen_port, 3416); + assert_eq!(stored.tor_config().socks_proxy_addr, "127.0.0.1:59051"); } } diff --git a/config/src/types.rs b/config/src/types.rs index ed53a97..bf33a57 100644 --- a/config/src/types.rs +++ b/config/src/types.rs @@ -312,9 +312,9 @@ impl fmt::Display for TorProxyConfig { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct GlobalWalletConfig { /// Keep track of the file we've read - pub config_file_path: Option, + pub config_file_path: PathBuf, /// Wallet members - pub members: Option, + pub members: GlobalWalletConfigMembers, } /// Wallet internal members diff --git a/controller/src/command.rs b/controller/src/command.rs index 40e0a57..397269c 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -24,13 +24,13 @@ use crate::impls::SlateGetter as _; use crate::keychain; use crate::libwallet::api_impl::types::update_tx_slate_state; use crate::libwallet::{ - self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack, + self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider, }; use crate::util::secp::key::SecretKey; use crate::util::{Mutex, ZeroingString}; use crate::{controller, display}; -use ::core::time; + use qr_code::QrCode; use serde_json as json; use std::convert::TryFrom; @@ -129,15 +129,12 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let rewind_hash = api.get_rewind_hash(m)?; - println!(); - println!("Wallet Rewind Hash"); - println!("-------------------------------------"); - println!("{}", rewind_hash); - println!(); - Ok(()) - })?; + let rewind_hash = owner_api.get_rewind_hash(keychain_mask)?; + println!(); + println!("Wallet Rewind Hash"); + println!("-------------------------------------"); + println!("{}", rewind_hash); + println!(); Ok(()) } @@ -158,50 +155,41 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, None, Some(owner_api), |api, m| { - let rewind_hash = args.rewind_hash; - let tip_height = api.node_height(m)?.height; - let start_height = match args.backwards_from_tip { - Some(b) => tip_height.saturating_sub(b), - None => match args.start_height { - Some(s) => s, - None => 1, - }, - }; - warn!( - "Starting view wallet output scan from height {} ...", - start_height - ); - let result = api.scan_rewind_hash(rewind_hash, Some(start_height)); - let deci_sec = time::Duration::from_millis(100); - thread::sleep(deci_sec); - match result { - Ok(res) => { - warn!("View wallet check complete"); - if res.total_balance != 0 { - display::view_wallet_output(res.clone(), tip_height, dark_scheme)?; - } - display::view_wallet_balance(res.clone(), tip_height, dark_scheme); - Ok(()) - } - Err(e) => { - error!("View wallet check failed: {}", e); - Err(e) + let rewind_hash = args.rewind_hash; + let tip_height = owner_api.node_height(None)?.height; + let start_height = match args.backwards_from_tip { + Some(b) => tip_height.saturating_sub(b), + None => args.start_height.unwrap_or_else(|| 1), + }; + warn!( + "Starting view wallet output scan from height {} ...", + start_height + ); + let result = owner_api.scan_rewind_hash(rewind_hash, Some(start_height)); + let deci_sec = Duration::from_millis(100); + thread::sleep(deci_sec); + match result { + Ok(res) => { + warn!("View wallet check complete"); + if res.total_balance != 0 { + display::view_wallet_output(res.clone(), tip_height, dark_scheme)?; } + display::view_wallet_balance(res.clone(), tip_height, dark_scheme); + Ok(()) } - })?; - Ok(()) + Err(e) => { + error!("View wallet check failed: {}", e); + Err(Error::from(e)) + } + } } -/// Arguments for listen command -pub struct ListenArgs {} - pub fn listen( owner_api: &mut Owner, keychain_mask: Arc>>, - config: &WalletConfig, - tor_config: &TorConfig, - _args: &ListenArgs, + config: WalletConfig, + bridge: Option, + use_tor: Option, g_args: &GlobalArgs, cli_mode: bool, test_mode: bool, @@ -212,20 +200,20 @@ where K: keychain::Keychain + 'static, { let wallet_inst = owner_api.wallet_inst.clone(); - let config = config.clone(); - let tor_config = tor_config.clone(); + let config_path = owner_api.config_path(); let g_args = g_args.clone(); let api_thread = thread::Builder::new() .name("wallet-http-listener".to_string()) .spawn(move || { let res = controller::foreign_listener( wallet_inst, + config_path, + bridge, + use_tor, keychain_mask, &config.api_listen_addr(), g_args.tls_conf.clone(), - tor_config.use_tor_listener, test_mode, - tor_config, ); if let Err(e) = res { error!("Error starting listener: {}", e); @@ -247,7 +235,6 @@ pub fn owner_api( owner_api: &mut Owner, keychain_mask: Option, config: &WalletConfig, - tor_config: &TorConfig, g_args: &GlobalArgs, test_mode: bool, ) -> Result<(), Error> @@ -260,13 +247,12 @@ where // also being run at the same time let km = Arc::new(Mutex::new(keychain_mask)); let res = controller::owner_listener( - owner_api.wallet_inst.clone(), + owner_api, km, config.owner_api_listen_addr().as_str(), g_args.api_secret.clone(), g_args.tls_conf.clone(), config.owner_api_include_foreign, - Some(tor_config.clone()), test_mode, ); if let Err(e) = res { @@ -291,25 +277,25 @@ where K: keychain::Keychain + 'static, { if args.create.is_none() { - let res = controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let acct_mappings = api.accounts(m)?; + let res = { + let acct_mappings = owner_api.accounts(keychain_mask)?; // give logging thread a moment to catch up thread::sleep(Duration::from_millis(200)); display::accounts(acct_mappings); Ok(()) - }); + }; if let Err(e) = res { error!("Error listing accounts: {}", e); return Err(Error::LibWallet(e)); } } else { let label = args.create.unwrap(); - let res = controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - api.create_account_path(m, &label)?; + let res = { + owner_api.create_account_path(keychain_mask, &label)?; thread::sleep(Duration::from_millis(200)); info!("Account: '{}' Created!", label); Ok(()) - }); + }; if let Err(e) = res { thread::sleep(Duration::from_millis(200)); error!("Error creating account '{}': {}", label, e); @@ -345,8 +331,8 @@ pub struct SendArgs { pub fn send( owner_api: &mut Owner, keychain_mask: Option<&SecretKey>, - tor_config: Option, args: SendArgs, + mut tor_config: TorConfig, dark_scheme: bool, test_mode: bool, ) -> Result<(), Error> @@ -358,84 +344,71 @@ where let mut slate = Slate::blank(2, false); let mut amount = args.amount; if args.use_max_amount { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let (_, wallet_info) = - api.retrieve_summary_info(m, true, args.minimum_confirmations)?; - amount = wallet_info.amount_currently_spendable; - Ok(()) - })?; - }; - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - if args.estimate_selection_strategies { - let strategies = vec!["smallest", "all"] - .into_iter() - .map(|strategy| { - let init_args = InitTxArgs { - src_acct_name: None, - amount: amount, - amount_includes_fee: Some(args.amount_includes_fee), - minimum_confirmations: args.minimum_confirmations, - max_outputs: args.max_outputs as u32, - num_change_outputs: args.change_outputs as u32, - selection_strategy_is_use_all: strategy == "all", - estimate_only: Some(true), - ..Default::default() - }; - let slate = api.init_send_tx(m, init_args)?; - Ok((strategy, slate.amount, slate.fee_fields)) - }) - .collect::, grin_wallet_libwallet::Error>>()?; - display::estimate(amount, strategies, dark_scheme); - return Ok(()); - } else { - let init_args = InitTxArgs { - src_acct_name: None, - amount: amount, - amount_includes_fee: Some(args.amount_includes_fee), - minimum_confirmations: args.minimum_confirmations, - max_outputs: args.max_outputs as u32, - num_change_outputs: args.change_outputs as u32, - selection_strategy_is_use_all: args.selection_strategy == "all", - target_slate_version: args.target_slate_version, - payment_proof_recipient_address: args.payment_proof_address.clone(), - ttl_blocks: args.ttl_blocks, - send_args: None, - late_lock: Some(args.late_lock), - ..Default::default() - }; - let result = api.init_send_tx(m, init_args); - slate = match result { - Ok(s) => { - info!( - "Tx created: {} grin to {} (strategy '{}')", - core::amount_to_hr_string(amount, false), - args.dest, - args.selection_strategy, - ); - s - } - Err(e) => { - info!("Tx not created: {}", e); - return Err(e); - } - }; - } - Ok(()) - })?; + let (_, wallet_info) = + owner_api.retrieve_summary_info(keychain_mask, true, args.minimum_confirmations)?; + amount = wallet_info.amount_currently_spendable; + } + if args.estimate_selection_strategies { + let strategies = vec!["smallest", "all"] + .into_iter() + .map(|strategy| { + let init_args = InitTxArgs { + src_acct_name: None, + amount, + amount_includes_fee: Some(args.amount_includes_fee), + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: args.change_outputs as u32, + selection_strategy_is_use_all: strategy == "all", + estimate_only: Some(true), + ..Default::default() + }; + let slate = owner_api.init_send_tx(keychain_mask, init_args)?; + Ok((strategy, slate.amount, slate.fee_fields)) + }) + .collect::, grin_wallet_libwallet::Error>>()?; + display::estimate(amount, strategies, dark_scheme); + } else { + let init_args = InitTxArgs { + src_acct_name: None, + amount, + amount_includes_fee: Some(args.amount_includes_fee), + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: args.change_outputs as u32, + selection_strategy_is_use_all: args.selection_strategy == "all", + target_slate_version: args.target_slate_version, + payment_proof_recipient_address: args.payment_proof_address.clone(), + ttl_blocks: args.ttl_blocks, + send_args: None, + late_lock: Some(args.late_lock), + ..Default::default() + }; + let result = owner_api.init_send_tx(keychain_mask, init_args); + slate = match result { + Ok(s) => { + info!( + "Tx created: {} grin to {} (strategy '{}')", + core::amount_to_hr_string(amount, false), + args.dest, + args.selection_strategy, + ); + s + } + Err(e) => { + info!("Tx not created: {}", e); + return Err(Error::from(e)); + } + }; + } if args.estimate_selection_strategies { return Ok(()); } - let tor_config = match tor_config { - Some(mut c) => { - if let Some(b) = args.bridge.clone() { - c.bridge.bridge_line = Some(b); - } - Some(c) - } - None => None, - }; + if let Some(b) = args.bridge.clone() { + tor_config.bridge.bridge_line = Some(b); + } let output_sp = || -> Result<(), Error> { Ok(output_slatepack( @@ -450,34 +423,27 @@ where )?) }; - let can_send = if let Some(tc) = tor_config.as_ref() { - tc.send_tor(args.skip_tor) - } else { - false - }; + let can_send = tor_config.send_tor(args.skip_tor); if test_mode || !can_send { return output_sp(); } - let res = try_slatepack_sync_workflow(&slate, &args.dest, tor_config, None, false); + let res = try_slatepack_sync_workflow(&slate, &args.dest, Some(tor_config), None, false); match res { Ok(s) => { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - api.tx_lock_outputs(m, &s)?; - let ret_slate = api.finalize_tx(m, &s)?; - let result = api.post_tx(m, &ret_slate, args.fluff); - match result { - Ok(_) => { - println!("Tx sent successfully",); - Ok(()) - } - Err(e) => { - error!("Tx sent fail: {}", e); - Err(e.into()) - } + owner_api.tx_lock_outputs(keychain_mask, &s)?; + let ret_slate = owner_api.finalize_tx(keychain_mask, &s)?; + let result = owner_api.post_tx(keychain_mask, &ret_slate, args.fluff); + match result { + Ok(_) => { + println!("Tx sent successfully",); } - })?; + Err(e) => { + error!("Tx sent fail: {}", e); + return Err(e.into()); + } + } } Err(e) => { error!("Error sending slate sync: {}", e); @@ -503,23 +469,17 @@ where K: keychain::Keychain + 'static, { // Output the slatepack file to stdout and to a file - let mut message = String::from(""); - let mut address = None; - let mut tld = String::from(""); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - address = match SlatepackAddress::try_from(dest) { - Ok(a) => Some(a), - Err(_) => None, - }; - // encrypt for recipient by default - let recipients = match address.clone() { - Some(a) => vec![a], - None => vec![], - }; - message = api.create_slatepack_message(m, &slate, Some(0), recipients)?; - tld = api.get_top_level_directory()?; - Ok(()) - })?; + let address = match SlatepackAddress::try_from(dest) { + Ok(a) => Some(a), + Err(_) => None, + }; + // encrypt for recipient by default + let recipients = match address.clone() { + Some(a) => vec![a], + None => vec![], + }; + let message = owner_api.create_slatepack_message(keychain_mask, &slate, Some(0), recipients)?; + let tld = owner_api.get_top_level_directory()?; // create a directory to which files will be output let slate_dir = format!("{}/{}", tld, "slatepack"); @@ -530,10 +490,7 @@ where }; if lock { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + owner_api.tx_lock_outputs(keychain_mask, &slate)?; } println!("{}", out_file_name); @@ -588,19 +545,15 @@ where let slate = match filename { Some(f) => { // otherwise, get slate from slatepack - let mut sl = None; - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let dec_key = api.get_slatepack_secret_key(m, 0)?; - let packer = Slatepacker::new(SlatepackerArgs { - sender: None, - recipients: vec![], - dec_key: Some(&dec_key), - }); - let pts = PathToSlatepack::new(f.into(), &packer, true); - sl = Some(pts.get_tx()?.0); - ret_address = pts.get_slatepack(true)?.sender; - Ok(()) - })?; + let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?; + let packer = Slatepacker::new(SlatepackerArgs { + sender: None, + recipients: vec![], + dec_key: Some(&dec_key), + }); + let pts = PathToSlatepack::new(f.into(), &packer, true); + let sl = Some(pts.get_tx()?.0); + ret_address = pts.get_slatepack(true)?.sender; sl } None => None, @@ -610,29 +563,26 @@ where Some(s) => s, None => { // try and parse directly from input_slatepack_message - let mut slate = Slate::blank(2, false); match message { Some(message) => { - controller::owner_single_use( - None, + let slate = owner_api.slate_from_slatepack_message( keychain_mask, - Some(owner_api), - |api, m| { - slate = - api.slate_from_slatepack_message(m, message.clone(), vec![0])?; - let slatepack = - api.decode_slatepack_message(m, message.clone(), vec![0])?; - ret_address = slatepack.sender; - Ok(()) - }, + message.clone(), + vec![0], )?; + let slatepack = owner_api.decode_slatepack_message( + keychain_mask, + message.clone(), + vec![0], + )?; + ret_address = slatepack.sender; + slate } None => { let msg = "No slate provided via file or direct input"; return Err(Error::GenericError(msg.into()).into()); } } - slate } }; Ok((slate, ret_address)) @@ -654,7 +604,7 @@ pub fn receive( keychain_mask: Option<&SecretKey>, g_args: &GlobalArgs, args: ReceiveArgs, - tor_config: Option, + mut tor_config: TorConfig, test_mode: bool, ) -> Result<(), Error> where @@ -674,23 +624,22 @@ where Some(&m) => Some(m.to_owned()), }; - let tor_config = match tor_config { - Some(mut c) => { - if let Some(b) = args.bridge { - c.bridge.bridge_line = Some(b); - } - Some(c) - } - None => None, - }; + if let Some(b) = args.bridge { + tor_config.bridge.bridge_line = Some(b); + } - controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| { - slate = api.receive_tx(&slate, Some(&g_args.account), None)?; - Ok(()) - })?; + controller::foreign_single_use( + owner_api.wallet_inst.clone(), + owner_api.config_path(), + km, + |api| { + slate = api.receive_tx(&slate, Some(&g_args.account), None)?; + Ok(()) + }, + )?; let dest = match ret_address { - Some(a) => String::try_from(&a).unwrap(), + Some(a) => String::try_from(&a)?, None => String::from(""), }; @@ -707,16 +656,12 @@ where )?) }; - let can_send = if let Some(tc) = tor_config.as_ref() { - tc.send_tor(args.skip_tor) - } else { - false - }; + let can_send = tor_config.send_tor(args.skip_tor); if test_mode || !can_send { return output_sp(); } - let res = try_slatepack_sync_workflow(&slate, &dest, tor_config, None, true); + let res = try_slatepack_sync_workflow(&slate, &dest, Some(tor_config), None, true); match res { Ok(s) => { @@ -765,14 +710,7 @@ where PathToSlatepack::new(f.into(), &packer, true).get_slatepack(false)? } None => match args.input_slatepack_message { - Some(mes) => { - let mut sp = Slatepack::default(); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - sp = api.decode_slatepack_message(m, mes, vec![])?; - Ok(()) - })?; - sp - } + Some(mes) => owner_api.decode_slatepack_message(keychain_mask, mes, vec![])?, None => { return Err(Error::ArgumentError("Invalid Slatepack Input".into()).into()); } @@ -791,27 +729,24 @@ where }); if slatepack.mode == 1 { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let dec_key = api.get_slatepack_secret_key(m, 0)?; - match slatepack.try_decrypt_payload(Some(&dec_key)) { - Ok(_) => { - println!("Slatepack is encrypted for this wallet"); - println!(); - println!("DECRYPTED SLATEPACK"); - println!("-------------------"); - println!("{}", slatepack); - let slate = packer.get_slate(&slatepack)?; - println!(); - println!("DECRYPTED SLATE"); - println!("---------------"); - println!("{}", slate); - } - Err(_) => { - println!("Slatepack payload cannot be decrypted by this wallet"); - } + let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?; + match slatepack.try_decrypt_payload(Some(&dec_key)) { + Ok(_) => { + println!("Slatepack is encrypted for this wallet"); + println!(); + println!("DECRYPTED SLATEPACK"); + println!("-------------------"); + println!("{}", slatepack); + let slate = packer.get_slate(&slatepack)?; + println!(); + println!("DECRYPTED SLATE"); + println!("---------------"); + println!("{}", slate); } - Ok(()) - })?; + Err(_) => { + println!("Slatepack payload cannot be decrypted by this wallet"); + } + } } else { let slate = packer.get_slate(&slatepack)?; println!("Slatepack is not encrypted"); @@ -861,34 +796,31 @@ where None => None, Some(&m) => Some(m.to_owned()), }; - controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| { - slate = api.finalize_tx(&slate, false)?; - Ok(()) - })?; + controller::foreign_single_use( + owner_api.wallet_inst.clone(), + owner_api.config_path(), + km, + |api| { + slate = api.finalize_tx(&slate, false)?; + Ok(()) + }, + )?; } else { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - slate = api.finalize_tx(m, &slate)?; - Ok(()) - })?; + slate = owner_api.finalize_tx(keychain_mask, &slate)? } if !&args.nopost { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let result = api.post_tx(m, &slate, args.fluff); - match result { - Ok(_) => { - info!( - "Transaction sent successfully, check the wallet again for confirmation." - ); - println!("Transaction posted"); - Ok(()) - } - Err(e) => { - error!("Tx not sent: {}", e); - Err(e) - } + let result = owner_api.post_tx(keychain_mask, &slate, args.fluff); + match result { + Ok(_) => { + info!("Transaction sent successfully, check the wallet again for confirmation."); + println!("Transaction posted"); } - })?; + Err(e) => { + error!("Tx not sent: {}", e); + return Err(Error::from(e)); + } + } } println!("Transaction finalized successfully"); @@ -931,11 +863,7 @@ where { let issue_args = args.issue_args.clone(); - let mut slate = Slate::blank(2, false); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - slate = api.issue_invoice_tx(m, issue_args)?; - Ok(()) - })?; + let slate = owner_api.issue_invoice_tx(keychain_mask, issue_args)?; output_slatepack( owner_api, @@ -969,7 +897,7 @@ pub struct ProcessInvoiceArgs { pub fn process_invoice( owner_api: &mut Owner, keychain_mask: Option<&SecretKey>, - tor_config: Option, + mut tor_config: TorConfig, args: ProcessInvoiceArgs, dark_scheme: bool, test_mode: bool, @@ -981,71 +909,61 @@ where { let mut slate = args.slate.clone(); let dest = match args.ret_address.clone() { - Some(a) => String::try_from(&a).unwrap(), + Some(a) => String::try_from(&a)?, None => String::from(""), }; - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - if args.estimate_selection_strategies { - let strategies = vec!["smallest", "all"] - .into_iter() - .map(|strategy| { - let init_args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: args.minimum_confirmations, - max_outputs: args.max_outputs as u32, - num_change_outputs: 1u32, - selection_strategy_is_use_all: strategy == "all", - estimate_only: Some(true), - ..Default::default() - }; - let slate = api.init_send_tx(m, init_args).unwrap(); - (strategy, slate.amount, slate.fee_fields) - }) - .collect(); - display::estimate(slate.amount, strategies, dark_scheme); - return Ok(()); - } else { - let init_args = InitTxArgs { - src_acct_name: None, - amount: 0, - minimum_confirmations: args.minimum_confirmations, - max_outputs: args.max_outputs as u32, - num_change_outputs: 1u32, - selection_strategy_is_use_all: args.selection_strategy == "all", - ttl_blocks: args.ttl_blocks, - send_args: None, - ..Default::default() - }; - let result = api.process_invoice_tx(m, &slate, init_args); - slate = match result { - Ok(s) => { - info!( - "Invoice processed: {} grin (strategy '{}')", - core::amount_to_hr_string(slate.amount, false), - args.selection_strategy, - ); - s - } - Err(e) => { - info!("Tx not created: {}", e); - return Err(e); - } - }; - } - Ok(()) - })?; - - let tor_config = match tor_config { - Some(mut c) => { - if let Some(b) = args.bridge { - c.bridge.bridge_line = Some(b); + if args.estimate_selection_strategies { + let strategies = vec!["smallest", "all"] + .into_iter() + .map(|strategy| { + let init_args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: 1u32, + selection_strategy_is_use_all: strategy == "all", + estimate_only: Some(true), + ..Default::default() + }; + let slate = owner_api.init_send_tx(keychain_mask, init_args).unwrap(); + (strategy, slate.amount, slate.fee_fields) + }) + .collect(); + display::estimate(slate.amount, strategies, dark_scheme); + } else { + let init_args = InitTxArgs { + src_acct_name: None, + amount: 0, + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: 1u32, + selection_strategy_is_use_all: args.selection_strategy == "all", + ttl_blocks: args.ttl_blocks, + send_args: None, + ..Default::default() + }; + let result = owner_api.process_invoice_tx(keychain_mask, &slate, init_args); + slate = match result { + Ok(s) => { + info!( + "Invoice processed: {} grin (strategy '{}')", + core::amount_to_hr_string(slate.amount, false), + args.selection_strategy, + ); + s } - Some(c) - } - None => None, - }; + Err(e) => { + info!("Tx not created: {}", e); + return Err(Error::from(e)); + } + }; + } + + if let Some(b) = args.bridge { + tor_config.bridge.bridge_line = Some(b); + } let output_sp = || -> Result<(), Error> { Ok(output_slatepack( @@ -1060,16 +978,12 @@ where )?) }; - let can_send = if let Some(tc) = tor_config.as_ref() { - tc.send_tor(args.skip_tor) - } else { - false - }; + let can_send = tor_config.send_tor(args.skip_tor); if test_mode || !can_send { return output_sp(); } - let res = try_slatepack_sync_workflow(&slate, &dest, tor_config, None, true); + let res = try_slatepack_sync_workflow(&slate, &dest, Some(tor_config), None, true); match res { Ok(s) => { @@ -1116,17 +1030,14 @@ where K: keychain::Keychain + 'static, { let updater_running = owner_api.updater_running.load(Ordering::Relaxed); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let (validated, wallet_info) = - api.retrieve_summary_info(m, true, args.minimum_confirmations)?; - display::info( - &g_args.account, - &wallet_info, - validated || updater_running, - dark_scheme, - ); - Ok(()) - })?; + let (validated, wallet_info) = + owner_api.retrieve_summary_info(keychain_mask, true, args.minimum_confirmations)?; + display::info( + &g_args.account, + &wallet_info, + validated || updater_running, + dark_scheme, + ); Ok(()) } @@ -1142,18 +1053,16 @@ where K: keychain::Keychain + 'static, { let updater_running = owner_api.updater_running.load(Ordering::Relaxed); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let res = api.node_height(m)?; - let (validated, outputs) = api.retrieve_outputs(m, g_args.show_spent, true, None)?; - display::outputs( - &g_args.account, - res.height, - validated || updater_running, - outputs, - dark_scheme, - )?; - Ok(()) - })?; + let res = owner_api.node_height(keychain_mask)?; + let (validated, outputs) = + owner_api.retrieve_outputs(keychain_mask, g_args.show_spent, true, None)?; + display::outputs( + &g_args.account, + res.height, + validated || updater_running, + outputs, + dark_scheme, + )?; Ok(()) } @@ -1177,56 +1086,53 @@ where K: keychain::Keychain + 'static, { let updater_running = owner_api.updater_running.load(Ordering::Relaxed); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let res = api.node_height(m)?; - // Note advanced query args not currently supported by command line client - let (validated, txs) = api.retrieve_txs(m, true, args.id, args.tx_slate_id, None)?; - let include_status = !args.id.is_some() && !args.tx_slate_id.is_some(); - // If view count is specified, restrict the TX list to `txs.len() - count` - let first_tx = args - .count - .map_or(0, |c| txs.len().saturating_sub(c as usize)); - display::txs( + let res = owner_api.node_height(keychain_mask)?; + // Note advanced query args not currently supported by command line client + let (validated, txs) = + owner_api.retrieve_txs(keychain_mask, true, args.id, args.tx_slate_id, None)?; + let include_status = !args.id.is_some() && !args.tx_slate_id.is_some(); + // If view count is specified, restrict the TX list to `txs.len() - count` + let first_tx = args + .count + .map_or(0, |c| txs.len().saturating_sub(c as usize)); + display::txs( + &g_args.account, + res.height, + validated || updater_running, + &txs[first_tx..], + include_status, + dark_scheme, + )?; + + // if given a particular transaction id or uuid, also get and display associated + // inputs/outputs and messages + let id = if args.id.is_some() { + args.id + } else if args.tx_slate_id.is_some() { + if let Some(tx) = txs.iter().find(|t| t.tx_slate_id == args.tx_slate_id) { + Some(tx.id) + } else { + println!("Could not find a transaction matching given txid.\n"); + None + } + } else { + None + }; + + if id.is_some() { + let (_, outputs) = owner_api.retrieve_outputs(keychain_mask, true, false, id)?; + display::outputs( &g_args.account, res.height, validated || updater_running, - &txs[first_tx..], - include_status, + outputs, dark_scheme, )?; - - // if given a particular transaction id or uuid, also get and display associated - // inputs/outputs and messages - let id = if args.id.is_some() { - args.id - } else if args.tx_slate_id.is_some() { - if let Some(tx) = txs.iter().find(|t| t.tx_slate_id == args.tx_slate_id) { - Some(tx.id) - } else { - println!("Could not find a transaction matching given txid.\n"); - None - } - } else { - None - }; - - if id.is_some() { - let (_, outputs) = api.retrieve_outputs(m, true, false, id)?; - display::outputs( - &g_args.account, - res.height, - validated || updater_running, - outputs, - dark_scheme, - )?; - // should only be one here, but just in case - for tx in txs { - display::payment_proof(&tx)?; - } + // should only be one here, but just in case + for tx in txs { + display::payment_proof(&tx)?; } - - Ok(()) - })?; + } Ok(()) } @@ -1256,11 +1162,8 @@ where )?; let fluff = args.fluff; - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - api.post_tx(m, &slate, fluff)?; - info!("Posted transaction"); - return Ok(()); - })?; + owner_api.post_tx(keychain_mask, &slate, fluff)?; + info!("Posted transaction"); Ok(()) } @@ -1281,53 +1184,55 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let stored_tx_slate = match api.get_stored_tx(m, Some(args.id), None)? { - None => { + let stored_tx_slate = match owner_api.get_stored_tx(keychain_mask, Some(args.id), None)? { + None => { + error!( + "Transaction with id {} does not have transaction data. Not reposting.", + args.id + ); + return Ok(()); + } + Some(s) => s, + }; + let (_, txs) = owner_api.retrieve_txs(keychain_mask, true, Some(args.id), None, None)?; + match args.dump_file { + None => { + if txs[0].confirmed { error!( - "Transaction with id {} does not have transaction data. Not reposting.", + "Transaction with id {} is confirmed. Not reposting.", args.id ); return Ok(()); } - Some(s) => s, - }; - let (_, txs) = api.retrieve_txs(m, true, Some(args.id), None, None)?; - match args.dump_file { - None => { - if txs[0].confirmed { - error!( - "Transaction with id {} is confirmed. Not reposting.", - args.id - ); - return Ok(()); - } - if libwallet::sig_is_blank( - &stored_tx_slate.tx.as_ref().unwrap().kernels()[0].excess_sig, - ) { - error!("Transaction at {} has not been finalized.", args.id); - return Ok(()); - } - - match api.post_tx(m, &stored_tx_slate, args.fluff) { - Ok(_) => info!("Reposted transaction at {}", args.id), - Err(e) => error!("Could not repost transaction at {}. Reason: {}", args.id, e), - } + if libwallet::sig_is_blank( + &stored_tx_slate.tx.as_ref().unwrap().kernels()[0].excess_sig, + ) { + error!("Transaction at {} has not been finalized.", args.id); return Ok(()); } - Some(f) => { - let mut tx_file = File::create(f.clone())?; - tx_file.write_all( + + match owner_api.post_tx(keychain_mask, &stored_tx_slate, args.fluff) { + Ok(_) => info!("Reposted transaction at {}", args.id), + Err(e) => error!("Could not repost transaction at {}. Reason: {}", args.id, e), + } + return Ok(()); + } + Some(f) => { + let mut tx_file = + File::create(f.clone()).map_err(|e| Error::GenericError(format!("{}", e)))?; + tx_file + .write_all( json::to_string(&stored_tx_slate.tx.unwrap()) .unwrap() .as_bytes(), - )?; - tx_file.sync_all()?; - info!("Dumped transaction data for tx {} to {}", args.id, f); - return Ok(()); - } + ) + .map_err(|e| Error::GenericError(format!("{}", e)))?; + tx_file + .sync_all() + .map_err(|e| Error::GenericError(format!("{}", e)))?; + info!("Dumped transaction data for tx {} to {}", args.id, f); } - })?; + } Ok(()) } @@ -1348,20 +1253,17 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let result = api.cancel_tx(m, args.tx_id, args.tx_slate_id); - match result { - Ok(_) => { - info!("Transaction {} Cancelled", args.tx_id_string); - Ok(()) - } - Err(e) => { - error!("TX Cancellation failed: {}", e); - Err(e) - } + let result = owner_api.cancel_tx(keychain_mask, args.tx_id, args.tx_slate_id); + match result { + Ok(_) => { + info!("Transaction {} Cancelled", args.tx_id_string); + Ok(()) } - })?; - Ok(()) + Err(e) => { + error!("TX Cancellation failed: {}", e); + Err(Error::from(e)) + } + } } /// wallet check @@ -1381,29 +1283,23 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let tip_height = api.node_height(m)?.height; - let start_height = match args.backwards_from_tip { - Some(b) => tip_height.saturating_sub(b), - None => match args.start_height { - Some(s) => s, - None => 1, - }, - }; - warn!("Starting output scan from height {} ...", start_height); - let result = api.scan(m, Some(start_height), args.delete_unconfirmed); - match result { - Ok(_) => { - warn!("Wallet check complete",); - Ok(()) - } - Err(e) => { - error!("Wallet check failed: {}", e); - Err(e) - } + let tip_height = owner_api.node_height(keychain_mask)?.height; + let start_height = match args.backwards_from_tip { + Some(b) => tip_height.saturating_sub(b), + None => args.start_height.unwrap_or_else(|| 1), + }; + warn!("Starting output scan from height {} ...", start_height); + let result = owner_api.scan(keychain_mask, Some(start_height), args.delete_unconfirmed); + match result { + Ok(_) => { + warn!("Wallet check complete",); + Ok(()) } - })?; - Ok(()) + Err(e) => { + error!("Wallet check failed: {}", e); + Err(Error::from(e)) + } + } } /// Payment Proof Address @@ -1417,16 +1313,13 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - // Just address at derivation index 0 for now - let address = api.get_slatepack_address(m, 0)?; - println!(); - println!("Address for account - {}", g_args.account); - println!("-------------------------------------"); - println!("{}", address); - println!(); - Ok(()) - })?; + // Just address at derivation index 0 for now + let address = owner_api.get_slatepack_address(keychain_mask, 0)?; + println!(); + println!("Address for account - {}", g_args.account); + println!("-------------------------------------"); + println!("{}", address); + println!(); Ok(()) } @@ -1447,24 +1340,26 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let result = api.retrieve_payment_proof(m, true, args.id, args.tx_slate_id); - match result { - Ok(p) => { - // actually export proof - let mut proof_file = File::create(args.output_file.clone())?; - proof_file.write_all(json::to_string_pretty(&p).unwrap().as_bytes())?; - proof_file.sync_all()?; - warn!("Payment proof exported to {}", args.output_file); - Ok(()) - } - Err(e) => { - error!("Proof export failed: {}", e); - Err(e) - } + let result = owner_api.retrieve_payment_proof(keychain_mask, true, args.id, args.tx_slate_id); + match result { + Ok(p) => { + // actually export proof + let mut proof_file = File::create(args.output_file.clone()) + .map_err(|e| Error::GenericError(format!("{}", e)))?; + proof_file + .write_all(json::to_string_pretty(&p).unwrap().as_bytes()) + .map_err(|e| Error::GenericError(format!("{}", e)))?; + proof_file + .sync_all() + .map_err(|e| Error::GenericError(format!("{}", e)))?; + warn!("Payment proof exported to {}", args.output_file); + Ok(()) } - })?; - Ok(()) + Err(e) => { + error!("Proof export failed: {}", e); + Err(Error::from(e)) + } + } } /// Proof Verify Args @@ -1482,51 +1377,50 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let mut proof_f = match File::open(&args.input_file) { - Ok(p) => p, - Err(e) => { - let msg = format!("{}", e); - error!( - "Unable to open payment proof file at {}: {}", - args.input_file, e - ); - return Err(libwallet::Error::PaymentProofParsing(msg)); - } - }; - let mut proof = String::new(); - proof_f.read_to_string(&mut proof)?; - // read - let proof: PaymentProof = match json::from_str(&proof) { - Ok(p) => p, - Err(e) => { - let msg = format!("{}", e); - error!("Unable to parse payment proof file: {}", e); - return Err(libwallet::Error::PaymentProofParsing(msg)); - } - }; - let result = api.verify_payment_proof(m, &proof); - match result { - Ok((iam_sender, iam_recipient)) => { - println!("Payment proof's signatures are valid."); - if iam_sender { - println!("The proof's sender address belongs to this wallet."); - } - if iam_recipient { - println!("The proof's recipient address belongs to this wallet."); - } - if !iam_recipient && !iam_sender { - println!( - "Neither the proof's sender nor recipient address belongs to this wallet." - ); - } - Ok(()) - } - Err(e) => { - error!("Proof not valid: {}", e); - Err(e) - } + let mut proof_f = match File::open(&args.input_file) { + Ok(p) => p, + Err(e) => { + let msg = format!("{}", e); + error!( + "Unable to open payment proof file at {}: {}", + args.input_file, e + ); + return Err(Error::from(libwallet::Error::PaymentProofParsing(msg))); } - })?; - Ok(()) + }; + let mut proof = String::new(); + proof_f + .read_to_string(&mut proof) + .map_err(|e| Error::GenericError(format!("{}", e)))?; + // read + let proof: PaymentProof = match json::from_str(&proof) { + Ok(p) => p, + Err(e) => { + let msg = format!("{}", e); + error!("Unable to parse payment proof file: {}", e); + return Err(Error::from(libwallet::Error::PaymentProofParsing(msg))); + } + }; + let result = owner_api.verify_payment_proof(keychain_mask, &proof); + match result { + Ok((iam_sender, iam_recipient)) => { + println!("Payment proof's signatures are valid."); + if iam_sender { + println!("The proof's sender address belongs to this wallet."); + } + if iam_recipient { + println!("The proof's recipient address belongs to this wallet."); + } + if !iam_recipient && !iam_sender { + println!( + "Neither the proof's sender nor recipient address belongs to this wallet." + ); + } + Ok(()) + } + Err(e) => { + error!("Proof not valid: {}", e); + Err(Error::from(e)) + } + } } diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 4a3264a..c2f2c4e 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Controller for wallet.. instantiates and handles listeners (or single-run +//! Controller for wallet, instantiates and handles listeners (or single-run //! invocations) as needed. use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig}; use crate::config::TorConfig; +use crate::impls::tor::config as tor_config; +use crate::impls::tor::process as tor_process; +use crate::impls::tor::{bridge as tor_bridge, proxy as tor_proxy}; use crate::keychain::Keychain; use crate::libwallet::{ address, Error, NodeClient, NodeVersionInfo, Slate, SlatepackAddress, WalletInst, @@ -25,6 +28,7 @@ use crate::util::secp::key::SecretKey; use crate::util::{from_hex, static_secp_instance, to_base64, Mutex}; use grin_wallet_api::JsonId; use grin_wallet_util::OnionV3Address; + use hyper::header::HeaderValue; use hyper::{Request, Response, StatusCode}; use qr_code::QrCode; @@ -34,21 +38,23 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::io::Cursor; use std::net::SocketAddr; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; - -use crate::impls::tor::config as tor_config; -use crate::impls::tor::process as tor_process; -use crate::impls::tor::{bridge as tor_bridge, proxy as tor_proxy}; +use std::thread; use crate::apiwallet::{ - EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign, + ConfigPath, EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign, ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc, }; use easy_jsonrpc_mw; use easy_jsonrpc_mw::{Handler, MaybeReply}; use grin_api::ApiBody; -use grin_wallet_impls::tor::arti::start_tor_service; -use grin_wallet_impls::tor::Tor; +use grin_wallet_config::config::{ + add_global_config_listener, get_global_config, remove_global_config_listener, +}; +use grin_wallet_impls::tor::arti::{start_tor_service, stop_tor_service}; +use grin_wallet_impls::tor::process::TorProcess; use http_body_util::{BodyExt, Full}; use hyper::body::Incoming; use tokio::sync::mpsc; @@ -91,7 +97,7 @@ fn init_tor_listener( tor_dir: String, addr: &str, tor_config: TorConfig, -) -> Result { +) -> Result { info!("Starting external Tor Process listener."); let mut process = tor_process::TorProcess::new(); @@ -131,19 +137,15 @@ fn init_tor_listener( .completion_percent(100) .launch() .map_err(|e| Error::TorProcess(format!("{:?}", e)))?; - Ok(Tor { - process: Some(process), - service: None, - client: None, - }) + Ok(process) } /// 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: Option>>>>, + wallet: Arc>>>, keychain_mask: Option<&SecretKey>, - api_context: Option<&mut Owner>, + config_path: PathBuf, f: F, ) -> Result<(), Error> where @@ -152,27 +154,14 @@ where C: NodeClient + 'static, K: Keychain + 'static, { - match api_context { - Some(c) => f(c, keychain_mask)?, - None => { - let wallet = match wallet { - Some(w) => w, - None => { - return Err(Error::GenericError( - "Instantiated wallet or Owner API context must be provided".to_string(), - )) - } - }; - f(&mut Owner::new(wallet, None), keychain_mask)? - } - } - Ok(()) + f(&mut Owner::new(wallet, None, config_path), keychain_mask) } /// 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<'a, L, F, C, K>( wallet: Arc>>>, + config_path: PathBuf, keychain_mask: Option, f: F, ) -> Result<(), Error> @@ -184,6 +173,7 @@ where { f(&mut Foreign::new( wallet, + config_path, keychain_mask, Some(check_middleware), false, @@ -196,13 +186,12 @@ where /// Note keychain mask is only provided here in case the foreign listener is also being used /// in the same wallet instance pub fn owner_listener( - wallet: Arc + 'static>>>, + owner_api: &mut Owner, keychain_mask: Arc>>, addr: &str, api_secret: Option, tls_config: Option, owner_api_include_foreign: Option, - tor_config: Option, test_mode: bool, ) -> Result<(), Error> where @@ -226,12 +215,8 @@ where running_foreign = true; } - let api_handler_v3 = OwnerAPIHandlerV3::new( - wallet.clone(), - keychain_mask.clone(), - tor_config.clone(), - running_foreign, - ); + let wallet = owner_api.wallet_inst.clone(); + let api_handler_v3 = OwnerAPIHandlerV3::new(owner_api, keychain_mask.clone(), running_foreign); router .add_route("/v3/owner", Arc::new(api_handler_v3)) @@ -240,8 +225,12 @@ where // If so configured, add the foreign API to the same port if running_foreign { warn!("Starting HTTP Foreign API on Owner server at {}.", addr); - let foreign_api_handler_v2 = - ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config)); + let foreign_api_handler_v2 = ForeignAPIHandlerV2::new( + wallet, + owner_api.shared_config_path(), + keychain_mask, + test_mode, + ); router .add_route("/v2/foreign", Arc::new(foreign_api_handler_v2)) .map_err(|_| Error::GenericError("Router failed to add route".to_string()))?; @@ -265,99 +254,162 @@ where /// port and wrapping the calls pub fn foreign_listener( wallet: Arc + 'static>>>, + config_path: PathBuf, + bridge: Option, + use_tor: Option, keychain_mask: Arc>>, addr: &str, tls_config: Option, - use_tor: bool, test_mode: bool, - tor_config: TorConfig, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: Keychain + 'static, { - // Check if wallet has been opened first - let (sec_key, tor_dir, onion_address) = { - let mask = keychain_mask.lock(); - let mut w_lock = wallet.lock(); - let lc = w_lock.lc_provider()?; - let w_inst = lc.wallet_inst()?; - let k = w_inst.keychain((&mask).as_ref())?; - let parent_key_id = w_inst.parent_key_id(); - let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0) - .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; - let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?); - let onion_address = OnionV3Address::from_private(&sec_key.0) - .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; - (sec_key, tor_dir, onion_address) - }; + loop { + let (restart_tx, restart_rx) = std::sync::mpsc::channel::<()>(); + register_config_listener(&config_path, "foreign_listener", restart_tx)?; - let api_handler_v2 = ForeignAPIHandlerV2::new( - wallet, - keychain_mask, - test_mode, - Mutex::new(Some(tor_config.clone())), - ); - let mut router = Router::new(); + let tor_config = listener_tor_config(&config_path, bridge.clone(), use_tor)?; - router - .add_route("/v2/foreign", Arc::new(api_handler_v2)) - .map_err(|_| Error::GenericError("Router failed to add route".to_string()))?; - - let api_chan: (mpsc::Sender<()>, mpsc::Receiver<()>) = mpsc::channel::<()>(1); - - let mut apis = ApiServer::new(); - warn!("Starting HTTP Foreign listener 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, api_chan) - .map_err(|_| Error::GenericError("API thread failed to start".to_string()))?; - warn!("HTTP Foreign listener started."); - - // Need to keep external process in scope while the listener is running. - let tor_service = if use_tor { - let use_integrated = tor_config.use_integrated.unwrap_or(false); - let res = if use_integrated { - start_tor_service(sec_key, &tor_dir, addr, tor_config.clone()) - } else { - init_tor_listener(sec_key, tor_dir, addr, tor_config) + // Check if wallet has been opened first + let (sec_key, tor_dir, onion_address) = { + let mask = keychain_mask.lock(); + let mut w_lock = wallet.lock(); + let lc = w_lock.lc_provider()?; + let w_inst = lc.wallet_inst()?; + let k = w_inst.keychain((&mask).as_ref())?; + let parent_key_id = w_inst.parent_key_id(); + let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0) + .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; + let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?); + let onion_address = OnionV3Address::from_private(&sec_key.0) + .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; + (sec_key, tor_dir, onion_address) }; - match res { - Ok(service) => { - warn!( - "Starting Tor Hidden Service for API listener at address {}, binding to {}", - onion_address, addr - ); - Ok(Some(service)) - } - Err(e) => { - warn!("Unable to start TOR listener"); - error!("Tor Error: {}", e); - warn!("Listener is available on {}", addr); - Err(e) - } - } - } else { - Ok(None) - }; - if tor_service.is_ok() { - if let Some(_) = tor_service.as_ref().unwrap() { - let sp_address = SlatepackAddress::try_from(onion_address.clone())?; - let qr_string = match QrCode::new(sp_address.to_string()) { - Ok(qr) => qr.to_string(false, 3), - Err(_) => "Failed to generate QR code!".to_string(), + let api_handler_v2 = ForeignAPIHandlerV2::new( + wallet.clone(), + config_path.clone().into(), + keychain_mask.clone(), + test_mode, + ); + let mut router = Router::new(); + + router + .add_route("/v2/foreign", Arc::new(api_handler_v2)) + .map_err(|_| Error::GenericError("Router failed to add route".to_string()))?; + + let api_chan: (mpsc::Sender<()>, mpsc::Receiver<()>) = mpsc::channel::<()>(1); + let stop_tx = api_chan.0.clone(); + + let mut apis = ApiServer::new(); + warn!("Starting HTTP Foreign listener 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.clone(), api_chan) + .map_err(|_| Error::GenericError("API thread failed to start".to_string()))?; + warn!("HTTP Foreign listener started."); + + // Need to keep external process in scope while the listener is running. + let _tor_service = if tor_config.use_tor_listener { + let use_integrated = tor_config.use_integrated.unwrap_or(false); + + let res: Result, Error> = if use_integrated { + match start_tor_service(sec_key, addr, &tor_config.clone()) { + Ok(_) => Ok(None), + Err(e) => { + error!("Error starting integrated Tor service: {}", e); + Err(e) + } + } + } else { + match init_tor_listener(sec_key, tor_dir, addr, tor_config.clone()) { + Ok(p) => Ok(Some(p)), + Err(e) => { + error!("Error starting external Tor listener: {}", e); + Err(e) + } + } }; - warn!("Slatepack Address is: {}\n{}", sp_address, qr_string); + match res { + Ok(service) => { + warn!( + "Starting Tor Hidden Service for API listener at address {}, binding to {}", + onion_address, addr + ); + let sp_address = SlatepackAddress::try_from(onion_address.clone())?; + let qr_string = match QrCode::new(sp_address.to_string()) { + Ok(qr) => qr.to_string(false, 3), + Err(_) => "Failed to generate QR code!".to_string(), + }; + warn!("Slatepack Address is: {}\n{}", sp_address, qr_string); + Ok(service) + } + Err(e) => { + warn!("Unable to start TOR listener"); + error!("Tor Error: {}", e); + warn!("Listener is available on {}", addr); + Err(e) + } + } } else { warn!("Listener is available on {}", addr); - } - } + Ok(None) + }; - api_thread - .join() - .map_err(|e| Error::GenericError(format!("API thread panicked :{:?}", e))) + // Start thread to check if restart is needed. + let restart_needed = Arc::new(AtomicBool::new(false)); + let t_restart_needed = restart_needed.clone(); + thread::spawn(move || { + if restart_rx.recv().is_ok() { + t_restart_needed.store(true, Ordering::SeqCst); + let _ = stop_tx.try_send(()); + } + }); + + let res = api_thread + .join() + .map_err(|e| Error::GenericError(format!("API thread panicked :{:?}", e))); + + if tor_config.use_tor_listener && tor_config.use_integrated.unwrap_or(false) { + stop_tor_service(onion_address.to_string())?; + } + + if restart_needed.load(Ordering::Relaxed) { + continue; + } + + remove_global_config_listener(&config_path, "foreign_listener"); + + return res; + } +} + +fn register_config_listener( + config_path: &PathBuf, + listener_id: &str, + tx: std::sync::mpsc::Sender<()>, +) -> Result<(), Error> { + get_global_config(config_path)?; + add_global_config_listener(config_path, listener_id, tx); + Ok(()) +} + +fn listener_tor_config( + config_path: &Path, + bridge: Option, + use_tor: Option, +) -> Result { + let mut tor_config = get_global_config(config_path)?.tor_config(); + if let Some(bridge) = bridge { + tor_config.bridge.bridge_line = Some(bridge); + } + if let Some(use_tor) = use_tor { + tor_config.use_tor_listener = use_tor; + } + Ok(tor_config) } /// V3 API Handler/Wrapper for owner functions, which include a secure @@ -577,17 +629,16 @@ where { /// Create a new owner API handler for GET methods pub fn new( - wallet: Arc + 'static>>>, + owner_api: &Owner, keychain_mask: Arc>>, - tor_config: Option, running_foreign: bool, ) -> OwnerAPIHandlerV3 { - let owner_api = Owner::new(wallet.clone(), None); - owner_api.set_tor_config(tor_config); - let owner_api = Arc::new(owner_api); + let wallet = owner_api.wallet_inst.clone(); + let config_path = owner_api.shared_config_path(); + let owner_api = Owner::new(wallet.clone(), None, config_path); OwnerAPIHandlerV3 { wallet, - owner_api, + owner_api: Arc::new(owner_api), shared_key: Arc::new(Mutex::new(None)), keychain_mask, running_foreign, @@ -706,14 +757,14 @@ where C: NodeClient + 'static, K: Keychain + 'static, { - /// Wallet instance + /// Wallet instance. pub wallet: Arc + 'static>>>, + /// Wallet configuration path. + pub config_path: ConfigPath, /// Keychain mask pub keychain_mask: Arc>>, /// run in doctest mode pub test_mode: bool, - /// tor config - pub tor_config: Mutex>, } impl ForeignAPIHandlerV2 @@ -725,15 +776,15 @@ where /// Create a new foreign API handler for GET methods pub fn new( wallet: Arc + 'static>>>, + config_path: ConfigPath, keychain_mask: Arc>>, test_mode: bool, - tor_config: Mutex>, ) -> ForeignAPIHandlerV2 { ForeignAPIHandlerV2 { wallet, + config_path, keychain_mask, test_mode, - tor_config, } } @@ -756,11 +807,10 @@ where req: Request, mask: Option, wallet: Arc + 'static>>>, + config_path: ConfigPath, test_mode: bool, - tor_config: Option, ) -> Result, Error> { - let api = Foreign::new(wallet, mask, Some(check_middleware), test_mode); - api.set_tor_config(tor_config); + let api = Foreign::new(wallet, config_path, mask, Some(check_middleware), test_mode); let res = Self::call_api(req, api).await?; Ok(json_response_pretty(&res)) } @@ -776,10 +826,10 @@ where let mask = self.keychain_mask.lock().clone(); let wallet = self.wallet.clone(); let test_mode = self.test_mode; - let tor_config = self.tor_config.lock().clone(); + let config_path = self.config_path.clone(); Box::pin(async move { - match Self::handle_post_request(req, mask, wallet, test_mode, tor_config).await { + match Self::handle_post_request(req, mask, wallet, config_path, test_mode).await { Ok(v) => Ok(v), Err(e) => { error!("Request Error: {:?}", e); @@ -866,3 +916,48 @@ where serde_json::from_reader(cursor) .map_err(|e| Error::GenericError(format!("Invalid request body: {}", e))) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::core::global::ChainTypes; + use grin_wallet_config::config::update_global_config; + use grin_wallet_config::GlobalWalletConfig; + use std::fs; + use std::sync::mpsc::channel; + use std::time::Duration; + use uuid::Uuid; + + #[test] + fn listener_reload() { + let dir = std::env::temp_dir().join(format!("grin-wallet-listener-{}", Uuid::new_v4())); + fs::create_dir(&dir).unwrap(); + let path = dir.join("grin-wallet.toml"); + let mut config = GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &path); + config + .write_to_file(path.to_str().unwrap(), false, None, None) + .unwrap(); + + let (restart_tx, restart_rx) = channel(); + register_config_listener(&path, "test_listener", restart_tx).unwrap(); + + update_global_config(&path, |config| { + config.members.tor.as_mut().unwrap().socks_proxy_addr = "127.0.0.1:59051".into(); + Ok(()) + }) + .unwrap(); + restart_rx.recv_timeout(Duration::from_secs(1)).unwrap(); + + let tor_config = + listener_tor_config(&path, Some("obfs4 test-bridge".into()), Some(false)).unwrap(); + assert_eq!(tor_config.socks_proxy_addr, "127.0.0.1:59051"); + assert_eq!( + tor_config.bridge.bridge_line.as_deref(), + Some("obfs4 test-bridge") + ); + assert!(!tor_config.use_tor_listener); + + remove_global_config_listener(&path, "test_listener"); + fs::remove_dir_all(dir).unwrap(); + } +} diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index 744a5e1..35413bd 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -19,6 +19,7 @@ extern crate grin_wallet_impls as impls; use grin_core as core; use grin_keychain as keychain; +use std::path::PathBuf; use self::core::global; use self::keychain::{ExtKeychain, Keychain}; @@ -78,33 +79,48 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height // test default accounts exist - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let accounts = api.accounts(m)?; - assert_eq!(accounts[0].label, "default"); - assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0)); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let accounts = api.accounts(m)?; + assert_eq!(accounts[0].label, "default"); + assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0)); + Ok(()) + }, + )?; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let new_path = api.create_account_path(m, "account1").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); - let new_path = api.create_account_path(m, "account2").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); - let new_path = api.create_account_path(m, "account3").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0)); - // trying to add same label again should fail - let res = api.create_account_path(m, "account1"); - assert!(res.is_err()); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let new_path = api.create_account_path(m, "account1").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); + let new_path = api.create_account_path(m, "account2").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); + let new_path = api.create_account_path(m, "account3").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0)); + // trying to add same label again should fail + let res = api.create_account_path(m, "account1"); + assert!(res.is_err()); + Ok(()) + }, + )?; // add account to wallet 2 - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let new_path = api.create_account_path(m, "listener_account").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let new_path = api.create_account_path(m, "listener_account").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); + Ok(()) + }, + )?; // Default wallet 2 to listen on that account { @@ -128,17 +144,22 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 5 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 5); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 12); + assert_eq!(wallet1_info.total, 5 * reward); + assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward); + // check tx log as well + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert_eq!(txs.len(), 5); + Ok(()) + }, + )?; // now check second account { @@ -149,115 +170,150 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { w.set_parent_key_id_by_name("account1")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // check last confirmed height on this account is different from above (should be 0) - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 0); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 7 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 7); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // check last confirmed height on this account is different from above (should be 0) + let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 0); + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 12); + assert_eq!(wallet1_info.total, 7 * reward); + assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward); + // check tx log as well + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert_eq!(txs.len(), 7); + Ok(()) + }, + )?; // should be nothing in default account { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("default")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 0); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 0,); - assert_eq!(wallet1_info.amount_currently_spendable, 0,); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 0); + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 12); + assert_eq!(wallet1_info.total, 0,); + assert_eq!(wallet1_info.amount_currently_spendable, 0,); + // check tx log as well + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert_eq!(txs.len(), 0); + Ok(()) + }, + )?; // Send a tx to another wallet { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account1")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate)?; - api.tx_lock_outputs(m, &slate)?; - slate = api.finalize_tx(m, &slate)?; - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let args = InitTxArgs { + src_acct_name: None, + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.init_send_tx(m, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate)?; + api.tx_lock_outputs(m, &slate)?; + slate = api.finalize_tx(m, &slate)?; + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 9); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 13); + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert_eq!(txs.len(), 9); + Ok(()) + }, + )?; // other account should be untouched { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account2")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 12); - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - println!("{:?}", txs); - assert_eq!(txs.len(), 5); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 12); + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 13); + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + println!("{:?}", txs); + assert_eq!(txs.len(), 5); + Ok(()) + }, + )?; // wallet 2 should only have this tx on the listener account - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, 13); + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert_eq!(txs.len(), 1); + Ok(()) + }, + )?; // Default account on wallet 2 should be untouched { wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("default")?; } - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (_, wallet2_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet2_info.last_confirmed_height, 0); - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 13); - assert_eq!(wallet2_info.total, 0,); - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet2_info) = api.retrieve_summary_info(m, false, 1)?; + assert_eq!(wallet2_info.last_confirmed_height, 0); + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, 13); + assert_eq!(wallet2_info.total, 0,); + assert_eq!(wallet2_info.amount_currently_spendable, 0,); + // check tx log as well + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert_eq!(txs.len(), 0); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/build_chain.rs b/controller/tests/build_chain.rs index cd9a2d8..7120e44 100644 --- a/controller/tests/build_chain.rs +++ b/controller/tests/build_chain.rs @@ -21,6 +21,7 @@ extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; use grin_core as core; +use std::path::PathBuf; use self::libwallet::{InitTxArgs, Slate}; use impls::test_framework::{self, LocalWalletClient}; @@ -73,15 +74,25 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal // Stop the scanning updater threads because it extends the time needed to build the chain // exponentially - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, _m| { - api.stop_updater()?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, _m| { + api.stop_updater()?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, _m| { - api.stop_updater()?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, _m| { + api.stop_updater()?; + Ok(()) + }, + )?; // few values to keep things shorter let reward = core::consensus::REWARD; @@ -94,17 +105,22 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal let mut wallet_1_has_funds = false; // Check wallet 1 contents - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 spendable - {}", - wallet1_info.amount_currently_spendable - ); - if wallet1_info.amount_currently_spendable > reward { - wallet_1_has_funds = true; - } - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + debug!( + "Wallet 1 spendable - {}", + wallet1_info.amount_currently_spendable + ); + if wallet1_info.amount_currently_spendable > reward { + wallet_1_has_funds = true; + } + Ok(()) + }, + )?; // let's say 1 in every 3 blocks has a transaction (i.e. random 0 here and wallet1 has funds) let transact = rng.gen_range(0, 2) == 0; @@ -121,14 +137,14 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal let mut slate = Slate::blank(1, false); debug!("Creating TX for {}", amount); wallet::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { src_acct_name: None, - amount: amount, + amount, minimum_confirmations: 1, max_outputs: 500, num_change_outputs: 1, diff --git a/controller/tests/build_output.rs b/controller/tests/build_output.rs index 7f05a2a..491e916 100644 --- a/controller/tests/build_output.rs +++ b/controller/tests/build_output.rs @@ -25,6 +25,7 @@ use grin_util::{secp, ZeroingString}; use grin_wallet_libwallet as libwallet; use impls::test_framework::LocalWalletClient; use rand::{thread_rng, Rng}; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -66,23 +67,28 @@ fn build_output_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); let features = OutputFeatures::Plain; let amount = 60_000_000_000; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - let built_output = sender_api.build_output(m, features, amount)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + let built_output = sender_api.build_output(m, features, amount)?; - let key_id = built_output.key_id; - assert_eq!(key_id.to_path(), ExtKeychainPath::new(3, 0, 0, 0, 0)); + let key_id = built_output.key_id; + assert_eq!(key_id.to_path(), ExtKeychainPath::new(3, 0, 0, 0, 0)); - let blind = built_output.blind; - let key = keychain.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?; - assert_eq!(blind, BlindingFactor::from_secret_key(key.clone())); + let blind = built_output.blind; + let key = keychain.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?; + assert_eq!(blind, BlindingFactor::from_secret_key(key.clone())); - let output = built_output.output; - assert_eq!(output.features(), features); - assert_eq!(output.commitment(), secp.commit(amount, key)?); - output.verify_proof()?; + let output = built_output.output; + assert_eq!(output.features(), features); + assert_eq!(output.commitment(), secp.commit(amount, key)?); + output.verify_proof()?; - Ok(()) - })?; + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/check.rs b/controller/tests/check.rs index c9c5990..d526335 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -27,6 +27,7 @@ use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlate, SlatePutter as _}; use libwallet::{InitTxArgs, NodeClient}; use std::cmp; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -92,20 +93,30 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let cm = global::coinbase_maturity() as u64; // assume all testing precedes soft fork height // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "named_account_1")?; - api.create_account_path(m, "account_2")?; - api.create_account_path(m, "account_3")?; - api.set_active_account(m, "named_account_1")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "named_account_1")?; + api.create_account_path(m, "account_2")?; + api.create_account_path(m, "account_3")?; + api.set_active_account(m, "named_account_1")?; + Ok(()) + }, + )?; // add account to wallet 2 - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - api.create_account_path(m, "account_1")?; - api.set_active_account(m, "account_1")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account_1")?; + api.set_active_account(m, "account_1")?; + Ok(()) + }, + )?; // Do some mining let bh = 20u64; @@ -113,26 +124,36 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Sanity check contents - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); - assert_eq!(wallet1_info.total, c); - assert_eq!(txs.len(), bh as usize); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); + // check tx log as well + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); + assert_eq!(wallet1_info.total, c); + assert_eq!(txs.len(), bh as usize); + Ok(()) + }, + )?; // Accidentally delete some outputs let mut w1_outputs_commits = vec![]; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - w1_outputs_commits = api.retrieve_outputs(m, false, true, None)?.1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + w1_outputs_commits = api.retrieve_outputs(m, false, true, None)?.1; + Ok(()) + }, + )?; let w1_outputs: Vec = w1_outputs_commits.into_iter().map(|m| m.output).collect(); { @@ -149,73 +170,108 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { } // check we have a problem now - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); - assert!(wallet1_info.total != c); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); + assert_ne!(wallet1_info.total, c); + Ok(()) + }, + )?; // this should restore our missing outputs - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.scan(m, None, true)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, true)?; + Ok(()) + }, + )?; // check our outputs match again - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.total, bh * reward); - // And check account names haven't been splatted - let accounts = api.accounts(m)?; - assert_eq!(accounts.len(), 4); - assert!(api.set_active_account(m, "account_1").is_err()); - assert!(api.set_active_account(m, "named_account_1").is_ok()); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.total, bh * reward); + // And check account names haven't been splatted + let accounts = api.accounts(m)?; + assert_eq!(accounts.len(), 4); + assert!(api.set_active_account(m, "account_1").is_err()); + assert!(api.set_active_account(m, "named_account_1").is_ok()); + Ok(()) + }, + )?; // perform a transaction, but don't let it finish - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: None, - amount: reward * 2, - minimum_confirmations: cm, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - let send_file = format!("{}/part_tx_1.tx", test_dir); - PathToSlate(send_file.into()).put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // send to send + let args = InitTxArgs { + src_acct_name: None, + amount: reward * 2, + minimum_confirmations: cm, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + // output tx file + let send_file = format!("{}/part_tx_1.tx", test_dir); + PathToSlate(send_file.into()).put_tx(&slate, false)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; // check we're all locked - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert!(wallet1_info.amount_currently_spendable == 0); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.amount_currently_spendable, 0); + Ok(()) + }, + )?; // unlock/restore - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.scan(m, None, true)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, true)?; + Ok(()) + }, + )?; // check spendable amount again - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); @@ -412,13 +468,18 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er bh += 3; // 0) Check repair when all is okay should leave wallet contents alone - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.scan(m, None, true)?; - let info = wallet_info!(wallet1.clone(), m)?; - assert_eq!(info.amount_currently_spendable, base_amount * 6); - assert_eq!(info.total, base_amount * 6); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, true)?; + let info = wallet_info!(wallet1.clone(), m)?; + assert_eq!(info.amount_currently_spendable, base_amount * 6); + assert_eq!(info.total, base_amount * 6); + Ok(()) + }, + )?; // send some funds to wallet 2 send_to_dest!( @@ -462,33 +523,53 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er // seed + BIP32 path. // 1) a full restore should recover all of them: - wallet::controller::owner_single_use(Some(wallet3.clone()), mask3, None, |api, m| { - api.scan(m, None, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet3.clone(), + mask3, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, false)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet3.clone()), mask3, None, |api, m| { - let info = wallet_info!(wallet3.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); - assert_eq!(info.total, base_amount * 21); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet3.clone(), + mask3, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet3.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); + assert_eq!(info.total, base_amount * 21); + Ok(()) + }, + )?; // 2) scan should recover them into a single wallet - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.scan(m, None, true)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, true)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let info = wallet_info!(wallet1.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet1.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); + Ok(()) + }, + )?; // 3) If I recover from seed and start using the wallet without restoring, // scan should restore the older outputs @@ -520,26 +601,41 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); bh += cm as u64; - wallet::controller::owner_single_use(Some(wallet4.clone()), mask4, None, |api, m| { - let info = wallet_info!(wallet4.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 9); - assert_eq!(info.amount_currently_spendable, base_amount * 45); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet4.clone(), + mask4, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet4.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 9); + assert_eq!(info.amount_currently_spendable, base_amount * 45); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet5.clone()), mask5, None, |api, m| { - api.scan(m, None, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet5.clone(), + mask5, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, false)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet5.clone()), mask5, None, |api, m| { - let info = wallet_info!(wallet5.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 9); - assert_eq!(info.amount_currently_spendable, base_amount * (45)); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet5.clone(), + mask5, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet5.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 9); + assert_eq!(info.amount_currently_spendable, base_amount * (45)); + Ok(()) + }, + )?; // 4) If I recover from seed and start using the wallet without restoring, // scan should restore the older outputs @@ -575,26 +671,41 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er ); bh += cm as u64; - wallet::controller::owner_single_use(Some(wallet6.clone()), mask6, None, |api, m| { - let info = wallet_info!(wallet6.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 12); - assert_eq!(info.amount_currently_spendable, base_amount * 78); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet6.clone(), + mask6, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet6.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 12); + assert_eq!(info.amount_currently_spendable, base_amount * 78); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet6.clone()), mask6, None, |api, m| { - api.scan(m, None, true)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet6.clone(), + mask6, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, true)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet6.clone()), mask6, None, |api, m| { - let info = wallet_info!(wallet6.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 12); - assert_eq!(info.amount_currently_spendable, base_amount * (78)); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet6.clone(), + mask6, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet6.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 12); + assert_eq!(info.amount_currently_spendable, base_amount * (78)); + Ok(()) + }, + )?; // 5) Start using same seed with a different account, amounts should // be distinct and restore should return funds from other account @@ -623,11 +734,16 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er bh += 3; // mix it up a bit - wallet::controller::owner_single_use(Some(wallet7.clone()), mask7, None, |api, m| { - api.create_account_path(m, "account_1")?; - api.set_active_account(m, "account_1")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet7.clone(), + mask7, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account_1")?; + api.set_active_account(m, "account_1")?; + Ok(()) + }, + )?; send_to_dest!( miner.clone(), @@ -656,42 +772,57 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); bh += cm as u64; - wallet::controller::owner_single_use(Some(wallet7.clone()), mask7, None, |api, m| { - let info = wallet_info!(wallet7.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 3); - assert_eq!(info.amount_currently_spendable, base_amount * 6); - api.set_active_account(m, "default")?; - let info = wallet_info!(wallet7.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet7.clone(), + mask7, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet7.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 3); + assert_eq!(info.amount_currently_spendable, base_amount * 6); + api.set_active_account(m, "default")?; + let info = wallet_info!(wallet7.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet8.clone()), mask8, None, |api, m| { - api.scan(m, None, false)?; - let info = wallet_info!(wallet8.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - api.set_active_account(m, "account_1")?; - let info = wallet_info!(wallet8.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 3); - assert_eq!(info.amount_currently_spendable, base_amount * 6); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet8.clone(), + mask8, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, false)?; + let info = wallet_info!(wallet8.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); + api.set_active_account(m, "account_1")?; + let info = wallet_info!(wallet8.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 3); + assert_eq!(info.amount_currently_spendable, base_amount * 6); + Ok(()) + }, + )?; // 6) Start using same seed with a different account, now overwriting // ids on account 2 as well, scan should get all outputs created // to now into 2 accounts - wallet::controller::owner_single_use(Some(wallet9.clone()), mask9, None, |api, m| { - api.create_account_path(m, "account_1")?; - api.set_active_account(m, "account_1")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet9.clone(), + mask9, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account_1")?; + api.set_active_account(m, "account_1")?; + Ok(()) + }, + )?; send_to_dest!( miner.clone(), @@ -717,43 +848,53 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er bh += 3; let _bh = bh; - wallet::controller::owner_single_use(Some(wallet9.clone()), mask9, None, |api, m| { - let info = wallet_info!(wallet9.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); - api.scan(m, None, true)?; - let info = wallet_info!(wallet9.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); + wallet::controller::owner_single_use( + wallet9.clone(), + mask9, + PathBuf::from(test_dir), + |api, m| { + let info = wallet_info!(wallet9.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); + api.scan(m, None, true)?; + let info = wallet_info!(wallet9.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); - api.set_active_account(m, "default")?; - let info = wallet_info!(wallet9.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - Ok(()) - })?; + api.set_active_account(m, "default")?; + let info = wallet_info!(wallet9.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); // 7) Ensure scan creates missing accounts - wallet::controller::owner_single_use(Some(wallet10.clone()), mask10, None, |api, m| { - api.scan(m, None, true)?; - api.set_active_account(m, "account_1")?; - let info = wallet_info!(wallet10.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); + wallet::controller::owner_single_use( + wallet10.clone(), + mask10, + PathBuf::from(test_dir), + |api, m| { + api.scan(m, None, true)?; + api.set_active_account(m, "account_1")?; + let info = wallet_info!(wallet10.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); - api.set_active_account(m, "default")?; - let info = wallet_info!(wallet10.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - Ok(()) - })?; + api.set_active_account(m, "default")?; + let info = wallet_info!(wallet10.clone(), m)?; + let outputs = api.retrieve_outputs(m, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); @@ -877,13 +1018,18 @@ fn multi_batch_scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); let mut output_commits = vec![]; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, bh * reward); - output_commits = api.retrieve_outputs(m, false, true, None)?.1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, info) = api.retrieve_summary_info(m, true, 1)?; + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, bh * reward); + output_commits = api.retrieve_outputs(m, false, true, None)?.1; + Ok(()) + }, + )?; { wallet_inst!(wallet1, w); @@ -930,13 +1076,18 @@ fn multi_batch_scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> } assert!(batches > 1); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; - let (_, info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(info.total, bh * reward); - assert_eq!(restored_outputs.len(), bh as usize); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; + let (_, info) = api.retrieve_summary_info(m, false, 1)?; + assert_eq!(info.total, bh * reward); + assert_eq!(restored_outputs.len(), bh as usize); + Ok(()) + }, + )?; stopper.store(false, Ordering::Relaxed); thread::sleep(Duration::from_millis(200)); @@ -974,12 +1125,17 @@ fn restore_corrupted_outputs_across_batches_impl( test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); let mut output_commits = vec![]; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(info.total, bh * reward); - output_commits = api.retrieve_outputs(m, false, true, None)?.1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, info) = api.retrieve_summary_info(m, true, 1)?; + assert_eq!(info.total, bh * reward); + output_commits = api.retrieve_outputs(m, false, true, None)?.1; + Ok(()) + }, + )?; { wallet_inst!(wallet1, w); @@ -1017,25 +1173,30 @@ fn restore_corrupted_outputs_across_batches_impl( assert_eq!(last_scanned.hash, ""); } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, outputs_after_first_batch) = api.retrieve_outputs(m, false, false, None)?; - assert_eq!(outputs_after_first_batch.len(), (bh - 1) as usize); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, outputs_after_first_batch) = api.retrieve_outputs(m, false, false, None)?; + assert_eq!(outputs_after_first_batch.len(), (bh - 1) as usize); - let (_, info) = api.retrieve_summary_info(m, true, 1)?; - let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; - let unique_keys = restored_outputs - .iter() - .map(|o| o.output.key_id.clone()) - .collect::>(); + let (_, info) = api.retrieve_summary_info(m, true, 1)?; + let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; + let unique_keys = restored_outputs + .iter() + .map(|o| o.output.key_id.clone()) + .collect::>(); - assert_eq!(info.total, bh * reward); - assert_eq!(restored_outputs.len(), bh as usize); - assert_eq!(unique_keys.len(), bh as usize); - assert!(restored_outputs - .iter() - .all(|o| o.output.status == libwallet::OutputStatus::Unspent)); - Ok(()) - })?; + assert_eq!(info.total, bh * reward); + assert_eq!(restored_outputs.len(), bh as usize); + assert_eq!(unique_keys.len(), bh as usize); + assert!(restored_outputs + .iter() + .all(|o| o.output.status == libwallet::OutputStatus::Unspent)); + Ok(()) + }, + )?; stopper.store(false, Ordering::Relaxed); thread::sleep(Duration::from_millis(200)); diff --git a/controller/tests/config_api.rs b/controller/tests/config_api.rs new file mode 100644 index 0000000..fd37e10 --- /dev/null +++ b/controller/tests/config_api.rs @@ -0,0 +1,228 @@ +// Copyright 2026 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. + +extern crate grin_wallet_api as api; +extern crate grin_wallet_config as config; +extern crate grin_wallet_impls as impls; +extern crate grin_wallet_libwallet as libwallet; + +use config::config::{get_global_config, reload_global_config}; +use config::GlobalWalletConfig; +use impls::test_framework::LocalWalletClient; +use libwallet::{InitTxArgs, InitTxSendArgs}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::atomic::Ordering; + +mod common; +use common::{clean_output_dir, create_wallet_proxy, setup}; + +fn snapshot(path: &Path) -> Vec<(PathBuf, Vec)> { + fn collect(root: &Path, path: &Path, files: &mut Vec<(PathBuf, Vec)>) { + for entry in fs::read_dir(path).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_dir() { + collect(root, &path, files); + } else if path.file_name().and_then(|name| name.to_str()) != Some("lock.mdb") { + files.push(( + path.strip_prefix(root).unwrap().into(), + fs::read(path).unwrap(), + )); + } + } + } + + let mut files = vec![]; + collect(path, path, &mut files); + files.sort_by(|left, right| left.0.cmp(&right.0)); + files +} + +#[test] +fn tor_disable() { + let test_dir = "test_output/config_api_tor"; + setup(test_dir); + let mut wallet_proxy = create_wallet_proxy(test_dir); + create_wallet_and_add!( + client, + wallet, + mask, + test_dir, + "wallet", + None, + &mut wallet_proxy, + false + ); + let path = PathBuf::from(test_dir).join("grin-wallet.toml"); + let mut config = + GlobalWalletConfig::for_chain(&grin_core::global::ChainTypes::AutomatedTesting, &path); + config + .write_to_file(path.to_str().unwrap(), false, None, None) + .unwrap(); + reload_global_config(&path).unwrap(); + let mut contents = fs::read_to_string(&path).unwrap(); + let tor = contents.find("[tor]").unwrap(); + let logging = contents.find("[logging]").unwrap(); + contents.replace_range(tor..logging, ""); + let contents = contents.replace("api_listen_port = 3415", "api_listen_port = 3416"); + fs::write(&path, contents).unwrap(); + + let owner = api::Owner::new(wallet.clone(), None, path.clone()); + owner.set_tor_config(None).unwrap(); + + let stored_config = GlobalWalletConfig::new(path.clone()).unwrap(); + assert_eq!(stored_config.members.wallet.api_listen_port, 3416); + let stored = stored_config.tor_config(); + let cached = get_global_config(&path).unwrap().tor_config(); + assert!(!stored.use_tor_listener); + assert_eq!(stored.skip_send_attempt, Some(true)); + assert_eq!( + stored.send_config_dir, + stored_config.members.wallet.data_file_dir + ); + assert_eq!(cached, stored); + + drop(owner); + drop(wallet); + drop(wallet_proxy); + clean_output_dir(test_dir); +} + +#[test] +fn send_preflight() { + let test_dir = "test_output/config_api_send"; + setup(test_dir); + let mut wallet_proxy = create_wallet_proxy(test_dir); + create_wallet_and_add!( + client, + wallet, + mask, + test_dir, + "wallet", + None, + &mut wallet_proxy, + false + ); + let owner = api::Owner::new( + wallet.clone(), + None, + PathBuf::from(test_dir).join("missing.toml"), + ); + let mask = mask.as_ref(); + let (_, before_txs) = owner.retrieve_txs(mask, false, None, None, None).unwrap(); + assert!(before_txs.is_empty()); + let wallet_data = PathBuf::from(test_dir).join("wallet/wallet_data"); + let before_data = snapshot(&wallet_data); + assert!(before_data.iter().any(|(path, _)| path.starts_with("db"))); + let args = InitTxArgs { + amount: 1, + send_args: Some(InitTxSendArgs { + dest: "unused".into(), + post_tx: false, + fluff: false, + skip_tor: Some(true), + }), + ..Default::default() + }; + + let error = owner.init_send_tx(mask, args).unwrap_err(); + assert!(error.to_string().contains("Configuration file not found")); + + let (_, after_txs) = owner.retrieve_txs(mask, false, None, None, None).unwrap(); + assert!(after_txs.is_empty()); + assert_eq!(snapshot(&wallet_data), before_data); + + drop(owner); + drop(wallet); + drop(wallet_proxy); + clean_output_dir(test_dir); +} + +#[test] +fn directory_change() { + let test_dir = "test_output/config_api_directory"; + setup(test_dir); + let mut wallet_proxy = create_wallet_proxy(test_dir); + create_wallet_and_add!( + client, + wallet, + mask, + test_dir, + "wallet", + None, + &mut wallet_proxy, + false + ); + let old_dir = PathBuf::from(test_dir).join("wallet"); + let old_config = old_dir.join("grin-wallet.toml"); + let new_dir = PathBuf::from(test_dir).join("other"); + let owner = api::Owner::new(wallet.clone(), None, old_config.clone()); + + let error = owner + .set_top_level_directory(new_dir.to_str().unwrap()) + .unwrap_err(); + assert!(error.to_string().contains("Close the wallet")); + assert_eq!(owner.config_path(), old_config); + assert_eq!( + PathBuf::from(owner.get_top_level_directory().unwrap()), + old_dir + ); + + owner.close_wallet(None).unwrap(); + owner.updater_running.store(true, Ordering::Relaxed); + let error = owner + .set_top_level_directory(new_dir.to_str().unwrap()) + .unwrap_err(); + assert!(error.to_string().contains("Stop the updater")); + owner.updater_running.store(false, Ordering::Relaxed); + + fs::create_dir_all(&new_dir).unwrap(); + let new_config = new_dir.join("grin-wallet.toml"); + let mut config = GlobalWalletConfig::for_chain( + &grin_core::global::ChainTypes::AutomatedTesting, + &new_config, + ); + config + .write_to_file(new_config.to_str().unwrap(), false, None, None) + .unwrap(); + reload_global_config(&new_config).unwrap(); + let expected_port = config.members.wallet.api_listen_port + 1; + config.members.wallet.api_listen_port = expected_port; + config + .write_to_file(new_config.to_str().unwrap(), false, None, None) + .unwrap(); + + owner + .set_top_level_directory(new_dir.to_str().unwrap()) + .unwrap(); + assert_eq!(owner.config_path(), new_config); + assert_eq!( + get_global_config(&owner.config_path()) + .unwrap() + .members + .wallet + .api_listen_port, + expected_port + ); + assert_eq!( + PathBuf::from(owner.get_top_level_directory().unwrap()), + new_dir + ); + + drop(owner); + drop(wallet); + drop(wallet_proxy); + clean_output_dir(test_dir); +} diff --git a/controller/tests/file.rs b/controller/tests/file.rs index c6c4e1a..e313407 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -19,6 +19,7 @@ extern crate grin_wallet_impls as impls; use grin_core as core; use grin_wallet_libwallet as libwallet; +use std::path::PathBuf; use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlate, SlateGetter as _, SlatePutter as _}; @@ -75,18 +76,28 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // add some accounts - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account1")?; + api.create_account_path(m, "account2")?; + Ok(()) + }, + )?; // Get some mining done { @@ -111,27 +122,32 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), }; // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + // output tx file + PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; // Get some mining done { @@ -142,43 +158,63 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), let mut slate = PathToSlate((&send_file).into()).get_tx()?.0; // wallet 2 receives file, completes, sends file back - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - slate = api.receive_tx(&slate, None, None)?; - PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + slate = api.receive_tx(&slate, None, None)?; + PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; + Ok(()) + }, + )?; // wallet 1 finalises and posts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let mut slate = PathToSlate(receive_file.into()).get_tx()?.0; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let mut slate = PathToSlate(receive_file.into()).get_tx()?.0; + slate = api.finalize_tx(m, &slate)?; + // Output final file for reference + PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + }, + )?; // Check total in 'wallet 2' account - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); + Ok(()) + }, + )?; // Now other types of exchange, for reference // Invoice transaction @@ -197,43 +233,63 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), let mut slate = Slate::blank(2, true); - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let args = IssueInvoiceTxArgs { - amount: 1000000000, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let args = IssueInvoiceTxArgs { + amount: 1000000000, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = PathToSlate((&send_file).into()).get_tx()?.0; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - })?; - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - // Wallet 2 receives the invoice transaction - slate = PathToSlate((&receive_file).into()).get_tx()?.0; - slate = api.finalize_tx(&slate, false)?; - PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - })?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = PathToSlate((&send_file).into()).get_tx()?.0; + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; + Ok(()) + }, + )?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + // Wallet 2 receives the invoice transaction + slate = PathToSlate((&receive_file).into()).get_tx()?.0; + slate = api.finalize_tx(&slate, false)?; + PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; + Ok(()) + }, + )?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; // Standard, with payment proof let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); @@ -251,46 +307,66 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), }; let mut slate = Slate::blank(2, true); let mut address = None; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - address = Some(api.get_slatepack_address(m, 0)?); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + address = Some(api.get_slatepack_address(m, 0)?); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - payment_proof_recipient_address: address.clone(), - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: address.clone(), + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - slate = PathToSlate((&send_file).into()).get_tx()?.0; - slate = api.receive_tx(&slate, None, None)?; - PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + slate = PathToSlate((&send_file).into()).get_tx()?.0; + slate = api.receive_tx(&slate, None, None)?; + PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; + Ok(()) + }, + )?; // wallet 1 finalises and posts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - slate = PathToSlate(receive_file.into()).get_tx()?.0; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + slate = PathToSlate(receive_file.into()).get_tx()?.0; + slate = api.finalize_tx(m, &slate)?; + // Output final file for reference + PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 2fe7d64..7acd8bf 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -19,6 +19,7 @@ extern crate grin_wallet_impls as impls; use grin_core as core; use grin_wallet_libwallet as libwallet; +use std::path::PathBuf; use impls::test_framework::{self, LocalWalletClient}; use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, SlateState}; @@ -71,11 +72,16 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // Get some mining done { @@ -87,122 +93,167 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, _bh as usize, false); // Sanity check wallet 1 contents - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, _bh); - assert_eq!(wallet1_info.total, _bh * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, _bh); + assert_eq!(wallet1_info.total, _bh * reward); + Ok(()) + }, + )?; let mut slate = Slate::blank(2, true); - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + // Wallet 2 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice1); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice2); // wallet 2 finalizes and posts - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - // Wallet 2 receives the invoice transaction - slate = api.finalize_tx(&slate, false)?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + // Wallet 2 receives the invoice transaction + slate = api.finalize_tx(&slate, false)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice3); // wallet 1 posts so wallet 2 doesn't get the mined amount - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; _bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); _bh += 3; // Check transaction log for wallet 2 - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (_, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert!(txs.len() == 1); - println!( - "last confirmed height: {}, bh: {}", - wallet2_info.last_confirmed_height, _bh - ); - assert!(refreshed); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + assert!(txs.len() == 1); + println!( + "last confirmed height: {}, bh: {}", + wallet2_info.last_confirmed_height, _bh + ); + assert!(refreshed); + Ok(()) + }, + )?; // Check transaction log for wallet 1, ensure only 1 entry // exists - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert_eq!(txs.len() as u64, _bh + 1); - println!( - "Wallet 1: last confirmed height: {}, bh: {}", - wallet1_info.last_confirmed_height, _bh - ); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + assert_eq!(txs.len() as u64, _bh + 1); + println!( + "Wallet 1: last confirmed height: {}, bh: {}", + wallet1_info.last_confirmed_height, _bh + ); + Ok(()) + }, + )?; // Test self-sending - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Wallet 1 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - println!("Self invoice slate init: {}", slate); - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // Wallet 1 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + println!("Self invoice slate init: {}", slate); + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; println!("Self invoice slate after process: {}", slate); // wallet 1 finalizes and posts - wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { - // Wallet 2 receives the invoice transaction - slate = api.finalize_tx(&slate, false)?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| { + // Wallet 2 receives the invoice transaction + slate = api.finalize_tx(&slate, false)?; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); //bh += 3; @@ -210,82 +261,107 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // As above, but use owner API to finalize let mut slate = Slate::blank(2, true); - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + // Wallet 2 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice1); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice2); // wallet 2 finalizes via owner API - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Wallet 2 receives the invoice transaction - slate = api.finalize_tx(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + // Wallet 2 receives the invoice transaction + slate = api.finalize_tx(m, &slate)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice3); // test that payee can only cancel once let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); _bh += 3; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + // Wallet 2 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice1); let orig_slate = slate.clone(); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args.clone())?; - api.tx_lock_outputs(m, &slate)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api.process_invoice_tx(m, &slate, args.clone())?; + api.tx_lock_outputs(m, &slate)?; - // Wallet 1 cancels the invoice transaction - api.cancel_tx(m, None, Some(slate.id))?; + // Wallet 1 cancels the invoice transaction + api.cancel_tx(m, None, Some(slate.id))?; - // Wallet 1 attempts to repay again - let res = api.process_invoice_tx(m, &orig_slate, args); - assert!(res.is_err()); + // Wallet 1 attempts to repay again + let res = api.process_invoice_tx(m, &orig_slate, args); + assert!(res.is_err()); - Ok(()) - })?; + Ok(()) + }, + )?; assert_eq!(slate.state, SlateState::Invoice2); // let logging finish diff --git a/controller/tests/late_lock.rs b/controller/tests/late_lock.rs index b80cc8c..2d1ec29 100644 --- a/controller/tests/late_lock.rs +++ b/controller/tests/late_lock.rs @@ -20,6 +20,7 @@ extern crate grin_wallet_libwallet as libwallet; use self::libwallet::{InitTxArgs, Slate}; use impls::test_framework::{self, LocalWalletClient}; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -68,16 +69,26 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { }); // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + Ok(()) + }, + )?; // add some accounts - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - api.create_account_path(m, "account1")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account1")?; + Ok(()) + }, + )?; // Get some mining done { @@ -94,52 +105,67 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(2, false); let amount = 100_000_000_000; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - late_lock: Some(true), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - println!("S1 SLATE: {}", slate_i); - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - println!("S2 SLATE: {}", slate); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + late_lock: Some(true), + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + println!("S1 SLATE: {}", slate_i); + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + println!("S2 SLATE: {}", slate); - // Note we don't call `tx_lock_outputs` on the sender side here, - // as the outputs will only be locked during finalization + // Note we don't call `tx_lock_outputs` on the sender side here, + // as the outputs will only be locked during finalization - slate = sender_api.finalize_tx(m, &slate)?; - println!("S3 SLATE: {}", slate); + slate = sender_api.finalize_tx(m, &slate)?; + println!("S3 SLATE: {}", slate); - // Now post tx to our node for inclusion in the next block. - sender_api.post_tx(m, &slate, true)?; + // Now post tx to our node for inclusion in the next block. + sender_api.post_tx(m, &slate, true)?; - Ok(()) - })?; + Ok(()) + }, + )?; test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false)?; // update/test contents of both accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - // Reward from mining 11 blocks, minus the amount sent. - // Note: We mined the block containing the tx, so fees are effectively refunded. - assert_eq!(560_000_000_000, wallet_info.amount_currently_spendable); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + // Reward from mining 11 blocks, minus the amount sent. + // Note: We mined the block containing the tx, so fees are effectively refunded. + assert_eq!(560_000_000_000, wallet_info.amount_currently_spendable); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(amount, wallet_info.amount_currently_spendable); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(amount, wallet_info.amount_currently_spendable); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/mwixnet.rs b/controller/tests/mwixnet.rs index 0db537d..60e4bd8 100644 --- a/controller/tests/mwixnet.rs +++ b/controller/tests/mwixnet.rs @@ -20,6 +20,7 @@ extern crate grin_wallet_impls as impls; use grin_core as core; use grin_util as util; use grin_util::secp::key::SecretKey; +use std::path::PathBuf; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; @@ -64,11 +65,16 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // Get some mining done { @@ -80,98 +86,123 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - api.tx_lock_outputs(m, &slate)?; - // Send directly to self - wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { - slate = api.receive_tx(&slate, Some("listener"), None)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.init_send_tx(m, args)?; + api.tx_lock_outputs(m, &slate)?; + // Send directly to self + wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| { + slate = api.receive_tx(&slate, Some("listener"), None)?; + Ok(()) + }, + )?; + slate = api.finalize_tx(m, &slate)?; + api.post_tx(m, &slate, false)?; // mines a block + bh += 1; Ok(()) - })?; - slate = api.finalize_tx(m, &slate)?; - api.post_tx(m, &slate, false)?; // mines a block - bh += 1; - Ok(()) - })?; + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + }, + )?; // Check total in 'listener' account { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, 2 * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, 2 * reward); + Ok(()) + }, + )?; // Recipient wallet creates a mwixnet request from the last output - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let secp_locked = util::static_secp_instance(); - let secp = secp_locked.lock(); - let server_pubkey_str_1 = - "97444ae673bb92c713c1a2f7b8882ffbfc1c67401a280a775dce1a8651584332"; - let server_pubkey_str_2 = - "0c9414341f2140ed34a5a12a6479bf5a6404820d001ab81d9d3e8cc38f049b4e"; - let server_pubkey_str_3 = - "b58ece97d60e71bb7e53218400b0d67bfe6a3cb7d3b4a67a44f8fb7c525cbca5"; - let server_key_1 = - SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_1).unwrap()) - .unwrap(); - let server_key_2 = - SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_2).unwrap()) - .unwrap(); - let server_key_3 = - SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_3).unwrap()) - .unwrap(); - let params = MixnetReqCreationParams { - server_keys: vec![server_key_1, server_key_2, server_key_3], - fee_per_hop: 50_000_000, - }; - let outputs = api.retrieve_outputs(mask1, false, false, None)?; - // get last output - let last_output = outputs.1[outputs.1.len() - 1].clone(); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let secp_locked = util::static_secp_instance(); + let secp = secp_locked.lock(); + let server_pubkey_str_1 = + "97444ae673bb92c713c1a2f7b8882ffbfc1c67401a280a775dce1a8651584332"; + let server_pubkey_str_2 = + "0c9414341f2140ed34a5a12a6479bf5a6404820d001ab81d9d3e8cc38f049b4e"; + let server_pubkey_str_3 = + "b58ece97d60e71bb7e53218400b0d67bfe6a3cb7d3b4a67a44f8fb7c525cbca5"; + let server_key_1 = + SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_1).unwrap()) + .unwrap(); + let server_key_2 = + SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_2).unwrap()) + .unwrap(); + let server_key_3 = + SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_3).unwrap()) + .unwrap(); + let params = MixnetReqCreationParams { + server_keys: vec![server_key_1, server_key_2, server_key_3], + fee_per_hop: 50_000_000, + }; + let outputs = api.retrieve_outputs(mask1, false, false, None)?; + // get last output + let last_output = outputs.1[outputs.1.len() - 1].clone(); - let mwixnet_req = api.create_mwixnet_req(m, ¶ms, &last_output.commit, true)?; + let mwixnet_req = api.create_mwixnet_req(m, ¶ms, &last_output.commit, true)?; - println!("MWIXNET REQ: {:?}", mwixnet_req); + println!("MWIXNET REQ: {:?}", mwixnet_req); - // check output we created comsig for is indeed locked - let outputs = api.retrieve_outputs(mask1, false, false, None)?; - // get last output - let last_output = outputs.1[outputs.1.len() - 1].clone(); - assert!(last_output.output.status == libwallet::OutputStatus::Locked); + // check output we created comsig for is indeed locked + let outputs = api.retrieve_outputs(mask1, false, false, None)?; + // get last output + let last_output = outputs.1[outputs.1.len() - 1].clone(); + assert!(last_output.output.status == libwallet::OutputStatus::Locked); - Ok(()) - })?; + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/no_change.rs b/controller/tests/no_change.rs index cefba91..aa1a56e 100644 --- a/controller/tests/no_change.rs +++ b/controller/tests/no_change.rs @@ -18,6 +18,7 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; use grin_core as core; +use std::path::PathBuf; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; @@ -78,122 +79,167 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // send a single block's worth of transactions with minimal strategy let mut slate = Slate::blank(2, false); let mut stored_excess = None; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: reward - fee, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - slate = api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate)?; - api.tx_lock_outputs(m, &slate)?; - slate = api.finalize_tx(m, &slate)?; - println!("Posted Slate: {:?}", slate); - stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let args = InitTxArgs { + src_acct_name: None, + amount: reward - fee, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + slate = api.init_send_tx(m, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate)?; + api.tx_lock_outputs(m, &slate)?; + slate = api.finalize_tx(m, &slate)?; + println!("Posted Slate: {:?}", slate); + stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; // ensure stored excess is correct in both wallets // Wallet 1 calculated the excess with the full slate // Wallet 2 only had the excess provided by // wallet 1 // Refresh and check transaction log for wallet 1 - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - let tx = txs[0].clone(); - println!("SIMPLE SEND - SENDING WALLET"); - println!("{:?}", tx); - println!(); - assert!(tx.confirmed); - assert_eq!(stored_excess, tx.kernel_excess); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; + assert!(refreshed); + let tx = txs[0].clone(); + println!("SIMPLE SEND - SENDING WALLET"); + println!("{:?}", tx); + println!(); + assert!(tx.confirmed); + assert_eq!(stored_excess, tx.kernel_excess); + Ok(()) + }, + )?; // Refresh and check transaction log for wallet 2 - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - let tx = txs[0].clone(); - println!("SIMPLE SEND - RECEIVING WALLET"); - println!("{:?}", tx); - println!(); - assert!(tx.confirmed); - assert_eq!(stored_excess, tx.kernel_excess); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; + assert!(refreshed); + let tx = txs[0].clone(); + println!("SIMPLE SEND - RECEIVING WALLET"); + println!("{:?}", tx); + println!(); + assert!(tx.confirmed); + assert_eq!(stored_excess, tx.kernel_excess); + Ok(()) + }, + )?; // ensure invoice TX works as well with no change - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward - fee, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + // Wallet 2 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward - fee, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; // wallet 2 finalizes and posts - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - // Wallet 2 receives the invoice transaction - slate = api.finalize_tx(&slate, false)?; - Ok(()) - })?; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask1, None, |api, m| { - println!("Invoice Posted TX: {}", slate); - stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + // Wallet 2 receives the invoice transaction + slate = api.finalize_tx(&slate, false)?; + Ok(()) + }, + )?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + println!("Invoice Posted TX: {}", slate); + stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; // check wallet 2's version - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - for tx in txs { - stored_excess = tx.kernel_excess; - println!("Wallet 2: {:?}", tx); - println!(); - assert!(tx.confirmed); - assert_eq!(stored_excess, tx.kernel_excess); - } - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; + assert!(refreshed); + for tx in txs { + stored_excess = tx.kernel_excess; + println!("Wallet 2: {:?}", tx); + println!(); + assert!(tx.confirmed); + assert_eq!(stored_excess, tx.kernel_excess); + } + Ok(()) + }, + )?; // Refresh and check transaction log for wallet 1 - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - for tx in txs { - println!("Wallet 1: {:?}", tx); - println!(); - assert_eq!(stored_excess, tx.kernel_excess); - assert!(tx.confirmed); - } - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; + assert!(refreshed); + for tx in txs { + println!("Wallet 1: {:?}", tx); + println!(); + assert_eq!(stored_excess, tx.kernel_excess); + assert!(tx.confirmed); + } + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/payment_proofs.rs b/controller/tests/payment_proofs.rs index edb416f..651e486 100644 --- a/controller/tests/payment_proofs.rs +++ b/controller/tests/payment_proofs.rs @@ -21,6 +21,7 @@ extern crate grin_wallet_util; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; use libwallet::{InitTxArgs, Slate}; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -77,83 +78,98 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); let mut address = None; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - address = Some(api.get_slatepack_address(m, 0)?); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + address = Some(api.get_slatepack_address(m, 0)?); + Ok(()) + }, + )?; println!("Public address is: {:?}", address); let amount = 60_000_000_000; let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - payment_proof_recipient_address: address.clone(), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: address.clone(), + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; - assert_eq!( - slate_i.payment_proof.as_ref().unwrap().receiver_address, - address.as_ref().unwrap().pub_key, - ); - println!( - "Sender addr: {:?}", - slate_i.payment_proof.as_ref().unwrap().sender_address - ); + assert_eq!( + slate_i.payment_proof.as_ref().unwrap().receiver_address, + address.as_ref().unwrap().pub_key, + ); + println!( + "Sender addr: {:?}", + slate_i.payment_proof.as_ref().unwrap().sender_address + ); - // Check we are creating a tx with kernel features 0 - // We will check this produces a Plain kernel later. - assert_eq!(0, slate.kernel_features); + // Check we are creating a tx with kernel features 0 + // We will check this produces a Plain kernel later. + assert_eq!(0, slate.kernel_features); - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.tx_lock_outputs(m, &slate)?; - // Ensure what's stored in TX log for payment proof is correct - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(txs[0].payment_proof.is_some()); - let pp = txs[0].clone().payment_proof.unwrap(); - assert_eq!( - pp.receiver_address, - slate_i.payment_proof.as_ref().unwrap().receiver_address - ); - assert!(pp.receiver_signature.is_some()); - assert_eq!(pp.sender_address_path, 0); - assert_eq!(pp.sender_signature, None); + // Ensure what's stored in TX log for payment proof is correct + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; + assert!(txs[0].payment_proof.is_some()); + let pp = txs[0].clone().payment_proof.unwrap(); + assert_eq!( + pp.receiver_address, + slate_i.payment_proof.as_ref().unwrap().receiver_address + ); + assert!(pp.receiver_signature.is_some()); + assert_eq!(pp.sender_address_path, 0); + assert_eq!(pp.sender_signature, None); - // check we should get an error at this point since proof is not complete - let pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id)); - assert!(pp.is_err()); + // check we should get an error at this point since proof is not complete + let pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id)); + assert!(pp.is_err()); - slate = sender_api.finalize_tx(m, &slate)?; - sender_api.post_tx(m, &slate, true)?; - Ok(()) - })?; + slate = sender_api.finalize_tx(m, &slate)?; + sender_api.post_tx(m, &slate, true)?; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // Check payment proof here - let mut pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id))?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // Check payment proof here + let mut pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id))?; - println!("Payment proof: {:?}", pp); + println!("Payment proof: {:?}", pp); - // verify, should be good - let res = sender_api.verify_payment_proof(m, &pp)?; - assert_eq!(res, (true, false)); + // verify, should be good + let res = sender_api.verify_payment_proof(m, &pp)?; + assert_eq!(res, (true, false)); - // Modify values, should not be good - pp.amount = 20; - let res = sender_api.verify_payment_proof(m, &pp); - assert!(res.is_err()); - Ok(()) - })?; + // Modify values, should not be good + pp.amount = 20; + let res = sender_api.verify_payment_proof(m, &pp); + assert!(res.is_err()); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 283164c..0718364 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -19,6 +19,7 @@ extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; use grin_core as core; +use std::path::PathBuf; use self::libwallet::{InitTxArgs, Slate}; use impls::test_framework::{self, LocalWalletClient}; @@ -74,18 +75,28 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // add some accounts - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account1")?; + api.create_account_path(m, "account2")?; + Ok(()) + }, + )?; // Get some mining done { @@ -102,26 +113,31 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let mut slate = Slate::blank(2, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - PathToSlate((&send_file).into()).put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + PathToSlate((&send_file).into()).put_tx(&slate, false)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; @@ -132,12 +148,17 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> w.set_parent_key_id_by_name("listener")?; } - wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { - slate = PathToSlate((&send_file).into()).get_tx()?.0; - slate = api.receive_tx(&slate, None, None)?; - PathToSlate((&receive_file).into()).put_tx(&slate, false)?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| { + slate = PathToSlate((&send_file).into()).get_tx()?.0; + slate = api.receive_tx(&slate, None, None)?; + PathToSlate((&receive_file).into()).put_tx(&slate, false)?; + Ok(()) + }, + )?; // wallet 1 receives file to different account, completes { @@ -146,47 +167,67 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> } // wallet 1 finalize - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - slate = PathToSlate((&receive_file).into()).get_tx()?.0; - slate = api.finalize_tx(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + slate = PathToSlate((&receive_file).into()).get_tx()?.0; + slate = api.finalize_tx(m, &slate)?; + Ok(()) + }, + )?; // Now repost from cached - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - println!("TXS[0]: {:?}", txs[0]); - let stored_tx = api.get_stored_tx(m, None, Some(&txs[0].tx_slate_id.unwrap()))?; - println!("Stored tx: {:?}", stored_tx); - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; + println!("TXS[0]: {:?}", txs[0]); + let stored_tx = api.get_stored_tx(m, None, Some(&txs[0].tx_slate_id.unwrap()))?; + println!("Stored tx: {:?}", stored_tx); + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // update/test contents of both accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + }, + )?; { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); + Ok(()) + }, + )?; // as above, but syncronously { @@ -201,55 +242,75 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let mut slate = Slate::blank(2, false); let amount = 60_000_000_000; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.tx_lock_outputs(m, &slate)?; + slate = sender_api.finalize_tx(m, &slate)?; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Now repost from cached - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let stored_tx_slate = api.get_stored_tx(m, Some(txs[0].id), None)?.unwrap(); - api.post_tx(m, &stored_tx_slate, false)?; - bh += 1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; + let stored_tx_slate = api.get_stored_tx(m, Some(txs[0].id), None)?.unwrap(); + api.post_tx(m, &stored_tx_slate, false)?; + bh += 1; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // // update/test contents of both accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 4); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 4); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * amount); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * amount); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/revert.rs b/controller/tests/revert.rs index 5286c85..ef5a887 100644 --- a/controller/tests/revert.rs +++ b/controller/tests/revert.rs @@ -31,6 +31,7 @@ use grin_wallet_libwallet as libwallet; use grin_wallet_libwallet::api_impl::types::InitTxArgs; use grin_wallet_libwallet::WalletInst; use log::error; +use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; @@ -104,13 +105,13 @@ fn revert( } }); - owner(Some(wallet1.clone()), mask1, None, |api, m| { + owner(wallet1.clone(), mask1, PathBuf::from(test_dir), |api, m| { api.create_account_path(m, "a")?; api.set_active_account(m, "a")?; Ok(()) })?; - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { api.create_account_path(m, "b")?; api.set_active_account(m, "b")?; Ok(()) @@ -125,7 +126,7 @@ fn revert( award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false)?; // Sanity check contents - owner(Some(wallet1.clone()), mask1, None, |api, m| { + owner(wallet1.clone(), mask1, PathBuf::from(test_dir), |api, m| { let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); assert_eq!(info.last_confirmed_height, bh); @@ -140,7 +141,7 @@ fn revert( Ok(()) })?; - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); assert_eq!(info.last_confirmed_height, bh); @@ -155,7 +156,7 @@ fn revert( // Send some funds let mut tx = None; - owner(Some(wallet1.clone()), mask1, None, |api, m| { + owner(wallet1.clone(), mask1, PathBuf::from(test_dir), |api, m| { // send to send let args = InitTxArgs { src_acct_name: None, @@ -180,7 +181,7 @@ fn revert( let tx = tx.expect("tx from slate"); // Check funds have been received - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); assert_eq!(info.last_confirmed_height, bh); @@ -222,7 +223,7 @@ fn revert( let bh = bh + 1; // Check funds have been confirmed - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); assert_eq!(info.last_confirmed_height, bh); @@ -257,7 +258,7 @@ fn revert( let bh = bh + 1; // Check funds have been reverted - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { api.scan(m, None, false)?; let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); @@ -292,7 +293,7 @@ fn revert_reconfirm_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let bh = bh + 1; // Check funds have been confirmed again - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); assert_eq!(info.last_confirmed_height, bh); @@ -320,7 +321,7 @@ fn revert_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let mask2 = mask2_i.as_ref(); // Cancelling tx - owner(Some(wallet2.clone()), mask2, None, |api, m| { + owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { // Sanity check let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index 5fc6058..6cf572e 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -18,6 +18,7 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; use grin_core as core; +use std::path::PathBuf; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; @@ -62,11 +63,16 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // Get some mining done { @@ -78,58 +84,78 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - api.tx_lock_outputs(m, &slate)?; - // Send directly to self - wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { - slate = api.receive_tx(&slate, Some("listener"), None)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.init_send_tx(m, args)?; + api.tx_lock_outputs(m, &slate)?; + // Send directly to self + wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| { + slate = api.receive_tx(&slate, Some("listener"), None)?; + Ok(()) + }, + )?; + slate = api.finalize_tx(m, &slate)?; + api.post_tx(m, &slate, false)?; // mines a block + bh += 1; Ok(()) - })?; - slate = api.finalize_tx(m, &slate)?; - api.post_tx(m, &slate, false)?; // mines a block - bh += 1; - Ok(()) - })?; + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + }, + )?; // Check total in 'listener' account { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, 2 * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, 2 * reward); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/slatepack.rs b/controller/tests/slatepack.rs index 4626892..cf2a7e1 100644 --- a/controller/tests/slatepack.rs +++ b/controller/tests/slatepack.rs @@ -19,6 +19,7 @@ extern crate grin_wallet_impls as impls; use grin_core as core; use grin_wallet_libwallet as libwallet; +use std::path::PathBuf; use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlatepack, SlatePutter as _}; @@ -124,18 +125,28 @@ fn slatepack_exchange_test_impl( let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // add some accounts - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account1")?; + api.create_account_path(m, "account2")?; + Ok(()) + }, + )?; // Get some mining done { @@ -150,12 +161,17 @@ fn slatepack_exchange_test_impl( true => { let mut rec_address = SlatepackAddress::random(); let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - sec_key = api.get_slatepack_secret_key(m, 0)?; - let pub_key = edDalekPublicKey::from(&sec_key); - rec_address = SlatepackAddress::new(&pub_key); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + sec_key = api.get_slatepack_secret_key(m, 0)?; + let pub_key = edDalekPublicKey::from(&sec_key); + rec_address = SlatepackAddress::new(&pub_key); + Ok(()) + }, + )?; ( vec![rec_address.clone()], Some(sec_key), @@ -169,12 +185,17 @@ fn slatepack_exchange_test_impl( true => { let mut rec_address = SlatepackAddress::random(); let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]); - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - sec_key = api.get_slatepack_secret_key(m, 0)?; - let pub_key = edDalekPublicKey::from(&sec_key); - rec_address = SlatepackAddress::new(&pub_key); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + sec_key = api.get_slatepack_secret_key(m, 0)?; + let pub_key = edDalekPublicKey::from(&sec_key); + rec_address = SlatepackAddress::new(&pub_key); + Ok(()) + }, + )?; ( vec![rec_address.clone()], Some(sec_key), @@ -197,34 +218,39 @@ fn slatepack_exchange_test_impl( ), }; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - output_slatepack( - &slate, - &send_file, - use_armored, - use_bin, - sender_1.clone(), - recipients_2.clone(), - )?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + // output tx file + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_1.clone(), + recipients_2.clone(), + )?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; // Get some mining done { @@ -236,54 +262,75 @@ fn slatepack_exchange_test_impl( slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?; // wallet 2 receives file, completes, sends file back - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - slate = api.receive_tx(&slate, None, None)?; - output_slatepack( - &slate, - &receive_file, - use_armored, - use_bin, - // re-encrypt for sender! - sender_2.clone(), - match slatepack.sender.clone() { - Some(s) => vec![s.clone()], - None => vec![], - }, - )?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + slate = api.receive_tx(&slate, None, None)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + // re-encrypt for sender! + sender_2.clone(), + match slatepack.sender.clone() { + Some(s) => vec![s.clone()], + None => vec![], + }, + )?; + Ok(()) + }, + )?; - // wallet 1 finalises and posts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, mut slate) = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - })?; + // wallet 1 finalizes and posts + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, mut slate) = + slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; + slate = api.finalize_tx(m, &slate)?; + // Output final file for reference + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + }, + )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + }, + )?; // Check total in 'wallet 2' account - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); + Ok(()) + }, + )?; // Now other types of exchange, for reference // Invoice transaction @@ -302,63 +349,83 @@ fn slatepack_exchange_test_impl( let mut slate = Slate::blank(2, true); - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let args = IssueInvoiceTxArgs { - amount: 1000000000, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - output_slatepack( - &slate, - &send_file, - use_armored, - use_bin, - sender_2.clone(), - recipients_1.clone(), - )?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let args = IssueInvoiceTxArgs { + amount: 1000000000, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_2.clone(), + recipients_1.clone(), + )?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let res = slate_from_packed(&send_file, use_armored, (&dec_key_1).as_ref())?; - slatepack = res.0; - slate = res.1; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - output_slatepack( - &slate, - &receive_file, - use_armored, - use_bin, - sender_1.clone(), - match slatepack.sender.clone() { - Some(s) => vec![s.clone()], - None => vec![], - }, - )?; - Ok(()) - })?; - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - // Wallet 2 receives the invoice transaction - let res = slate_from_packed(&receive_file, use_armored, (&dec_key_2).as_ref())?; - slate = res.1; - slate = api.finalize_tx(&slate, false)?; - output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; - Ok(()) - })?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let res = slate_from_packed(&send_file, use_armored, (&dec_key_1).as_ref())?; + slatepack = res.0; + slate = res.1; + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + sender_1.clone(), + match slatepack.sender.clone() { + Some(s) => vec![s.clone()], + None => vec![], + }, + )?; + Ok(()) + }, + )?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + // Wallet 2 receives the invoice transaction + let res = slate_from_packed(&receive_file, use_armored, (&dec_key_2).as_ref())?; + slate = res.1; + slate = api.finalize_tx(&slate, false)?; + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + Ok(()) + }, + )?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; // Standard, with payment proof let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); @@ -377,66 +444,86 @@ fn slatepack_exchange_test_impl( let mut slate = Slate::blank(2, true); let mut address = None; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - address = Some(api.get_slatepack_address(m, 0)?); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + address = Some(api.get_slatepack_address(m, 0)?); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - payment_proof_recipient_address: address.clone(), - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - output_slatepack( - &slate, - &send_file, - use_armored, - use_bin, - sender_1, - recipients_2.clone(), - )?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: address.clone(), + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_1, + recipients_2.clone(), + )?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + }, + )?; - wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - let res = slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?; - let slatepack = res.0; - slate = res.1; - slate = api.receive_tx(&slate, None, None)?; - output_slatepack( - &slate, - &receive_file, - use_armored, - use_bin, - sender_2, - match slatepack.sender { - Some(s) => vec![s.clone()], - None => vec![], - }, - )?; - Ok(()) - })?; + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + let res = slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?; + let slatepack = res.0; + slate = res.1; + slate = api.receive_tx(&slate, None, None)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + sender_2, + match slatepack.sender { + Some(s) => vec![s.clone()], + None => vec![], + }, + )?; + Ok(()) + }, + )?; // wallet 1 finalises and posts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let res = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; - slate = res.1; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let res = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; + slate = res.1; + slate = api.finalize_tx(m, &slate)?; + // Output final file for reference + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); @@ -481,27 +568,32 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // create an encrypted slatepack (just encrypted for self) - let enc_addr = api.get_slatepack_address(m, 0)?; - let slatepack = api.create_slatepack_message(m, &slate, Some(0), vec![enc_addr])?; - println!("{}", slatepack); - let slatepack_raw = api.decode_slatepack_message(m, slatepack.clone(), vec![0])?; - println!("{}", slatepack_raw); - let decoded_slate = api.slate_from_slatepack_message(m, slatepack, vec![0])?; - println!("{}", decoded_slate); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + // create an encrypted slatepack (just encrypted for self) + let enc_addr = api.get_slatepack_address(m, 0)?; + let slatepack = api.create_slatepack_message(m, &slate, Some(0), vec![enc_addr])?; + println!("{}", slatepack); + let slatepack_raw = api.decode_slatepack_message(m, slatepack.clone(), vec![0])?; + println!("{}", slatepack_raw); + let decoded_slate = api.slate_from_slatepack_message(m, slatepack, vec![0])?; + println!("{}", decoded_slate); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 33db4c9..55734dc 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -25,6 +25,7 @@ use self::core::global; use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState}; use impls::test_framework::{self, LocalWalletClient}; use std::convert::TryInto; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -80,320 +81,390 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false); // Check wallet 1 contents are as expected - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 Info Pre-Transaction, after {} blocks: {:?}", - wallet1_info.last_confirmed_height, wallet1_info - ); - assert!(wallet1_refreshed); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - ); - assert_eq!(wallet1_info.amount_immature, cm * reward); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + debug!( + "Wallet 1 Info Pre-Transaction, after {} blocks: {:?}", + wallet1_info.last_confirmed_height, wallet1_info + ); + assert!(wallet1_refreshed); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward + ); + assert_eq!(wallet1_info.amount_immature, cm * reward); + Ok(()) + }, + )?; // assert wallet contents // and a single use api for a send command let amount = 60_000_000_000; let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; - assert_eq!(slate_i.state, SlateState::Standard1); + assert_eq!(slate_i.state, SlateState::Standard1); - // Check we are creating a tx with the expected lock_height of 0. - // We will check this produces a Plain kernel later. - assert_eq!(0, slate.kernel_features); + // Check we are creating a tx with the expected lock_height of 0. + // We will check this produces a Plain kernel later. + assert_eq!(0, slate.kernel_features); - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - assert_eq!(slate.state, SlateState::Standard2); - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - assert_eq!(slate.state, SlateState::Standard3); + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + assert_eq!(slate.state, SlateState::Standard2); + sender_api.tx_lock_outputs(m, &slate)?; + slate = sender_api.finalize_tx(m, &slate)?; + assert_eq!(slate.state, SlateState::Standard3); - // Check we have a single kernel and that it is a Plain kernel (no lock_height). - // fees for 7 inputs, 2 outputs, 1 kernel (weight 52) - assert_eq!(slate.tx_or_err()?.kernels().len(), 1); - assert_eq!( - slate - .tx_or_err()? - .kernels() - .first() - .map(|k| k.features) - .unwrap(), - transaction::KernelFeatures::Plain { - fee: 26_000_000.into() - } - ); + // Check we have a single kernel and that it is a Plain kernel (no lock_height). + // fees for 7 inputs, 2 outputs, 1 kernel (weight 52) + assert_eq!(slate.tx_or_err()?.kernels().len(), 1); + assert_eq!( + slate + .tx_or_err()? + .kernels() + .first() + .map(|k| k.features) + .unwrap(), + transaction::KernelFeatures::Plain { + fee: 26_000_000.into() + } + ); - Ok(()) - })?; + Ok(()) + }, + )?; // Check transaction log for wallet 1 - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let fee = core::libtx::tx_fee( - wallet1_info.last_confirmed_height as usize - cm as usize, - 2, - 1, - ); - // we should have a transaction entry for this slate - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(!tx.confirmed); - assert!(tx.confirmation_ts.is_none()); - assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount); - println!("tx: {:?}", tx); - assert_eq!(Some(fee.try_into().unwrap()), tx.fee); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + let fee = core::libtx::tx_fee( + wallet1_info.last_confirmed_height as usize - cm as usize, + 2, + 1, + ); + // we should have a transaction entry for this slate + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(!tx.confirmed); + assert!(tx.confirmation_ts.is_none()); + assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount); + println!("tx: {:?}", tx); + assert_eq!(Some(fee.try_into().unwrap()), tx.fee); + Ok(()) + }, + )?; // Check transaction log for wallet 2 - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - // we should have a transaction entry for this slate - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(!tx.confirmed); - assert!(tx.confirmation_ts.is_none()); - assert_eq!(amount, tx.amount_credited); - assert_eq!(0, tx.amount_debited); - assert_eq!(None, tx.fee); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + // we should have a transaction entry for this slate + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(!tx.confirmed); + assert!(tx.confirmation_ts.is_none()); + assert_eq!(amount, tx.amount_credited); + assert_eq!(0, tx.amount_debited); + assert_eq!(None, tx.fee); + Ok(()) + }, + )?; // post transaction - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.post_tx(m, &slate, false)?; + Ok(()) + }, + )?; // Check wallet 1 contents are as expected - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 Info Post Transaction, after {} blocks: {:?}", - wallet1_info.last_confirmed_height, wallet1_info - ); - let fee = core::libtx::tx_fee( - wallet1_info.last_confirmed_height as usize - 1 - cm as usize, - 2, - 1, - ); - assert!(wallet1_refreshed); - // wallet 1 received fees, so amount should be the same - assert_eq!( - wallet1_info.total, - amount * wallet1_info.last_confirmed_height - amount - ); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - amount - fee - ); - assert_eq!(wallet1_info.amount_immature, cm * reward + fee); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + debug!( + "Wallet 1 Info Post Transaction, after {} blocks: {:?}", + wallet1_info.last_confirmed_height, wallet1_info + ); + let fee = core::libtx::tx_fee( + wallet1_info.last_confirmed_height as usize - 1 - cm as usize, + 2, + 1, + ); + assert!(wallet1_refreshed); + // wallet 1 received fees, so amount should be the same + assert_eq!( + wallet1_info.total, + amount * wallet1_info.last_confirmed_height - amount + ); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward - amount - fee + ); + assert_eq!(wallet1_info.amount_immature, cm * reward + fee); - // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(tx.confirmed); - assert!(tx.confirmation_ts.is_some()); + // check tx log entry is confirmed + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(tx.confirmed); + assert!(tx.confirmation_ts.is_some()); - Ok(()) - })?; + Ok(()) + }, + )?; // mine a few more blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); // refresh wallets and retrieve info/tests for each wallet after maturity - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!("Wallet 1 Info: {:?}", wallet1_info); - assert!(wallet1_refreshed); - assert_eq!( - wallet1_info.total, - amount * wallet1_info.last_confirmed_height - amount - ); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm - 1) * reward - ); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + debug!("Wallet 1 Info: {:?}", wallet1_info); + assert!(wallet1_refreshed); + assert_eq!( + wallet1_info.total, + amount * wallet1_info.last_confirmed_height - amount + ); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm - 1) * reward + ); + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.amount_currently_spendable, amount); + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, amount); - // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(tx.confirmed); - assert!(tx.confirmation_ts.is_some()); - Ok(()) - })?; + // check tx log entry is confirmed + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(tx.confirmed); + assert!(tx.confirmation_ts.is_some()); + Ok(()) + }, + )?; // Estimate fee and locked amount for a transaction - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - let init_args = InitTxArgs { - src_acct_name: None, - amount: amount * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - estimate_only: Some(true), - ..Default::default() - }; - let est = sender_api.init_send_tx(m, init_args)?; - assert_eq!(est.amount, 600_000_000_000); - // fees for 5 inputs, 2 outputs, 1 kernel (weight 50) - assert_eq!(est.fee_fields.fee(), 25_000_000); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + let init_args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + estimate_only: Some(true), + ..Default::default() + }; + let est = sender_api.init_send_tx(m, init_args)?; + assert_eq!(est.amount, 600_000_000_000); + // fees for 5 inputs, 2 outputs, 1 kernel (weight 50) + assert_eq!(est.fee_fields.fee(), 25_000_000); - let init_args = InitTxArgs { - src_acct_name: None, - amount: amount * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, //select smallest number - estimate_only: Some(true), - ..Default::default() - }; - let est = sender_api.init_send_tx(m, init_args)?; - assert_eq!(est.amount, 180_000_000_000); - // fees for 3 inputs, 2 outputs, 1 kernel (weight 48) - assert_eq!(est.fee_fields.fee(), 24_000_000); + let init_args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, //select smallest number + estimate_only: Some(true), + ..Default::default() + }; + let est = sender_api.init_send_tx(m, init_args)?; + assert_eq!(est.amount, 180_000_000_000); + // fees for 3 inputs, 2 outputs, 1 kernel (weight 48) + assert_eq!(est.fee_fields.fee(), 24_000_000); - Ok(()) - })?; + Ok(()) + }, + )?; // Send another transaction, but don't post to chain immediately and use // the stored transaction instead - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.tx_lock_outputs(m, &slate)?; + slate = sender_api.finalize_tx(m, &slate)?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - let (_, txs) = sender_api.retrieve_txs(m, true, None, None, None)?; - // find the transaction - let tx = txs - .iter() - .find(|t| t.tx_slate_id == Some(slate.id)) - .unwrap(); - let stored_tx_slate = sender_api - .get_stored_tx(m, None, Some(&tx.tx_slate_id.unwrap()))? - .unwrap(); - sender_api.post_tx(m, &stored_tx_slate, false)?; - let (_, wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; - // should be mined now - assert_eq!( - wallet1_info.total, - amount * wallet1_info.last_confirmed_height - amount * 3 - ); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; + assert!(refreshed); + let (_, txs) = sender_api.retrieve_txs(m, true, None, None, None)?; + // find the transaction + let tx = txs + .iter() + .find(|t| t.tx_slate_id == Some(slate.id)) + .unwrap(); + let stored_tx_slate = sender_api + .get_stored_tx(m, None, Some(&tx.tx_slate_id.unwrap()))? + .unwrap(); + sender_api.post_tx(m, &stored_tx_slate, false)?; + let (_, wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; + // should be mined now + assert_eq!( + wallet1_info.total, + amount * wallet1_info.last_confirmed_height - amount * 3 + ); + Ok(()) + }, + )?; // mine a few more blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); // check wallet2 has stored transaction - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.amount_currently_spendable, amount * 3); + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, amount * 3); - // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(tx.confirmed); - assert!(tx.confirmation_ts.is_some()); - Ok(()) - })?; + // check tx log entry is confirmed + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(tx.confirmed); + assert!(tx.confirmation_ts.is_some()); + Ok(()) + }, + )?; // try to send a transaction with amount inclusive of fees, but amount too // small to cover fees. Should fail. - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: 1, - amount_includes_fee: Some(true), - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let res = sender_api.init_send_tx(m, args); - assert!(res.is_err()); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: 1, + amount_includes_fee: Some(true), + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let res = sender_api.init_send_tx(m, args); + assert!(res.is_err()); + Ok(()) + }, + )?; // try to build a transaction with amount inclusive of fees. Confirm that tx // amount + fee is equal to the originally specified amount let amount = 60_000_000_000; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - amount_includes_fee: Some(true), - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - assert_eq!(slate_i.state, SlateState::Standard1); - let total_spend: u64 = slate_i.amount + slate_i.fee_fields.fee(); - assert_eq!(amount, total_spend); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + amount_includes_fee: Some(true), + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + assert_eq!(slate_i.state, SlateState::Standard1); + let total_spend: u64 = slate_i.amount + slate_i.fee_fields.fee(); + assert_eq!(amount, total_spend); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); @@ -447,132 +518,158 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { let amount = 30_000_000_000; let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - })?; + let slate_i = sender_api.init_send_tx(m, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.tx_lock_outputs(m, &slate)?; + slate = sender_api.finalize_tx(m, &slate)?; + Ok(()) + }, + )?; // Check transaction log for wallet 1 - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - println!( - "last confirmed height: {}", - wallet1_info.last_confirmed_height - ); - assert!(refreshed); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - // we should have a transaction entry for this slate - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let mut locked_count = 0; - let mut unconfirmed_count = 0; - // get the tx entry, check outputs are as expected - let (_, output_mappings) = api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?; - for m in output_mappings.clone() { - if m.output.status == OutputStatus::Locked { - locked_count = locked_count + 1; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + println!( + "last confirmed height: {}", + wallet1_info.last_confirmed_height + ); + assert!(refreshed); + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + // we should have a transaction entry for this slate + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let mut locked_count = 0; + let mut unconfirmed_count = 0; + // get the tx entry, check outputs are as expected + let (_, output_mappings) = + api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?; + for m in output_mappings.clone() { + if m.output.status == OutputStatus::Locked { + locked_count = locked_count + 1; + } + if m.output.status == OutputStatus::Unconfirmed { + unconfirmed_count = unconfirmed_count + 1; + } } - if m.output.status == OutputStatus::Unconfirmed { - unconfirmed_count = unconfirmed_count + 1; - } - } - assert_eq!(output_mappings.len(), 3); - assert_eq!(locked_count, 2); - assert_eq!(unconfirmed_count, 1); + assert_eq!(output_mappings.len(), 3); + assert_eq!(locked_count, 2); + assert_eq!(unconfirmed_count, 1); - Ok(()) - })?; + Ok(()) + }, + )?; // Check transaction log for wallet 2 - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let mut unconfirmed_count = 0; - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - // get the tx entry, check outputs are as expected - let (_, outputs) = api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?; - for m in outputs.clone() { - if m.output.status == OutputStatus::Unconfirmed { - unconfirmed_count = unconfirmed_count + 1; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; + assert!(refreshed); + let mut unconfirmed_count = 0; + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + // get the tx entry, check outputs are as expected + let (_, outputs) = api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?; + for m in outputs.clone() { + if m.output.status == OutputStatus::Unconfirmed { + unconfirmed_count = unconfirmed_count + 1; + } } - } - assert_eq!(outputs.len(), 1); - assert_eq!(unconfirmed_count, 1); - let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - assert_eq!(wallet2_info.amount_awaiting_finalization, amount); - Ok(()) - })?; + assert_eq!(outputs.len(), 1); + assert_eq!(unconfirmed_count, 1); + let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, 0,); + assert_eq!(wallet2_info.amount_awaiting_finalization, amount); + Ok(()) + }, + )?; // wallet 1 is bold and doesn't ever post the transaction // mine a few more blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); // Wallet 1 decides to roll back instead - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // can't roll back coinbase - let res = api.cancel_tx(m, Some(1), None); - assert!(res.is_err()); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let tx = txs - .iter() - .find(|t| t.tx_slate_id == Some(slate.id)) - .unwrap(); - api.cancel_tx(m, Some(tx.id), None)?; - let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - println!( - "last confirmed height: {}", - wallet1_info.last_confirmed_height - ); - // check all eligible inputs should be now be spendable - println!("cm: {}", cm); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - ); - // can't roll back again - let res = api.cancel_tx(m, Some(tx.id), None); - assert!(res.is_err()); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + // can't roll back coinbase + let res = api.cancel_tx(m, Some(1), None); + assert!(res.is_err()); + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + let tx = txs + .iter() + .find(|t| t.tx_slate_id == Some(slate.id)) + .unwrap(); + api.cancel_tx(m, Some(tx.id), None)?; + let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(refreshed); + println!( + "last confirmed height: {}", + wallet1_info.last_confirmed_height + ); + // check all eligible inputs should be now be spendable + println!("cm: {}", cm); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward + ); + // can't roll back again + let res = api.cancel_tx(m, Some(tx.id), None); + assert!(res.is_err()); - Ok(()) - })?; + Ok(()) + }, + )?; // Wallet 2 rolls back - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let tx = txs - .iter() - .find(|t| t.tx_slate_id == Some(slate.id)) - .unwrap(); - api.cancel_tx(m, Some(tx.id), None)?; - let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - // check all eligible inputs should be now be spendable - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - assert_eq!(wallet2_info.total, 0,); - // can't roll back again - let res = api.cancel_tx(m, Some(tx.id), None); - assert!(res.is_err()); + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; + let tx = txs + .iter() + .find(|t| t.tx_slate_id == Some(slate.id)) + .unwrap(); + api.cancel_tx(m, Some(tx.id), None)?; + let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(refreshed); + // check all eligible inputs should be now be spendable + assert_eq!(wallet2_info.amount_currently_spendable, 0,); + assert_eq!(wallet2_info.total, 0,); + // can't roll back again + let res = api.cancel_tx(m, Some(tx.id), None); + assert!(res.is_err()); - Ok(()) - })?; + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/ttl_cutoff.rs b/controller/tests/ttl_cutoff.rs index ec4340b..faa3990 100644 --- a/controller/tests/ttl_cutoff.rs +++ b/controller/tests/ttl_cutoff.rs @@ -21,6 +21,7 @@ extern crate grin_wallet_util; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; use libwallet::{InitTxArgs, Slate, TxLogEntryType}; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -78,95 +79,125 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let amount = 60_000_000_000; let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ttl_blocks: Some(2), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ttl_blocks: Some(2), + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.tx_lock_outputs(m, &slate)?; - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); - assert_eq!(tx.ttl_cutoff_height, Some(12)); - Ok(()) - })?; + assert_eq!(tx.ttl_cutoff_height, Some(12)); + Ok(()) + }, + )?; // Now mine past the block, and check again. Transaction should be gone. let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); - assert_eq!(tx.ttl_cutoff_height, Some(12)); - assert!(tx.tx_type == TxLogEntryType::TxSentCancelled); - Ok(()) - })?; + assert_eq!(tx.ttl_cutoff_height, Some(12)); + assert_eq!(tx.tx_type, TxLogEntryType::TxSentCancelled); + Ok(()) + }, + )?; // Should also be gone in wallet 2, and output gone - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| { - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); - let outputs = sender_api.retrieve_outputs(m, false, true, None)?.1; - assert_eq!(outputs.len(), 0); + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |sender_api, m| { + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); + let outputs = sender_api.retrieve_outputs(m, false, true, None)?.1; + assert_eq!(outputs.len(), 0); - assert_eq!(tx.ttl_cutoff_height, Some(12)); - assert!(tx.tx_type == TxLogEntryType::TxReceivedCancelled); - Ok(()) - })?; + assert_eq!(tx.ttl_cutoff_height, Some(12)); + assert_eq!(tx.tx_type, TxLogEntryType::TxReceivedCancelled); + Ok(()) + }, + )?; // try again, except try and send off the transaction for completion beyond the expiry let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ttl_blocks: Some(2), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - sender_api.tx_lock_outputs(m, &slate_i)?; - slate = slate_i; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ttl_blocks: Some(2), + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + sender_api.tx_lock_outputs(m, &slate_i)?; + slate = slate_i; - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); - assert_eq!(tx.ttl_cutoff_height, Some(14)); - Ok(()) - })?; + assert_eq!(tx.ttl_cutoff_height, Some(14)); + Ok(()) + }, + )?; // Mine past the ttl block and try to send let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); // Wallet 2 will need to have updated past the TTL - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| { - let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |sender_api, m| { + let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; + Ok(()) + }, + )?; // And when wallet 1 sends, should be rejected - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |_sender_api, _m| { - let res = client1.send_tx_slate_direct("wallet2", &slate); - println!("Send after TTL result is: {:?}", res); - assert!(res.is_err()); - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |_sender_api, _m| { + let res = client1.send_tx_slate_direct("wallet2", &slate); + println!("Send after TTL result is: {:?}", res); + assert!(res.is_err()); + Ok(()) + }, + )?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/tx_list_filter.rs b/controller/tests/tx_list_filter.rs index d744694..424db24 100644 --- a/controller/tests/tx_list_filter.rs +++ b/controller/tests/tx_list_filter.rs @@ -23,6 +23,7 @@ use grin_core as core; use grin_keychain as keychain; use grin_util as util; use libwallet::{RetrieveTxQueryArgs, RetrieveTxQuerySortField}; +use std::path::PathBuf; use self::libwallet::{InitTxArgs, Slate}; use impls::test_framework::{self, LocalWalletClient}; @@ -53,154 +54,160 @@ fn test_wallet_tx_filtering( >, >, mask: Option<&SecretKey>, + test_dir: &'static str, ) -> Result<(), libwallet::Error> { - wallet::controller::owner_single_use(Some(wallet.clone()), mask, None, |api, _m| { - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_id = Some(5); + wallet::controller::owner_single_use( + wallet.clone(), + mask, + PathBuf::from(test_dir), + |api, _m| { + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_id = Some(5); - // Min ID - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results[0].id, 5); - assert_eq!(tx_results[tx_results.len() - 1].id, 33); + // Min ID + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results[0].id, 5); + assert_eq!(tx_results[tx_results.len() - 1].id, 33); - // Max ID - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(20); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results[0].id, 5); - assert_eq!(tx_results[tx_results.len() - 1].id, 20); + // Max ID + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(20); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results[0].id, 5); + assert_eq!(tx_results[tx_results.len() - 1].id, 20); - // Exclude 1 cancelled - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 28); + // Exclude 1 cancelled + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 28); - // Exclude 1 cancelled, show confirmed only - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(true); - tx_query_args.include_confirmed_only = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 14); + // Exclude 1 cancelled, show confirmed only + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(true); + tx_query_args.include_confirmed_only = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 14); - // show outstanding only (including cancelled) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(false); - tx_query_args.include_outstanding_only = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 15); + // show outstanding only (including cancelled) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(false); + tx_query_args.include_outstanding_only = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 15); - // outstanding only and confirmed only should give empty set - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(false); - tx_query_args.include_outstanding_only = Some(true); - tx_query_args.include_confirmed_only = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 0); + // outstanding only and confirmed only should give empty set + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(false); + tx_query_args.include_outstanding_only = Some(true); + tx_query_args.include_confirmed_only = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 0); - // include sent only - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_sent_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 15); + // include sent only + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_sent_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 15); - // include received only (none in this set) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_received_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 0); + // include received only (none in this set) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_received_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 0); - // include reverted only (none in this set) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_reverted_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 0); + // include reverted only (none in this set) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_reverted_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 0); - // include coinbase only - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_coinbase_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 19); + // include coinbase only + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_coinbase_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 19); - // Amounts - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 27); + // Amounts + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 27); - // amount, should see as above with coinbases excluded - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); - tx_query_args.max_amount = Some(60_000_000_000 - 1); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 8); + // amount, should see as above with coinbases excluded + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); + tx_query_args.max_amount = Some(60_000_000_000 - 1); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 8); - // Amount - should only see coinbase (incoming) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_amount = Some(60_000_000_000); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 19); + // Amount - should only see coinbase (incoming) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_amount = Some(60_000_000_000); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 19); - // sort order - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; + // sort order + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; - assert_eq!(tx_results[0].id, 33); - assert_eq!(tx_results[tx_results.len() - 1].id, 0); + assert_eq!(tx_results[0].id, 33); + assert_eq!(tx_results[tx_results.len() - 1].id, 0); - // change sort field to amount desc, should have coinbases first - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); - tx_query_args.sort_field = Some(RetrieveTxQuerySortField::TotalAmount); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results[0].amount_credited, 60_000_000_000); + // change sort field to amount desc, should have coinbases first + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); + tx_query_args.sort_field = Some(RetrieveTxQuerySortField::TotalAmount); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results[0].amount_credited, 60_000_000_000); - /*for entry in tx_results.iter() { - println!("{:?}", entry); - }*/ + /*for entry in tx_results.iter() { + println!("{:?}", entry); + }*/ - Ok(()) - })?; + Ok(()) + }, + )?; Ok(()) } @@ -248,15 +255,25 @@ fn build_chain_for_tx_filtering( // Stop the scanning updater threads because it extends the time needed to build the chain // exponentially - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, _m| { - api.stop_updater()?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, _m| { + api.stop_updater()?; + Ok(()) + }, + )?; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, _m| { - api.stop_updater()?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, _m| { + api.stop_updater()?; + Ok(()) + }, + )?; // few values to keep things shorter let reward = core::consensus::REWARD; @@ -268,17 +285,22 @@ fn build_chain_for_tx_filtering( let mut wallet_1_has_funds = false; // Check wallet 1 contents - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 spendable - {}", - wallet1_info.amount_currently_spendable - ); - if wallet1_info.amount_currently_spendable > reward { - wallet_1_has_funds = true; - } - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + debug!( + "Wallet 1 spendable - {}", + wallet1_info.amount_currently_spendable + ); + if wallet1_info.amount_currently_spendable > reward { + wallet_1_has_funds = true; + } + Ok(()) + }, + )?; if !wallet_1_has_funds { let _ = @@ -293,9 +315,9 @@ fn build_chain_for_tx_filtering( let mut slate = Slate::blank(1, false); debug!("Creating TX for {}", amount); wallet::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { @@ -321,26 +343,31 @@ fn build_chain_for_tx_filtering( let amount: u64 = 1_000_000; let mut slate = Slate::blank(1, false); debug!("Creating TX for {}", amount); - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 1, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - sender_api.cancel_tx(m, Some(33), None)?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |sender_api, m| { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 1, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + let slate_i = sender_api.init_send_tx(m, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + sender_api.tx_lock_outputs(m, &slate)?; + sender_api.cancel_tx(m, Some(33), None)?; + Ok(()) + }, + )?; // Perform actual testing - test_wallet_tx_filtering(wallet1, mask1)?; + test_wallet_tx_filtering(wallet1, mask1, test_dir)?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/updater_thread.rs b/controller/tests/updater_thread.rs index c137b4e..ea8273d 100644 --- a/controller/tests/updater_thread.rs +++ b/controller/tests/updater_thread.rs @@ -23,6 +23,7 @@ extern crate grin_wallet_libwallet as libwallet; // use grin_wallet_util::grin_core as core; use impls::test_framework::{self, LocalWalletClient}; +use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -71,18 +72,28 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err }); // add some accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + }, + )?; // add some accounts - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - })?; + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + api.create_account_path(m, "account1")?; + api.create_account_path(m, "account2")?; + Ok(()) + }, + )?; // Get some mining done { @@ -93,7 +104,7 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - let owner_api = api::Owner::new(wallet1, None); + let owner_api = api::Owner::new(wallet1, None, PathBuf::from(test_dir)); owner_api.start_updater(mask1, Duration::from_secs(5))?; // let updater thread run a bit diff --git a/impls/Cargo.toml b/impls/Cargo.toml index 188c5de..893f61f 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -35,6 +35,7 @@ url = "2.1" arti-client = { version = "0.44.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] } tor-rtcompat = { version = "0.44.0", features = ["static"] } fs-mistrust = "0.15.0" +fslock-guard = "0.8" tor-hsservice = "0.44.0" tor-hsrproxy = "0.44.0" tor-keymgr = "0.44.0" diff --git a/impls/src/adapters/tor.rs b/impls/src/adapters/tor.rs index 278ccfe..1394dd7 100644 --- a/impls/src/adapters/tor.rs +++ b/impls/src/adapters/tor.rs @@ -17,9 +17,9 @@ use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate}; use crate::libwallet::{Error, Slate}; use crate::tor::arti::{start_tor_client, tor_post}; use crate::tor::bridge::TorBridge; +use crate::tor::config as tor_config; use crate::tor::process::TorProcess; use crate::tor::proxy::TorProxy; -use crate::tor::{config as tor_config, Tor}; use crate::SlateSender; use grin_wallet_config::TorConfig; use serde::Serialize; @@ -34,7 +34,7 @@ use std::sync::Arc; pub struct TorSlateSender { base_url: String, config: TorConfig, - tor: Arc, + tor_process: Arc>, } impl TorSlateSender { @@ -43,27 +43,29 @@ impl TorSlateSender { if !base_url.starts_with("http") && !base_url.starts_with("https") { Err(Error::GenericError("Scheme must be http".to_string())) } else { - let tor_dir = { - let mut path = PathBuf::from(&config.send_config_dir); - path.push("tor"); - path.push("sender"); - path - }; let tor = if config.use_integrated.unwrap_or(false) { - start_tor_client(tor_dir.to_str().unwrap(), config.clone())? + start_tor_client(config.clone())?; + None } else { - Self::launch_tor_process(&config, &tor_dir)? + let tor_dir = { + let mut path = PathBuf::from(&config.send_config_dir); + path.push("tor"); + path.push("sender"); + path + }; + let p = Self::launch_tor_process(&config, &tor_dir)?; + Some(p) }; Ok(TorSlateSender { base_url: base_url.to_owned(), config, - tor: Arc::new(tor), + tor_process: Arc::new(tor), }) } } /// Launch external Tor process. - fn launch_tor_process(config: &TorConfig, tor_dir: &PathBuf) -> Result { + fn launch_tor_process(config: &TorConfig, tor_dir: &PathBuf) -> Result { let mut tor = TorProcess::new(); let socks_proxy_addr = SocketAddr::V4( config @@ -107,11 +109,7 @@ impl TorSlateSender { .completion_percent(100) .launch() .map_err(|e| Error::TorProcess(format!("{:?}", e)))?; - Ok(Tor { - process: Some(tor), - service: None, - client: None, - }) + Ok(tor) } /// Check version of the listening wallet @@ -177,7 +175,7 @@ impl TorSlateSender { where IN: Serialize, { - let res = if self.tor.process.is_some() { + let res = if self.tor_process.is_some() { let socks_proxy_addr = SocketAddr::V4(self.config.socks_proxy_addr.parse().map_err(|_| { ClientError::Internal("Socks proxy address is not set".to_string()) @@ -189,12 +187,8 @@ impl TorSlateSender { let res = client.send_request(req)?; res } else { - if let Some(client) = &self.tor.client { - tor_post(client.clone(), &self.config, &input, url) - .map_err(|e| ClientError::RequestError(format!("{:?}", e)))? - } else { - return Err(ClientError::Internal("Tor is not configured".to_string())); - } + tor_post(&self.config, &input, url) + .map_err(|e| ClientError::RequestError(format!("{:?}", e)))? }; Ok(res) } diff --git a/impls/src/lifecycle/default.rs b/impls/src/lifecycle/default.rs index 4514eb3..7514a30 100644 --- a/impls/src/lifecycle/default.rs +++ b/impls/src/lifecycle/default.rs @@ -24,6 +24,7 @@ use crate::lifecycle::seed::WalletSeed; use crate::util::secp::key::SecretKey; use crate::util::ZeroingString; use grin_util::logger::LoggingConfig; +use grin_wallet_config::config::reload_global_config; use std::fs; use std::path::PathBuf; use std::path::MAIN_SEPARATOR; @@ -89,48 +90,11 @@ where logging_config: Option, tor_config: Option, ) -> Result<(), Error> { - let mut default_config = GlobalWalletConfig::for_chain(&chain_type); - let config_file_version = match default_config.members.as_ref() { - Some(m) => m.clone().config_file_version, - None => None, - }; - let logging = match logging_config { - Some(l) => Some(l), - None => match default_config.members.as_ref() { - Some(m) => m.clone().logging, - None => None, - }, - }; - // Check if config was provided, if not load default and set update to "true" - let (wallet, update) = match wallet_config { - Some(w) => (w, false), - None => match default_config.members.as_ref() { - Some(m) => (m.clone().wallet, true), - None => (WalletConfig::default(), true), - }, - }; - let tor = match tor_config { - Some(t) => Some(t), - None => match default_config.members.as_ref() { - Some(m) => m.clone().tor, - None => Some(TorConfig::default()), - }, - }; - default_config = GlobalWalletConfig { - members: Some(GlobalWalletConfigMembers { - config_file_version, - wallet, - tor, - logging, - }), - ..default_config - }; 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", @@ -140,10 +104,35 @@ where return Err(Error::Lifecycle(msg)); } - // If config exists but the datadir return ok if config_file_name.exists() { return Ok(()); } + + let mut default_config = GlobalWalletConfig::for_chain(&chain_type, &config_file_name); + let config_file_version = default_config.members.config_file_version; + let logging = match logging_config.clone() { + Some(l) => Some(l), + None => default_config.members.logging, + }; + // Check if config was provided, if not load default and set update to "true" + let (wallet, update) = match wallet_config.clone() { + Some(w) => (w, false), + None => (default_config.members.wallet, true), + }; + let tor = match tor_config.clone() { + Some(t) => Some(t), + None => default_config.members.tor, + }; + default_config = GlobalWalletConfig { + members: GlobalWalletConfigMembers { + config_file_version, + wallet, + tor, + logging, + }, + ..default_config + }; + // default settings are updated if no config was provided, no support for top_dir/here let mut abs_path_node = std::env::current_dir()?; abs_path_node.push(self.data_dir.clone()); @@ -171,6 +160,8 @@ where return Err(Error::Lifecycle(msg)); } + reload_global_config(&config_file_name)?; + info!( "File {} configured and created", config_file_name.to_str().unwrap(), diff --git a/impls/src/tor/arti.rs b/impls/src/tor/arti.rs index 1d4acd6..769e2bc 100644 --- a/impls/src/tor/arti.rs +++ b/impls/src/tor/arti.rs @@ -13,7 +13,6 @@ // limitations under the License. use crate::tor::config::exp_sec_key_bytes; -use crate::tor::{ArtiRuntimeWrapper, Tor}; use arti_client::config::pt::TransportConfigBuilder; use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder}; use arti_client::{TorClient, TorClientConfig}; @@ -21,6 +20,7 @@ use bytes::Bytes; use ed25519_dalek::hazmat::ExpandedSecretKey; use ed25519_dalek::Digest; use fs_mistrust::Mistrust; +use fslock_guard::LockFileGuard; use grin_util::secp::SecretKey; use grin_wallet_config::TorConfig; use grin_wallet_libwallet::Error; @@ -31,10 +31,11 @@ use hyper_util::rt::TokioIo; use lazy_static::lazy_static; use serde::Serialize; use sha2::Sha512; +use std::collections::HashMap; use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::sync::{Arc, LazyLock, Mutex}; -use std::thread; +use std::thread::{self, JoinHandle}; use std::time::Duration; use tor_hscrypto::pk::{HsIdKey, HsIdKeypair}; use tor_hsrproxy::config::{ @@ -42,19 +43,46 @@ use tor_hsrproxy::config::{ }; use tor_hsrproxy::OnionServiceReverseProxy; use tor_hsservice::config::OnionServiceConfigBuilder; -use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname}; +use tor_hsservice::{ + HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname, RunningOnionService, +}; use tor_keymgr::config::CfgPath; use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector}; use tor_llcrypto::pk::ed25519::ExpandedKeypair; use tor_rtcompat::tokio::TokioNativeTlsRuntime; use tor_rtcompat::{SleepProviderExt, ToplevelBlockOn}; -// Arti Tokio runtime. +struct TorService { + service: Arc, + proxy: Arc, + worker: JoinHandle<()>, + state_lock: PathBuf, +} + lazy_static! { - pub static ref ARTI_RUNTIME: LazyLock>> = + /// Arti Tokio runtime. + static ref ARTI_RUNTIME: LazyLock>> = LazyLock::new(|| Mutex::new(ArtiRuntimeWrapper::create().ok())); - pub static ref ARTI_CLIENT_CONFIG: LazyLock>, TorClientConfig)>>> = + /// Arti client and config. + static ref ARTI_CLIENT_CONFIG: LazyLock>, TorClientConfig)>>> = LazyLock::new(|| Mutex::new(None)); + /// Running services, where key is onion address. + static ref ARTI_PROXY_SERVICES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +} + +/// Arti client runtime wrapper. +#[derive(Clone)] +struct ArtiRuntimeWrapper { + runtime: TokioNativeTlsRuntime, +} + +impl ArtiRuntimeWrapper { + fn create() -> Result { + Ok(Self { + runtime: TokioNativeTlsRuntime::create()?, + }) + } } /// Get Tor client runtime. @@ -67,22 +95,51 @@ fn runtime() -> Result { Ok(r.runtime.clone()) } +/// Stop running Tor service by onion address as key. +pub fn stop_tor_service(onion_addr: String) -> Result<(), Error> { + let service = ARTI_PROXY_SERVICES.lock().unwrap().remove(&onion_addr); + let Some(service) = service else { + error!("Service {} to stop was not found", onion_addr); + return Ok(()); + }; + let TorService { + service, + proxy, + worker, + state_lock, + } = service; + + proxy.shutdown(); + worker + .join() + .map_err(|_| Error::TorProcess("Tor proxy thread panicked".into()))?; + drop(service); + drop( + LockFileGuard::lock(state_lock) + .map_err(|e| Error::TorProcess(format!("Can not stop Tor service: {}", e)))?, + ); + Ok(()) +} + +/// Get state and cache data paths. +fn state_cache_paths(config: &TorConfig) -> (PathBuf, PathBuf) { + let mut tor_dir = PathBuf::from(&config.send_config_dir); + tor_dir.push("arti"); + let state_path = tor_dir.join("state"); + let cache_path = tor_dir.join("cache"); + (state_path, cache_path) +} + /// Start Tor service from provided key. -pub fn start_tor_service( - key: SecretKey, - tor_dir: &str, - addr: &str, - config: TorConfig, -) -> Result { +pub fn start_tor_service(key: SecretKey, addr: &str, config: &TorConfig) -> Result<(), Error> { info!("Starting integrated Tor listener."); let use_proxy = config.proxy.transport.is_some() && config.proxy.address.is_some(); if use_proxy { info!("Proxy configuration will be ignored."); } - let state_path = Path::new(&tor_dir).join("state"); - let cache_path = Path::new(&tor_dir).join("cache"); - let (client, config) = init_client(&state_path, &cache_path, config, true)?; + let (state_path, cache_path) = state_cache_paths(&config); + let (client, config) = init_client(&state_path, &cache_path, config)?; // Add service key to keystore. let onion_address = @@ -90,6 +147,10 @@ pub fn start_tor_service( let hs = HsNickname::new(onion_address.to_string()) .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; let keystore_path = Path::new(&state_path).join("keystore"); + // Arti 0.44 uses this lock to finish stopping a service before it can start again. + let state_lock = state_path + .join("hss") + .join(format!("{}.lock", onion_address)); let _ = add_service_key(config.fs_mistrust(), &key, &hs, keystore_path)?; // Launch Onion service. @@ -97,77 +158,66 @@ pub fn start_tor_service( .nickname(hs.clone()) .build() .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; - let running_onion_service = match client.launch_onion_service(service_config) { + let (service, proxy, worker) = match client.launch_onion_service(service_config) { Ok(res) => { if let Some((service, mut request)) = res { let addr: SocketAddr = addr .parse() .map_err(|e| Error::TorProcess(format!("{:?}", e)))?; + let proxy = create_service_proxy(addr)?; let c = client.clone(); - thread::spawn(move || { + let p = proxy.clone(); + // Launch service proxy. + let worker = thread::spawn(move || { c.clone().runtime().block_on(async move { - // Launch service proxy. - async fn run_proxy( - c: Arc>, - addr: SocketAddr, - request: &mut S, - hs: HsNickname, - ) where - S: futures::Stream - + Unpin - + Send - + 'static, - { - match run_service_proxy(c.clone(), addr, request, hs.clone()).await { + loop { + match run_service_proxy(p.clone(), &mut request, hs.clone()).await { Ok(_) => { info!("Tor proxy stopped"); + break; } Err(e) => { error!("Tor proxy error: {:?}, restarting", e); tokio::time::sleep(Duration::from_millis(1000)).await; - Box::pin(run_proxy(c, addr, request, hs)).await; + continue; } } } - run_proxy(c.clone(), addr, &mut request, hs.clone()).await; }) }); - service + (service, proxy, worker) } else { return Err(Error::TorProcess("Can not launch onion service".to_owned())); } } Err(e) => return Err(Error::TorProcess(format!("{:?}", e))), }; - Ok(Tor { - process: None, - service: Some(running_onion_service), - client: Some(client), - }) + + let mut running_services = ARTI_PROXY_SERVICES.lock().unwrap(); + running_services.insert( + onion_address.to_string(), + TorService { + service, + proxy, + worker, + state_lock, + }, + ); + + Ok(()) } /// Start Tor client to send requests. -pub fn start_tor_client(tor_dir: &str, config: TorConfig) -> Result { +pub fn start_tor_client(config: TorConfig) -> Result<(), Error> { info!("Starting integrated Tor client"); - let state_path = Path::new(tor_dir).join("state"); - let cache_path = Path::new(tor_dir).join("cache"); - - let (client, _) = init_client(&state_path, &cache_path, config, false)?; - Ok(Tor { - process: None, - service: None, - client: Some(client), - }) + let (state_path, cache_path) = state_cache_paths(&config); + let (_, _) = init_client(&state_path, &cache_path, &config)?; + Ok(()) } -/// Make POST request with provided client. -pub fn tor_post( - client: Arc>, - tor_config: &TorConfig, - input: &IN, - url: &str, -) -> Result +/// Make POST request. +pub fn tor_post(tor_config: &TorConfig, input: &IN, url: &str) -> Result where IN: Serialize, { @@ -183,6 +233,8 @@ where } .to_string(); let timeout = tor_config.request_timeout(); + let (state_path, cache_path) = state_cache_paths(&tor_config); + let (client, _) = init_client(&state_path, &cache_path, tor_config)?; let res: Result = thread::spawn(move || { let c = client.clone(); client.runtime().block_on(async move { @@ -242,8 +294,7 @@ where fn init_client( state_path: &PathBuf, cache_path: &PathBuf, - tor_config: TorConfig, - reuse_client: bool, + tor_config: &TorConfig, ) -> Result<(Arc>, TorClientConfig), Error> { let mut builder = TorClientConfigBuilder::from_directories(&state_path, cache_path); builder.address_filter().allow_onion_addrs(true); @@ -283,31 +334,23 @@ fn init_client( .build() .map_err(|e| Error::TorConfig(format!("{:?}", e)))?; - if reuse_client { - // Return existing client if config was not changed. - let mut cached_client_config = ARTI_CLIENT_CONFIG.lock().unwrap(); - if let Some((client, c)) = cached_client_config.as_ref() { - if c == &config { - debug!("Reusing Arti Tor client from global state."); - return Ok((client.clone(), c.clone())); - } else { - debug!("Tor config changed, rebuild client."); - *cached_client_config = None; - } + // Return existing client if config was not changed. + let mut cached_client_config = ARTI_CLIENT_CONFIG.lock().unwrap(); + if let Some((client, c)) = cached_client_config.as_ref() { + if c == &config { + debug!("Reusing Arti Tor client from global state."); + return Ok((client.clone(), c.clone())); + } else { + debug!("Tor config changed, rebuild client."); + *cached_client_config = None; } - let res = launch_client(config.clone(), &tor_config); - return match res { - Ok(client) => { - cached_client_config.replace((client.clone(), config.clone())); - Ok((client, config)) - } - Err(e) => Err(e), - }; } - let res = launch_client(config.clone(), &tor_config); match res { - Ok(client) => Ok((client, config)), + Ok(client) => { + cached_client_config.replace((client.clone(), config.clone())); + Ok((client, config)) + } Err(e) => Err(e), } } @@ -355,18 +398,8 @@ fn launch_client( res } -/// Launch Onion service proxy. -async fn run_service_proxy( - client: Arc>, - addr: SocketAddr, - request: &mut S, - nickname: HsNickname, -) -> Result<(), Error> -where - S: futures::Stream + Unpin + Send + 'static, -{ - let runtime = client.runtime().clone(); - +/// Create Onion service proxy. +fn create_service_proxy(addr: SocketAddr) -> Result, Error> { // Setup proxy to forward request from Tor address to local address. let proxy_rule = ProxyRule::new( ProxyPattern::one_port(80).map_err(|e| Error::TorConfig(format!("{}", e)))?, @@ -378,12 +411,24 @@ where .build() .map_err(|e| Error::TorConfig(format!("{}", e)))?; let proxy = OnionServiceReverseProxy::new(proxy_cfg); + Ok(proxy) +} - // Start proxy for launched service. +/// Launch service proxy. +async fn run_service_proxy( + proxy: Arc, + request: &mut S, + nickname: HsNickname, +) -> Result<(), Error> +where + S: futures::Stream + Unpin + Send + 'static, +{ + let runtime = runtime()?; proxy .handle_requests(runtime, nickname, request) .await - .map_err(|e| Error::TorProcess(format!("{:?}", e))) + .map_err(|e| Error::TorProcess(format!("{:?}", e)))?; + Ok(()) } /// Save Onion service key to keystore. diff --git a/impls/src/tor/mod.rs b/impls/src/tor/mod.rs index 7bfeb0a..9eb97ec 100644 --- a/impls/src/tor/mod.rs +++ b/impls/src/tor/mod.rs @@ -17,28 +17,3 @@ pub mod bridge; pub mod config; pub mod process; pub mod proxy; - -/// Running Tor instance control. -pub struct Tor { - /// External to process control. - pub process: Option, - /// Integrated service. - pub service: Option>, - /// Integrated client. - pub client: - Option>>, -} - -/// Arti client runtime wrapper. -#[derive(Clone)] -pub struct ArtiRuntimeWrapper { - pub runtime: tor_rtcompat::tokio::TokioNativeTlsRuntime, -} - -impl ArtiRuntimeWrapper { - fn create() -> Result { - Ok(Self { - runtime: tor_rtcompat::tokio::TokioNativeTlsRuntime::create()?, - }) - } -} diff --git a/integration/tests/framework.rs b/integration/tests/framework.rs index 2c8e503..3e0b883 100644 --- a/integration/tests/framework.rs +++ b/integration/tests/framework.rs @@ -281,7 +281,11 @@ impl LocalServerContainer { let _ = fs::create_dir_all(self.wallet_config.clone().data_file_dir); let r = wallet::WalletSeed::init_file(&self.wallet_config, 32, None, ""); - let client_n = HTTPNodeClient::new(&self.wallet_config.check_node_api_http_addr, None, std::time::Duration::from_secs(60)); + let client_n = HTTPNodeClient::new( + &self.wallet_config.check_node_api_http_addr, + None, + std::time::Duration::from_secs(60), + ); if let Err(_e) = r { //panic!("Error initializing wallet seed: {}", e); @@ -354,7 +358,11 @@ impl LocalServerContainer { let keychain: keychain::ExtKeychain = wallet_seed .derive_keychain(false) .expect("Failed to derive keychain from seed file and passphrase."); - let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None, std::time::Duration::from_secs(60)); + let client_n = HTTPNodeClient::new( + &config.check_node_api_http_addr, + None, + std::time::Duration::from_secs(60), + ); let mut wallet = LMDBBackend::new(config.clone(), "", client_n) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); wallet.keychain = Some(keychain); @@ -382,7 +390,11 @@ impl LocalServerContainer { .derive_keychain(false) .expect("Failed to derive keychain from seed file and passphrase."); - let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None, std::time::Duration::from_secs(60)); + let client_n = HTTPNodeClient::new( + &config.check_node_api_http_addr, + None, + std::time::Duration::from_secs(60), + ); let client_w = HTTPWalletCommAdapter::new(); let max_outputs = 500; diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index d5a9f9c..0e6af52 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -326,6 +326,12 @@ pub enum Error { GenericError(String), } +impl From for Error { + fn from(error: grin_wallet_config::ConfigError) -> Error { + Error::GenericError(format!("{}", error)) + } +} + impl From for Error { fn from(error: grin_store::Error) -> Error { Error::Backend(format!("{}", error)) diff --git a/src/bin/grin-wallet.rs b/src/bin/grin-wallet.rs index 2ded32d..6e90995 100644 --- a/src/bin/grin-wallet.rs +++ b/src/bin/grin-wallet.rs @@ -31,7 +31,7 @@ use std::env; use std::path::PathBuf; use std::path::MAIN_SEPARATOR; -// include build information +/// Include build information pub mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); } @@ -55,7 +55,7 @@ pub fn info_strings() -> (String, String) { ) } -// Helper fuction to format paths according to OS, avoids bugs on Linux +/// Helper function to format paths according to OS, avoids bugs on Linux pub fn fmt_path(path: String) -> String { let sep = &MAIN_SEPARATOR.to_string(); let path = path.replace("/", &sep).replace("\\", &sep); @@ -117,22 +117,24 @@ fn real_main() -> i32 { // Load relevant config, try and load a wallet config file // Use defaults for configuration if config file not found anywhere - let mut config = match config::initial_setup_wallet(&chain_type, current_dir, create_path) { + let config = match config::initial_setup_wallet(&chain_type, current_dir, create_path) { Ok(c) => c, - Err(e) => match e { - ConfigError::PathNotFoundError(m) => { - println!("Wallet configuration not found at {}. (Run `grin-wallet init` to create a new wallet)", m); - return 0; + Err(e) => { + return match e { + ConfigError::PathNotFoundError(m) => { + println!("Wallet configuration not found at {}. (Run `grin-wallet init` to create a new wallet)", m); + 0 + } + m => { + println!("Unable to load wallet configuration: {} (Run `grin-wallet init` to create a new wallet)", m); + 0 + } } - m => { - println!("Unable to load wallet configuration: {} (Run `grin-wallet init` to create a new wallet)", m); - return 0; - } - }, + } }; // Load logging config - let mut l = config.members.as_mut().unwrap().logging.clone().unwrap(); + let mut l = config.members.logging.clone().unwrap(); // no logging to stdout if we're running cli match args.subcommand() { ("cli", _) => l.log_to_stdout = true, @@ -141,24 +143,14 @@ fn real_main() -> i32 { init_logger(Some(l), None); info!( "Using wallet configuration file at {}", - config.config_file_path.as_ref().unwrap().to_str().unwrap() + config.config_file_path.to_str().unwrap() ); log_build_info(); - global::init_global_chain_type( - config - .members - .as_ref() - .unwrap() - .wallet - .chain_type - .as_ref() - .unwrap() - .clone(), - ); + global::init_global_chain_type(config.members.wallet.chain_type.as_ref().unwrap().clone()); - global::init_global_accept_fee_base(config.members.as_ref().unwrap().wallet.accept_fee_base()); - let wallet_config = config.clone().members.unwrap().wallet; + global::init_global_accept_fee_base(config.members.wallet.accept_fee_base()); + let wallet_config = config.clone().members.wallet; let timeout = wallet_config.api_request_timeout(); let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None, timeout).unwrap(); diff --git a/src/cli/cli.rs b/src/cli/cli.rs index 816bfcb..d410785 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -19,7 +19,8 @@ use clap::App; //use colored::Colorize; use grin_keychain as keychain; use grin_wallet_api::Owner; -use grin_wallet_config::{TorConfig, WalletConfig}; +use grin_wallet_config::config::get_global_config; +use grin_wallet_config::GlobalWalletConfig; use grin_wallet_controller::command::GlobalArgs; use grin_wallet_controller::Error; use grin_wallet_impls::DefaultWalletImpl; @@ -107,10 +108,9 @@ pub fn start_updater_thread(rx: Receiver) -> Result<(), Error> { } pub fn command_loop( + config: GlobalWalletConfig, wallet_inst: Arc>>>, keychain_mask: Option, - wallet_config: &WalletConfig, - tor_config: &TorConfig, global_wallet_args: &GlobalArgs, test_mode: bool, ) -> Result<(), Error> @@ -151,7 +151,7 @@ where // catch updater messages let (tx, rx) = channel(); - let mut owner_api = Owner::new(wallet_inst, Some(tx)); + let mut owner_api = Owner::new(wallet_inst, Some(tx), config.config_file_path.clone()); start_updater_thread(rx)?; // start the automatic updater @@ -218,11 +218,15 @@ where } _ => keychain_mask, }; + let config = get_global_config(&config.config_file_path) + .map_err(|e| Error::GenericError(e.to_string()))?; + let tor_config = config.tor_config(); + let wallet_config = config.members.wallet; match wallet_args::parse_and_execute( &mut owner_api, keychain_mask.clone(), &wallet_config, - &tor_config, + tor_config, &global_wallet_args, &args, test_mode, diff --git a/src/cmd/wallet.rs b/src/cmd/wallet.rs index 14cd244..e538ef6 100644 --- a/src/cmd/wallet.rs +++ b/src/cmd/wallet.rs @@ -30,13 +30,8 @@ pub fn wallet_command( where C: NodeClient + 'static, { - // just get defaults from the global config - let wallet_config = config.members.clone().unwrap().wallet; - - let tor_config = config.members.unwrap().tor; - // Check the node version info, and exit with report if we're not compatible - let global_wallet_args = wallet_args::parse_global_args(&wallet_config, &wallet_args) + let global_wallet_args = wallet_args::parse_global_args(&config.members.wallet, &wallet_args) .expect("Can't read configuration file"); node_client.set_node_api_secret(global_wallet_args.node_api_secret.clone()); @@ -63,14 +58,7 @@ where } // ... if node isn't available, allow offline functions - let res = wallet_args::wallet_command( - wallet_args, - wallet_config, - tor_config, - node_client, - false, - |_| {}, - ); + let res = wallet_args::wallet_command(wallet_args, config, node_client, false, |_| {}); // we need to give log output a chance to catch up before exiting thread::sleep(Duration::from_millis(100)); diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index be22dcd..2bce63f 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -25,7 +25,7 @@ use grin_core as core; use grin_core::core::amount_to_hr_string; use grin_keychain as keychain; use grin_wallet_api::Owner; -use grin_wallet_config::{TorConfig, WalletConfig}; +use grin_wallet_config::{GlobalWalletConfig, TorConfig, WalletConfig}; use grin_wallet_controller::{command, Error}; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; use grin_wallet_libwallet::{self, Slate, SlatepackAddress, SlatepackArmor}; @@ -395,23 +395,6 @@ where Ok(command::RecoverArgs { passphrase }) } -pub fn parse_listen_args( - config: &mut WalletConfig, - tor_config: &mut TorConfig, - args: &ArgMatches, -) -> Result { - if let Some(port) = args.value_of("port") { - config.api_listen_port = port.parse().unwrap(); - } - if let Some(bridge) = args.value_of("bridge") { - tor_config.bridge.bridge_line = Some(bridge.into()); - } - if args.is_present("no_tor") { - tor_config.use_tor_listener = false; - } - Ok(command::ListenArgs {}) -} - pub fn parse_owner_api_args( config: &mut WalletConfig, args: &ArgMatches, @@ -996,8 +979,7 @@ pub fn parse_verify_proof_args(args: &ArgMatches) -> Result( wallet_args: &ArgMatches, - mut wallet_config: WalletConfig, - tor_config: Option, + config: GlobalWalletConfig, mut node_client: C, test_mode: bool, wallet_inst_cb: F, @@ -1019,6 +1001,7 @@ where >, ), { + let mut wallet_config = config.members.wallet.clone(); if let Some(dir) = wallet_args.value_of("top_level_dir") { wallet_config.data_file_dir = dir.to_string().clone(); } @@ -1041,13 +1024,6 @@ where wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into(); } - // for backwards compatibility: If tor config doesn't exist in the file, assume - // the top level directory for data - let tor_config = tor_config.unwrap_or_else(|| TorConfig { - send_config_dir: wallet_config.data_file_dir.clone(), - ..Default::default() - }); - // Instantiate wallet (doesn't open the wallet) let wallet = inst_wallet::, C, keychain::ExtKeychain>( @@ -1105,20 +1081,20 @@ where let res = match wallet_args.subcommand() { ("cli", Some(_)) => command_loop( + config, wallet, keychain_mask, - &wallet_config, - &tor_config, &global_wallet_args, test_mode, ), _ => { - let mut owner_api = Owner::new(wallet, None); + let tor_config = config.tor_config(); + let mut owner_api = Owner::new(wallet, None, config.config_file_path); parse_and_execute( &mut owner_api, keychain_mask, &wallet_config, - &tor_config, + tor_config, &global_wallet_args, &wallet_args, test_mode, @@ -1138,7 +1114,7 @@ pub fn parse_and_execute( owner_api: &mut Owner, keychain_mask: Option, wallet_config: &WalletConfig, - tor_config: &TorConfig, + tor_config: TorConfig, global_wallet_args: &command::GlobalArgs, wallet_args: &ArgMatches, test_mode: bool, @@ -1174,14 +1150,25 @@ where } ("listen", Some(args)) => { let mut c = wallet_config.clone(); - let mut t = tor_config.clone(); - let a = arg_parse!(parse_listen_args(&mut c, &mut t, &args)); + if let Some(port) = args.value_of("port") { + c.api_listen_port = port.parse().unwrap(); + } + let bridge = if let Some(bridge) = args.value_of("bridge") { + Some(bridge.into()) + } else { + None + }; + let use_tor = if args.is_present("no_tor") { + Some(false) + } else { + None + }; command::listen( owner_api, Arc::new(Mutex::new(keychain_mask)), - &c, - &t, - &a, + c, + bridge, + use_tor, &global_wallet_args.clone(), cli_mode, test_mode, @@ -1192,13 +1179,12 @@ where let mut g = global_wallet_args.clone(); g.tls_conf = None; arg_parse!(parse_owner_api_args(&mut c, &args)); - command::owner_api(owner_api, keychain_mask, &c, &tor_config, &g, test_mode) + command::owner_api(owner_api, keychain_mask, &c, &g, test_mode) } ("web", Some(_)) => command::owner_api( owner_api, keychain_mask, wallet_config, - tor_config, global_wallet_args, test_mode, ), @@ -1220,22 +1206,15 @@ where command::send( owner_api, km, - Some(tor_config.clone()), a, + tor_config, wallet_config.dark_background_color_scheme.unwrap_or(true), test_mode, ) } ("receive", Some(args)) => { let a = arg_parse!(parse_receive_args(&args)); - command::receive( - owner_api, - km, - &global_wallet_args, - a, - Some(tor_config.clone()), - test_mode, - ) + command::receive(owner_api, km, &global_wallet_args, a, tor_config, test_mode) } ("unpack", Some(args)) => { let a = arg_parse!(parse_unpack_args(&args)); @@ -1259,7 +1238,7 @@ where command::process_invoice( owner_api, km, - Some(tor_config.clone()), + tor_config, a, wallet_config.dark_background_color_scheme.unwrap_or(true), test_mode, diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 2c66f83..019e6cf 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -21,6 +21,7 @@ extern crate log; extern crate grin_wallet; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use std::path::PathBuf; use clap::App; use std::thread; @@ -60,7 +61,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // add wallet to proxy //let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); let config1 = initial_setup_wallet(test_dir, "wallet1"); - let wallet_config1 = config1.clone().members.unwrap().wallet; + let wallet_config1 = config1.clone().members.wallet; let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), @@ -80,7 +81,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; let config2 = initial_setup_wallet(test_dir, "wallet2"); - let wallet_config2 = config2.clone().members.unwrap().wallet; + let wallet_config2 = config2.clone().members.wallet; let (wallet2, mask2_i) = instantiate_wallet( wallet_config2.clone(), client2.clone(), @@ -148,14 +149,14 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Mine a bit into wallet 1 so we have something to send // (TODO: Be able to stop listeners so we can test this better) - let wallet_config1 = config1.clone().members.unwrap().wallet; + let wallet_config1 = config1.clone().members.wallet; let (wallet1, mask1_i) = instantiate_wallet(wallet_config1, client1.clone(), "password1", "default")?; let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; Ok(()) @@ -222,7 +223,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; bh += 1; - let wallet_config1 = config1.clone().members.unwrap().wallet; + let wallet_config1 = config1.clone().members.wallet; let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), @@ -233,9 +234,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Check our transaction log, should have 10 entries grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; @@ -259,7 +260,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet2", &client1, arg_vec)?; // check results in wallet 2 - let wallet_config2 = config2.clone().members.unwrap().wallet; + let wallet_config2 = config2.clone().members.wallet; let (wallet2, mask2_i) = instantiate_wallet( wallet_config2.clone(), client2.clone(), @@ -269,9 +270,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let mask2 = (&mask2_i).as_ref(); grin_wallet_controller::controller::owner_single_use( - Some(wallet2.clone()), + wallet2.clone(), mask2, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "account_1")?; let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; @@ -284,9 +285,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Send to wallet 2 with --amount_includes_fee let mut old_balance = 0; grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; @@ -344,9 +345,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Check the new balance of wallet 1 reduced by EXACTLY the tx amount (instead of amount + fee) // This confirms that the TX amount was correctly computed to allow for the fee grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; @@ -421,7 +422,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: bh += 1; // Check our transaction log, should have bh entries - let wallet_config1 = config1.clone().members.unwrap().wallet; + let wallet_config1 = config1.clone().members.wallet; let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), @@ -431,9 +432,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; @@ -494,7 +495,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: bh += 1; // Check our transaction log, should have bh entries + 1 for self-seld - let wallet_config1 = config1.clone().members.unwrap().wallet; + let wallet_config1 = config1.clone().members.wallet; let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), @@ -504,9 +505,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; @@ -629,9 +630,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // get tx output via -tx parameter let mut tx_id = "".to_string(); grin_wallet_controller::controller::owner_single_use( - Some(wallet2.clone()), + wallet2.clone(), mask2, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "default")?; let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; @@ -696,9 +697,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // and recently matured coinbase outputs, which were not mature at time of spending. // This confirms that the TX amount was correctly computed to allow for the fee grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), + wallet1.clone(), mask1, - None, + PathBuf::from(test_dir), |api, m| { api.set_active_account(m, "mining")?; let (_, wallet1_info) = api.retrieve_summary_info(m, true, 10)?; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 7ecd502..7833061 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -36,6 +36,7 @@ use util::secp::key::{PublicKey, SecretKey}; use grin_api as api; use grin_wallet::cmd::wallet_args; +use grin_wallet_config::config::reload_global_config; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -72,7 +73,7 @@ macro_rules! setup_proxy { // add wallet to proxy let config1 = initial_setup_wallet($test_dir, "wallet1"); - let wallet_config1 = config1.clone().members.unwrap().wallet; + let wallet_config1 = config1.clone().members.wallet; //config1.owner_api_listen_port = Some(13420); let ($wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), @@ -97,7 +98,7 @@ macro_rules! setup_proxy { } let config2 = initial_setup_wallet($test_dir, "wallet2"); - let wallet_config2 = config2.clone().members.unwrap().wallet; + let wallet_config2 = config2.clone().members.wallet; //config2.api_listen_port = 23415; let ($wallet2, mask2_i) = instantiate_wallet( wallet_config2.clone(), @@ -131,7 +132,7 @@ pub fn clean_output_dir(test_dir: &str) { pub fn setup(test_dir: &str) { util::init_test_logger(); clean_output_dir(test_dir); - global::set_local_chain_type(global::ChainTypes::AutomatedTesting); + global::set_local_chain_type(ChainTypes::AutomatedTesting); } /// Some tests require the global chain_type to be configured. @@ -141,7 +142,7 @@ pub fn setup(test_dir: &str) { /// leaks across multiple tests and will likely have unintended consequences. #[allow(dead_code)] pub fn setup_global_chain_type() { - global::init_global_chain_type(global::ChainTypes::AutomatedTesting); + global::init_global_chain_type(ChainTypes::AutomatedTesting); } /// Create a wallet config file in the given current directory @@ -150,7 +151,6 @@ pub fn config_command_wallet( wallet_name: &str, ) -> Result<(), grin_wallet_controller::Error> { let mut current_dir; - let mut default_config = GlobalWalletConfig::default(); current_dir = env::current_dir().unwrap_or_else(|e| { panic!("Error creating config file: {}", e); }); @@ -165,6 +165,9 @@ pub fn config_command_wallet( .to_owned(), ))?; } + + let mut default_config = + GlobalWalletConfig::for_chain(&ChainTypes::AutomatedTesting, &config_file_name); default_config.update_paths(¤t_dir, ¤t_dir); default_config .write_to_file(config_file_name.to_str().unwrap(), false, None, None) @@ -191,7 +194,7 @@ pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> GlobalWalletCo let _ = fs::create_dir_all(current_dir.clone()); let mut config_file_name = current_dir.clone(); config_file_name.push("grin-wallet.toml"); - GlobalWalletConfig::new(config_file_name.to_str().unwrap()).unwrap() + reload_global_config(&config_file_name).unwrap() } fn get_wallet_subcommand<'a>( @@ -211,8 +214,8 @@ fn get_wallet_subcommand<'a>( _ => ArgMatches::new(), } } -// -// Helper to create an instance of the LMDB wallet + +/// Helper to create an instance of the LMDB wallet #[allow(dead_code)] pub fn instantiate_wallet( mut wallet_config: WalletConfig, @@ -275,21 +278,10 @@ pub fn execute_command( let args = app.clone().get_matches_from(arg_vec); let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone()); let config = initial_setup_wallet(test_dir, wallet_name); - let mut wallet_config = config.clone().members.unwrap().wallet; - let tor_config = config.clone().members.unwrap().tor; - //unset chain type so it doesn't get reset - wallet_config.chain_type = None; - wallet_args::wallet_command( - &args, - wallet_config.clone(), - tor_config, - client.clone(), - true, - |_| {}, - ) + wallet_args::wallet_command(&args, config, client.clone(), true, |_| {}) } -// as above, but without necessarily setting up the wallet +/// As above, but without necessarily setting up the wallet #[allow(dead_code)] pub fn execute_command_no_setup( app: &App, @@ -307,13 +299,14 @@ where { let args = app.clone().get_matches_from(arg_vec); let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone()); - let config = config::initial_setup_wallet(&ChainTypes::AutomatedTesting, None, true).unwrap(); - let mut wallet_config = config.clone().members.unwrap().wallet; + let mut config = + config::initial_setup_wallet(&ChainTypes::AutomatedTesting, None, true).unwrap(); + let mut wallet_config = config.clone().members.wallet; wallet_config.chain_type = None; wallet_config.api_secret_path = None; wallet_config.node_api_secret_path = None; - let tor_config = config.members.unwrap().tor.clone(); - wallet_args::wallet_command(&args, wallet_config, tor_config, client.clone(), true, f) + config.members.wallet = wallet_config; + wallet_args::wallet_command(&args, config, client.clone(), true, f) } pub fn post(url: &Url, api_secret: Option, input: &IN) -> Result diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index fd27a50..997bdb3 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -27,6 +27,7 @@ use std::thread; use std::time::Duration; use grin_keychain::ExtKeychain; +use grin_wallet_config::GlobalWalletConfig; use grin_wallet_impls::DefaultLCProvider; use grin_wallet_libwallet::{InitTxArgs, Slate, SlateVersion, VersionedSlate}; use serde_json; @@ -74,7 +75,7 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; let config2 = initial_setup_wallet(test_dir, "wallet2"); - let wallet_config2 = config2.clone().members.unwrap().wallet; + let wallet_config2 = config2.clone().members.wallet; //config2.api_listen_port = 23415; let (wallet2, mask2_i) = instantiate_wallet( wallet_config2.clone(), @@ -193,6 +194,26 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { let pb = PathBuf::from(format!("{}/wallet1/grin-wallet.toml", test_dir)); assert!(pb.exists()); + let req = serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "set_tor_config", + "params": { + "tor_config": null + } + }); + let res = send_request_enc::<()>( + &JsonId::StrId(String::from("1")), + 1, + "http://127.0.0.1:43420/v3/owner", + &req.to_string(), + &shared_key, + )?; + assert!(res.is_ok()); + let tor = GlobalWalletConfig::new(pb.clone()).unwrap().tor_config(); + assert!(!tor.use_tor_listener); + assert_eq!(tor.skip_send_attempt, Some(true)); + // 5) Try and perform an operation without having a wallet open let req = include_str!("data/v3_reqs/retrieve_info.req.json"); let res = send_request_enc::( @@ -382,9 +403,9 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { // give this slate over to wallet 2 manually grin_wallet_controller::controller::owner_single_use( - Some(wallet2.clone()), + wallet2.clone(), mask2, - None, + PathBuf::from(test_dir), |api, m| { let args = InitTxArgs { src_acct_name: None,