Global config instance (#769)

* config: global instance

* fix: cli tests

* fix: owner api tests

* config: save tor config

* fix: owner rpc tests

* fix: set config file path on init and owner api call

* tor: store launched services, get global config at command loop, remove unused listen args, check for empty global config to set default, do not save empty tor config at owner

* api: update config in memory after successful file write

* arti: restart proxy in loop

* fix: comment for tor config save

* config: set or init, update without error

* config: scoped tor config

* build: update lock file

* config: ability to store multiple configurations at memory from config path

* tor: restart service on config change

* fix: owner api test

* foreign: pass tor config from args, stop service, error handling

* arti: move runtime wrapper to single file

* config: read existing config from file at create_config

* config: handle error on read

* arti: single dir for cache

* foreign: register config listener before launch

* config: save temp

* config: remove unused error type

* config: make path and members not optional, return an error on config creation if it exists, pass default tor config to send if not set

* arti: non public runtime wrapper

* config: migrate comments

* fix: rpc tests

* config: return from cache if exists

* config: save disabled tor config when null provided at owner and tor section was not set

* config: update tor with args at listener

* config: pass pathbuf directly on creation

* config: unique tmp file name, setting permissions and providing config version

* fix global config handling

* fix config persistence

* fix tor service restart

* fix config synchronization

* require wallet close before changing data directory

* unify global config updates

---------

Co-authored-by: wiesche <wiesche89@googlemail.com>
This commit is contained in:
ardocrat
2026-07-24 22:15:19 +02:00
committed by GitHub
co-authored by wiesche
parent 7fb47646e9
commit d2fe2b90f6
46 changed files with 4880 additions and 3311 deletions
Generated
+4
View File
@@ -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",
+28 -34
View File
@@ -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<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
/// 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<ForeignCheckMiddleware>,
/// Stored keychain mask (in case the stored wallet seed is tokenized)
keychain_mask: Option<SecretKey>,
/// Optional TOR configuration, holding address of sender and
/// data directory
tor_config: Mutex<Option<TorConfig>>,
}
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<P>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
config_path: P,
keychain_mask: Option<SecretKey>,
middleware: Option<ForeignCheckMiddleware>,
doctest_mode: bool,
) -> Self {
) -> Self
where
P: Into<crate::ConfigPath>,
{
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<TorConfig>) {
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<String>,
) -> Result<Slate, Error> {
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
+24 -2
View File
@@ -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, ExtKeychain>,
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;
+3
View File
@@ -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;
+157 -96
View File
@@ -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<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
/// 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<Mutex<Option<SecretKey>>>,
@@ -84,9 +90,6 @@ where
/// Holds all update and status messages returned by the
/// updater process
updater_messages: Arc<Mutex<Vec<StatusMessage>>>,
/// Optional TOR configuration, holding address of sender and
/// data directory
tor_config: Mutex<Option<TorConfig>>,
}
impl<L, C, K> Owner<L, C, K>
@@ -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<P>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
custom_channel: Option<Sender<StatusMessage>>,
) -> Self {
config_path: P,
) -> Self
where
P: Into<crate::ConfigPath>,
{
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<TorConfig>) {
let mut lock = self.tor_config.lock();
*lock = tor_config;
pub fn set_tor_config(&self, tor_config: Option<TorConfig>) -> 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<Slate, Error> {
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<Slate, Error> {
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<Slate, Error> {
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.
+21 -5
View File
@@ -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<TorConfig>) -> 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<Option<serde_json::Value>, 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, ExtKeychain>,
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);
+45
View File
@@ -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<Mutex<PathBuf>>);
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<PathBuf> for ConfigPath {
fn from(path: PathBuf) -> Self {
Self(Arc::new(Mutex::new(path)))
}
}
pub(crate) fn load(config_path: &Path) -> Result<TorConfig, Error> {
Ok(get_global_config(config_path)?.tor_config())
}
+4
View File
@@ -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"
+3 -91
View File
@@ -313,10 +313,10 @@ fn comments() -> HashMap<String, String> {
}
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::<Vec<&str>>()[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<u32>,
) -> 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::<Vec<String>>();
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::<Vec<(String, String)>>();
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
}
+517 -84
View File
@@ -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<String, Sender<()>>;
type ConfigRegistry = HashMap<PathBuf, (GlobalWalletConfig, ConfigListeners)>;
lazy_static! {
/// Global configuration instances and change listeners mapped to config path.
static ref CONFIG_INSTANCES: Arc<RwLock<ConfigRegistry>> =
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<GlobalWalletConfig, ConfigError> {
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<GlobalWalletConfig, ConfigError> {
{
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<F>(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<PathBuf, ConfigError> {
// 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<GlobalWalletConfig, ConfigError> {
let mut return_value = GlobalWalletConfig::default();
return_value.config_file_path = Some(PathBuf::from(&file_path));
pub fn new(config_file_path: PathBuf) -> Result<GlobalWalletConfig, ConfigError> {
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<GlobalWalletConfig, ConfigError> {
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<GlobalWalletConfigMembers, toml::de::Error> = 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<String, ConfigError> {
let encoded: Result<String, toml::ser::Error> =
toml::to_string(self.members.as_mut().unwrap());
let encoded: Result<String, toml::ser::Error> = 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<String>,
old_version: Option<u32>,
) -> 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<String>,
old_version: Option<u32>,
) -> 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<u32>,
) -> Result<String, ConfigError> {
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::<Vec<_>>();
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::<DocumentMut>()
.map_err(|e| ConfigError::SerializationError(format!("{}", e)))?;
let new = new_config
.parse::<DocumentMut>()
.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::<DocumentMut>()
.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::<u64>()));
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::<DocumentMut>().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");
}
}
+2 -2
View File
@@ -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<PathBuf>,
pub config_file_path: PathBuf,
/// Wallet members
pub members: Option<GlobalWalletConfigMembers>,
pub members: GlobalWalletConfigMembers,
}
/// Wallet internal members
+457 -563
View File
File diff suppressed because it is too large Load Diff
+227 -132
View File
@@ -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<Tor, Error> {
) -> Result<tor_process::TorProcess, Error> {
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<L, F, C, K>(
wallet: Option<Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
api_context: Option<&mut Owner<L, C, K>>,
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<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
config_path: PathBuf,
keychain_mask: Option<SecretKey>,
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<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
owner_api: &mut Owner<L, C, K>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
api_secret: Option<String>,
tls_config: Option<TLSConfig>,
owner_api_include_foreign: Option<bool>,
tor_config: Option<TorConfig>,
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<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
config_path: PathBuf,
bridge: Option<String>,
use_tor: Option<bool>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
tls_config: Option<TLSConfig>,
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<Option<TorProcess>, 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<String>,
use_tor: Option<bool>,
) -> Result<TorConfig, Error> {
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<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
owner_api: &Owner<L, C, K>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
tor_config: Option<TorConfig>,
running_foreign: bool,
) -> OwnerAPIHandlerV3<L, C, K> {
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<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
/// Wallet configuration path.
pub config_path: ConfigPath,
/// Keychain mask
pub keychain_mask: Arc<Mutex<Option<SecretKey>>>,
/// run in doctest mode
pub test_mode: bool,
/// tor config
pub tor_config: Mutex<Option<TorConfig>>,
}
impl<L, C, K> ForeignAPIHandlerV2<L, C, K>
@@ -725,15 +776,15 @@ where
/// Create a new foreign API handler for GET methods
pub fn new(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
config_path: ConfigPath,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
test_mode: bool,
tor_config: Mutex<Option<TorConfig>>,
) -> ForeignAPIHandlerV2<L, C, K> {
ForeignAPIHandlerV2 {
wallet,
config_path,
keychain_mask,
test_mode,
tor_config,
}
}
@@ -756,11 +807,10 @@ where
req: Request<Incoming>,
mask: Option<SecretKey>,
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
config_path: ConfigPath,
test_mode: bool,
tor_config: Option<TorConfig>,
) -> Result<Response<ApiBody>, 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();
}
}
+173 -117
View File
@@ -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);
+38 -22
View File
@@ -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,
+19 -13
View File
@@ -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);
+414 -253
View File
@@ -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<libwallet::OutputData> =
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::<std::collections::HashSet<_>>();
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::<std::collections::HashSet<_>>();
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));
+228
View File
@@ -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<u8>)> {
fn collect(root: &Path, path: &Path, files: &mut Vec<(PathBuf, Vec<u8>)>) {
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);
}
+207 -131
View File
@@ -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);
+229 -153
View File
@@ -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
+71 -45
View File
@@ -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);
+110 -79
View File
@@ -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, &params, &last_output.commit, true)?;
let mwixnet_req = api.create_mwixnet_req(m, &params, &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);
+145 -99
View File
@@ -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);
+77 -61
View File
@@ -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);
+163 -102
View File
@@ -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);
+11 -10
View File
@@ -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);
+71 -45
View File
@@ -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);
+315 -223
View File
@@ -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);
+471 -374
View File
@@ -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);
+97 -66
View File
@@ -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);
+195 -168
View File
@@ -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);
+22 -11
View File
@@ -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
+1
View File
@@ -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"
+18 -24
View File
@@ -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>,
tor_process: Arc<Option<TorProcess>>,
}
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<Tor, Error> {
fn launch_tor_process(config: &TorConfig, tor_dir: &PathBuf) -> Result<TorProcess, Error> {
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)
}
+29 -38
View File
@@ -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<LoggingConfig>,
tor_config: Option<TorConfig>,
) -> 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(),
+137 -92
View File
@@ -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<RunningOnionService>,
proxy: Arc<OnionServiceReverseProxy>,
worker: JoinHandle<()>,
state_lock: PathBuf,
}
lazy_static! {
pub static ref ARTI_RUNTIME: LazyLock<Mutex<Option<ArtiRuntimeWrapper>>> =
/// Arti Tokio runtime.
static ref ARTI_RUNTIME: LazyLock<Mutex<Option<ArtiRuntimeWrapper>>> =
LazyLock::new(|| Mutex::new(ArtiRuntimeWrapper::create().ok()));
pub static ref ARTI_CLIENT_CONFIG: LazyLock<Mutex<Option<(Arc<TorClient<TokioNativeTlsRuntime>>, TorClientConfig)>>> =
/// Arti client and config.
static ref ARTI_CLIENT_CONFIG: LazyLock<Mutex<Option<(Arc<TorClient<TokioNativeTlsRuntime>>, TorClientConfig)>>> =
LazyLock::new(|| Mutex::new(None));
/// Running services, where key is onion address.
static ref ARTI_PROXY_SERVICES: LazyLock<Mutex<HashMap<String, TorService>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
}
/// Arti client runtime wrapper.
#[derive(Clone)]
struct ArtiRuntimeWrapper {
runtime: TokioNativeTlsRuntime,
}
impl ArtiRuntimeWrapper {
fn create() -> Result<ArtiRuntimeWrapper, std::io::Error> {
Ok(Self {
runtime: TokioNativeTlsRuntime::create()?,
})
}
}
/// Get Tor client runtime.
@@ -67,22 +95,51 @@ fn runtime() -> Result<TokioNativeTlsRuntime, Error> {
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<Tor, Error> {
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<S>(
c: Arc<TorClient<TokioNativeTlsRuntime>>,
addr: SocketAddr,
request: &mut S,
hs: HsNickname,
) where
S: futures::Stream<Item = tor_hsservice::RendRequest>
+ 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<Tor, Error> {
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<IN>(
client: Arc<TorClient<TokioNativeTlsRuntime>>,
tor_config: &TorConfig,
input: &IN,
url: &str,
) -> Result<String, Error>
/// Make POST request.
pub fn tor_post<IN>(tor_config: &TorConfig, input: &IN, url: &str) -> Result<String, Error>
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<String, Error> = 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<TorClient<TokioNativeTlsRuntime>>, 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<S>(
client: Arc<TorClient<TokioNativeTlsRuntime>>,
addr: SocketAddr,
request: &mut S,
nickname: HsNickname,
) -> Result<(), Error>
where
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
{
let runtime = client.runtime().clone();
/// Create Onion service proxy.
fn create_service_proxy(addr: SocketAddr) -> Result<Arc<OnionServiceReverseProxy>, 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<S>(
proxy: Arc<OnionServiceReverseProxy>,
request: &mut S,
nickname: HsNickname,
) -> Result<(), Error>
where
S: futures::Stream<Item = tor_hsservice::RendRequest> + 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.
-25
View File
@@ -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<process::TorProcess>,
/// Integrated service.
pub service: Option<std::sync::Arc<tor_hsservice::RunningOnionService>>,
/// Integrated client.
pub client:
Option<std::sync::Arc<arti_client::TorClient<tor_rtcompat::tokio::TokioNativeTlsRuntime>>>,
}
/// Arti client runtime wrapper.
#[derive(Clone)]
pub struct ArtiRuntimeWrapper {
pub runtime: tor_rtcompat::tokio::TokioNativeTlsRuntime,
}
impl ArtiRuntimeWrapper {
fn create() -> Result<ArtiRuntimeWrapper, std::io::Error> {
Ok(Self {
runtime: tor_rtcompat::tokio::TokioNativeTlsRuntime::create()?,
})
}
}
+15 -3
View File
@@ -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;
+6
View File
@@ -326,6 +326,12 @@ pub enum Error {
GenericError(String),
}
impl From<grin_wallet_config::ConfigError> for Error {
fn from(error: grin_wallet_config::ConfigError) -> Error {
Error::GenericError(format!("{}", error))
}
}
impl From<grin_store::Error> for Error {
fn from(error: grin_store::Error) -> Error {
Error::Backend(format!("{}", error))
+19 -27
View File
@@ -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();
+9 -5
View File
@@ -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<StatusMessage>) -> Result<(), Error> {
}
pub fn command_loop<L, C, K>(
config: GlobalWalletConfig,
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
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,
+2 -14
View File
@@ -30,13 +30,8 @@ pub fn wallet_command<C>(
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));
+28 -49
View File
@@ -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<command::ListenArgs, ParseError> {
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<command::ProofVerify
pub fn wallet_command<C, F>(
wallet_args: &ArgMatches,
mut wallet_config: WalletConfig,
tor_config: Option<TorConfig>,
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::<DefaultLCProvider<C, keychain::ExtKeychain>, 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<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<SecretKey>,
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,
+26 -25
View File
@@ -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)?;
+18 -25
View File
@@ -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(&current_dir, &current_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<C, F>(
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<IN>(url: &Url, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
+24 -3
View File
@@ -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::<String>(
@@ -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,