Accounts balances (#815)

Co-authored-by: wiesche <wiesche89@googlemail.com>
This commit is contained in:
ardocrat
2026-09-28 11:20:01 +03:00
committed by GitHub
co-authored by wiesche
parent 452a3ec51b
commit 2d87b8c894
23 changed files with 525 additions and 153 deletions
+39 -27
View File
@@ -262,7 +262,7 @@ where
}
/// Returns a list of accounts stored in the wallet (i.e. mappings between
/// user-specified labels and BIP32 derivation paths.
/// user-specified labels and BIP32 derivation paths).
/// # Arguments
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
/// being used.
@@ -296,11 +296,22 @@ where
&self,
keychain_mask: Option<&SecretKey>,
) -> Result<Vec<AcctPathMapping>, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
wallet_lock!(self.wallet_inst, w);
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
owner::accounts(w)
owner::accounts(w, None)
}
/// Returns accounts with balances from local wallet data, without refreshing from the node.
/// `minimum_confirmations` is the minimum confirmations required for spendable outputs.
pub fn accounts_info(
&self,
keychain_mask: Option<&SecretKey>,
minimum_confirmations: u64,
) -> Result<Vec<AcctPathMapping>, Error> {
wallet_lock!(self.wallet_inst, w);
let _ = w.keychain(keychain_mask)?;
owner::accounts(w, Some(minimum_confirmations))
}
/// Creates a new 'account', which is a mapping of a user-specified
@@ -310,7 +321,7 @@ where
///
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
/// being used.
/// * `label` - A human readable label to which to map the new BIP32 Path
/// * `label` - A human-readable label to which to map the new BIP32 Path
///
/// # Returns
/// * Result Containing:
@@ -319,10 +330,10 @@ where
///
/// # Remarks
///
/// * Wallets should be initialised with the 'default' path mapped to `m/0/0`
/// * Wallets should be initialized with the 'default' path mapped to `m/0/0`
/// * Each call to this function will increment the first element of the path
/// so the first call will create an account at `m/1/0` and the second at
/// `m/2/0` etc. . .
/// `m/2/0` etc.
/// * The account path is used throughout as the parent key for most key-derivation
/// operations. See [`set_active_account`](struct.Owner.html#method.set_active_account) for
/// further details.
@@ -354,12 +365,13 @@ where
}
/// Sets the wallet's currently active account. This sets the
/// BIP32 parent path used for most key-derivation operations.
/// BIP32 parent path used for most key-derivation operations
/// persistently between wallet restarts.
///
/// # Arguments
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
/// being used.
/// * `label` - The human readable label for the account. Accounts can be retrieved via
/// * `label` - The human-readable label for the account. Accounts can be retrieved via
/// the [`account`](struct.Owner.html#method.accounts) method
///
/// # Returns
@@ -370,7 +382,7 @@ where
/// # Remarks
///
/// * Wallet parent paths are 2 path elements long, e.g. `m/0/0` is the path
/// labelled 'default'. Keys derived from this parent path are 3 elements long,
/// labeled 'default'. Keys derived from this parent path are 3 elements long,
/// e.g. the secret keys derived from the `m/0/0` path will be at paths `m/0/0/0`,
/// `m/0/0/1` etc...
///
@@ -400,7 +412,7 @@ where
let w = w_lock.lc_provider()?.wallet_inst()?;
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
owner::set_active_account(w, label)
owner::set_active_account(w, keychain_mask, label)
}
/// Returns a list of outputs from the active account in the wallet.
@@ -710,9 +722,9 @@ where
/// is confirmed. This method also returns a function that will perform that locking, and it is
/// up to the caller to decide the best time to call the lock function
/// (via the [`tx_lock_outputs`](struct.Owner.html#method.tx_lock_outputs) method).
/// If the exchange method is intended to be synchronous (such as via a direct http call)
/// If the exchange method is intended to be synchronous (such as via a direct http call),
/// then the lock call can wait until the response is confirmed. If it is asynchronous, (such
/// as via file transfer) the lock call should happen immediately (before the file is sent
/// as via file transfer), the lock call should happen immediately (before the file is sent
/// to the recipient).
///
/// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html),
@@ -1221,10 +1233,10 @@ where
/// Cancels a transaction. This entails:
/// * Setting the transaction status to either `TxSentCancelled` or `TxReceivedCancelled`
/// * Deleting all change outputs or recipient outputs associated with the transaction
/// * Setting the status of all assocatied inputs from `Locked` to `Spent` so they can be
/// * Setting the status of all associated inputs from `Locked` to `Spent` so they can be
/// used in new transactions.
///
/// Transactions can be cancelled by transaction log id or slate id (call with either set to
/// Transactions can be canceled by transaction log id or slate id (call with either set to
/// Some, not both)
///
/// # Arguments
@@ -1306,7 +1318,7 @@ where
///
/// # Returns
/// * Ok(Some([Slate](../grin_wallet_libwallet/slate/struct.Slate.html)) containing the stored
/// transaction, if successful. Note that this Slate will not contain all of the fields used by
/// transaction, if successful. Note that this Slate will not contain all the fields used by
/// the original Slate that resulted in the transaction.
/// * Ok(None) if the stored Transaction isn't found.
/// * [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
@@ -1344,7 +1356,7 @@ where
}
/// Return the rewind hash of the wallet.
/// The rewind hash when shared, help third-party to retrieve informations (outputs, balance, ...) that belongs to this wallet.
/// The rewind hash when shared, help third-party to retrieve information (outputs, balance, ...) that belongs to this wallet.
///
/// # Arguments
///
@@ -1379,7 +1391,7 @@ where
/// Scans the entire UTXO set from the node, identify which outputs belong to the given rewind hash view wallet.
///
/// This function can be used to retrieve outputs informations (outputs, balance, ...) from a rewind hash view wallet.
/// This function can be used to retrieve outputs information (outputs, balance, ...) from a rewind hash view wallet.
///
/// This operation scans the entire chain, and is expected to be time intensive. It is imperative
/// that no other processes should be trying to use the wallet at the same time this function is
@@ -1429,7 +1441,7 @@ where
/// update the wallet state to be consistent with what's currently in the UTXO set.
///
/// This function can be used to repair wallet state, particularly by restoring outputs that may
/// be missing if the wallet owner has cancelled transactions locally that were then successfully
/// be missing if the wallet owner has canceled transactions locally that were then successfully
/// posted to the chain.
///
/// This operation scans the entire chain, and is expected to be time intensive. It is imperative
@@ -1571,7 +1583,7 @@ where
/// * None
///
/// # Returns
/// * Ok with a String value representing the full path to the top level wallet dierctory
/// * Ok with a String value representing the full path to the top level wallet directory
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
@@ -1605,7 +1617,7 @@ where
/// 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.
/// Open the wallet again afterward to load its database from the new location.
///
/// # Arguments
///
@@ -1803,7 +1815,7 @@ where
/// `Opens` a wallet, populating the internal keychain with the encrypted seed, and optionally
/// returning a `keychain_mask` token to the caller to provide in all future calls.
/// If using a mask, the seed will be stored in-memory XORed against the `keychain_mask`, and
/// will not be useable if the mask is not provided.
/// will not be usable if the mask is not provided.
///
/// # Arguments
///
@@ -1840,7 +1852,7 @@ where
/// // Create configuration
/// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None, None);
///
/// // create new wallet wirh random seed
/// // create new wallet with random seed
/// let pw = ZeroingString::from("my_password");
/// let _ = api_owner.create_wallet(None, None, 0, pw.clone());
///
@@ -1916,7 +1928,7 @@ where
/// * `password`: The password used to encrypt the seed file.
///
/// # Returns
/// * Ok(BIP-39 mneminc) if successful
/// * Ok(BIP-39 mnemonic) if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
@@ -2409,14 +2421,14 @@ where
}
/// Decode an armored slatepack, returning a Slatepack object that can be
/// viewed, manipulated, output as json, etc. The resulting slatepack will be
/// viewed, manipulated, output as JSON, etc. The resulting slatepack will be
/// decrypted by this wallet if possible
///
/// # Arguments
///
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using
/// * `slatepack` - A string representing an armored slatepack
/// * `secret_indices` - Indices along this wallet's deriviation path with which to attempt
/// * `secret_indices` - Indices along this wallet's derivation path with which to attempt
/// decryption. If this wallet can't decrypt this slatepack, the payload of the returned
/// Slatepack will remain encrypted.
///
@@ -2601,7 +2613,7 @@ where
// MWIXNET
/// Creates an mwixnet request [SwapReq](../grin_wallet_libwallet/api_impl/types/struct.SwapReq.html)
/// Creates a mwixnet request [SwapReq](../grin_wallet_libwallet/api_impl/types/struct.SwapReq.html)
/// from a given output commitment under this wallet's control.
///
/// # Arguments
+67 -2
View File
@@ -48,7 +48,7 @@ use std::time::Duration;
#[easy_jsonrpc_mw::rpc]
pub trait OwnerRpc {
/**
Networked version of [Owner::accounts](struct.Owner.html#method.accounts).
Networked version of [Owner::accounts](struct.Owner.html#method.accounts). Returns account mappings without balances.
# Json rpc example
@@ -72,7 +72,9 @@ pub trait OwnerRpc {
"Ok": [
{
"label": "default",
"path": "0200000000000000000000000000000000"
"path": "0200000000000000000000000000000000",
"info": null,
"current": true
}
]
},
@@ -84,6 +86,60 @@ pub trait OwnerRpc {
*/
fn accounts(&self, token: Token) -> Result<Vec<AcctPathMapping>, Error>;
/**
Networked version of [Owner::accounts_info](struct.Owner.html#method.accounts_info). Returns balances from locally stored state without refreshing from the node.
# Json rpc example
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "accounts_info",
"params": {
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
"minimum_confirmations": 1
},
"id": 1
}
# "#
# ,
# r#"
{
"jsonrpc": "2.0",
"result": {
"Ok": [
{
"label": "default",
"path": "0200000000000000000000000000000000",
"info": {
"amount_awaiting_confirmation": "0",
"amount_awaiting_finalization": "0",
"amount_currently_spendable": "60000000000",
"amount_immature": "180000000000",
"amount_locked": "0",
"amount_reverted": "0",
"last_confirmed_height": "4",
"minimum_confirmations": "1",
"total": "240000000000"
},
"current": true
}
]
},
"id": 1
}
# "#
# , 4, false, false, false, false);
```
*/
fn accounts_info(
&self,
token: Token,
minimum_confirmations: u64,
) -> Result<Vec<AcctPathMapping>, Error>;
/**
Networked version of [Owner::create_account_path](struct.Owner.html#method.create_account_path).
@@ -119,6 +175,7 @@ pub trait OwnerRpc {
/**
Networked version of [Owner::set_active_account](struct.Owner.html#method.set_active_account).
The account selection is saved across wallet restarts
# Json rpc example
@@ -2089,6 +2146,14 @@ where
Owner::accounts(self, (&token.keychain_mask).as_ref())
}
fn accounts_info(
&self,
token: Token,
minimum_confirmations: u64,
) -> Result<Vec<AcctPathMapping>, Error> {
Owner::accounts_info(self, (&token.keychain_mask).as_ref(), minimum_confirmations)
}
fn create_account_path(&self, token: Token, label: &String) -> Result<Identifier, Error> {
Owner::create_account_path(self, (&token.keychain_mask).as_ref(), label)
}
+51 -25
View File
@@ -31,6 +31,7 @@ use crate::util::secp::key::SecretKey;
use crate::util::{Mutex, ZeroingString};
use crate::{controller, display};
use grin_wallet_libwallet::wallet_lock;
use qr_code::QrCode;
use serde_json as json;
use std::fs::File;
@@ -52,7 +53,7 @@ fn show_recovery_phrase(phrase: ZeroingString) {
/// Arguments common to all wallet commands
#[derive(Clone)]
pub struct GlobalArgs {
pub account: String,
pub account: Option<String>,
pub api_secret: Option<String>,
pub node_api_secret: Option<String>,
pub show_spent: bool,
@@ -263,6 +264,8 @@ where
/// Arguments for account command
pub struct AccountArgs {
pub create: Option<String>,
pub minimum_confirmations: u64,
pub active: Option<String>,
}
pub fn account<L, C, K>(
@@ -275,20 +278,7 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
if args.create.is_none() {
let res = {
let acct_mappings = owner_api.accounts(keychain_mask)?;
// give logging thread a moment to catch up
thread::sleep(Duration::from_millis(200));
display::accounts(acct_mappings);
Ok(())
};
if let Err(e) = res {
error!("Error listing accounts: {}", e);
return Err(Error::LibWallet(e));
}
} else {
let label = args.create.unwrap();
if let Some(label) = args.create {
let res = {
owner_api.create_account_path(keychain_mask, &label)?;
thread::sleep(Duration::from_millis(200));
@@ -300,6 +290,31 @@ where
error!("Error creating account '{}': {}", label, e);
return Err(Error::LibWallet(e));
}
} else if let Some(label) = args.active {
let res = {
owner_api.set_active_account(keychain_mask, &label)?;
thread::sleep(Duration::from_millis(200));
info!("Account: '{}' set as active!", label);
Ok(())
};
if let Err(e) = res {
thread::sleep(Duration::from_millis(200));
error!("Error setting account '{}' as active: {}", label, e);
return Err(Error::LibWallet(e));
}
} else {
let res = {
let acct_mappings =
owner_api.accounts_info(keychain_mask, args.minimum_confirmations)?;
// give logging thread a moment to catch up
thread::sleep(Duration::from_millis(200));
display::accounts(acct_mappings);
Ok(())
};
if let Err(e) = res {
error!("Error listing accounts: {}", e);
return Err(Error::LibWallet(e));
}
}
Ok(())
}
@@ -702,7 +717,6 @@ pub struct ReceiveArgs {
pub fn receive<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: ReceiveArgs,
mut tor_config: TorConfig,
test_mode: bool,
@@ -752,7 +766,7 @@ where
owner_api.config_path(),
km,
|api| {
slate = api.receive_tx(&slate, Some(&g_args.account), None)?;
slate = api.receive_tx(&slate, None, None)?;
Ok(())
},
)?;
@@ -1174,7 +1188,6 @@ pub struct InfoArgs {
pub fn info<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: InfoArgs,
dark_scheme: bool,
) -> Result<(), Error>
@@ -1186,8 +1199,9 @@ where
let updater_running = owner_api.updater_running.load(Ordering::Relaxed);
let (validated, wallet_info) =
owner_api.retrieve_summary_info(keychain_mask, true, args.minimum_confirmations)?;
let account = account_label(owner_api)?;
display::info(
&g_args.account,
&account,
&wallet_info,
validated || updater_running,
dark_scheme,
@@ -1210,8 +1224,9 @@ where
let res = owner_api.node_height(keychain_mask)?;
let (validated, outputs) =
owner_api.retrieve_outputs(keychain_mask, g_args.show_spent, true, None)?;
let account = account_label(owner_api)?;
display::outputs(
&g_args.account,
&account,
res.height,
validated || updater_running,
outputs,
@@ -1230,7 +1245,6 @@ pub struct TxsArgs {
pub fn txs<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: TxsArgs,
dark_scheme: bool,
) -> Result<(), Error>
@@ -1249,8 +1263,9 @@ where
let first_tx = args
.count
.map_or(0, |c| txs.len().saturating_sub(c as usize));
let account = account_label(owner_api)?;
display::txs(
&g_args.account,
&account,
res.height,
validated || updater_running,
&txs[first_tx..],
@@ -1276,7 +1291,7 @@ where
if id.is_some() {
let (_, outputs) = owner_api.retrieve_outputs(keychain_mask, true, false, id)?;
display::outputs(
&g_args.account,
&account,
res.height,
validated || updater_running,
outputs,
@@ -1459,7 +1474,6 @@ where
/// Payment Proof Address
pub fn address<L, C, K>(
owner_api: &mut Owner<L, C, K>,
g_args: &GlobalArgs,
keychain_mask: Option<&SecretKey>,
) -> Result<(), Error>
where
@@ -1469,14 +1483,26 @@ where
{
// Just address at derivation index 0 for now
let address = owner_api.get_slatepack_address(keychain_mask, 0)?;
let account = account_label(owner_api)?;
println!();
println!("Address for account - {}", g_args.account);
println!("Address for account - {}", account);
println!("-------------------------------------");
println!("{}", address);
println!();
Ok(())
}
/// Get current account label.
fn account_label<L, C, K>(owner_api: &mut Owner<L, C, K>) -> Result<String, Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
wallet_lock!(owner_api.wallet_inst, w);
Ok(w.active_account().label)
}
/// Proof Export Args
pub struct ProofExportArgs {
pub output_file: String,
+8
View File
@@ -540,13 +540,20 @@ pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
mMG->"Name",
bMG->"Parent Output Key",
bMG->"Slatepack Address (Index 0)",
bMG->"Spendable balance",
]);
for m in acct_mappings {
let slatepack_path = address::address_derivation_path(&m.path, 0).to_bip_32_string();
let spendable = if let Some(info) = m.info {
amount_to_hr_string(info.amount_currently_spendable, true)
} else {
"-".to_string()
};
table.add_row(row![
bFC->m.label,
bGC->m.path.to_bip_32_string(),
bGC->slatepack_path,
bGC->spendable,
]);
}
table.set_format(
@@ -562,6 +569,7 @@ pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
let width = table.to_string().find('\n').unwrap_or(0);
println!("{:^1$}", "BIP-32 Derivation Path", width);
table.printstd();
println!("Balances use local data and may be out of date.");
println!();
}
+74 -10
View File
@@ -81,7 +81,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// test default accounts exist
{
let accounts = api1.accounts(mask1)?;
let accounts = api1.accounts_info(mask1, 2)?;
assert_eq!(accounts[0].label, "default");
assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0));
}
@@ -97,6 +97,53 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// trying to add same label again should fail
let res = api1.create_account_path(mask1, "account1");
assert!(res.is_err());
// Check missing flags and temporary account changes
for saved in [None, Some(true)] {
{
wallet_inst!(wallet1, w);
let mut account = w.get_acct_path("default".to_owned())?.unwrap();
account.current = saved;
let mut batch = w.batch(mask1)?;
batch.save_acct_path(account)?;
batch.commit()?;
}
for active in ["default", "account1"] {
{
wallet_inst!(wallet1, w);
w.set_account_by_name(active)?;
}
for accounts in [api1.accounts(mask1)?, api1.accounts_info(mask1, 2)?] {
assert_eq!(accounts.len(), 4);
assert_eq!(accounts[0].label, active);
for account in accounts {
assert_eq!(account.current, Some(account.label == active));
}
}
// Keep the saved selection unchanged
wallet_inst!(wallet1, w);
for account in w.acct_path_iter()? {
let expected = if account.label == "default" {
saved
} else {
None
};
assert_eq!(account.current, expected);
}
}
}
// Restore the saved account on reopen
api1.set_active_account(mask1, "account1")?;
{
wallet_inst!(wallet1, w);
w.set_account_by_name("default")?;
assert_eq!(w.active_account().label, "default");
}
api1.close_wallet(None)?;
api1.open_wallet(None, "".into(), false)?;
{
wallet_inst!(wallet1, w);
assert_eq!(w.active_account().label, "account1");
}
}
// add account to wallet 2
@@ -108,20 +155,20 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Default wallet 2 to listen on that account
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("listener_account")?;
w.set_account_by_name("listener_account")?;
}
// Mine into two different accounts in the same wallet
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
}
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 7, false);
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account2")?;
w.set_account_by_name("account2")?;
assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 2, 0, 0, 0));
}
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
@@ -141,12 +188,28 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// now check second account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
}
{
let labels: Vec<_> = api1.accounts(mask1)?.into_iter().map(|a| a.label).collect();
let labels: Vec<_> = api1
.accounts_info(mask1, 2)?
.into_iter()
.map(|a| a.label)
.collect();
assert_eq!(labels, ["account1", "default", "account2", "account3"]);
assert!(api1.accounts(mask1)?.iter().all(|a| a.info.is_none()));
for confirmations in [0, 1, 2, 10] {
let accounts = api1.accounts_info(mask1, confirmations)?;
assert_eq!(accounts[0].info.as_ref().unwrap().last_confirmed_height, 0);
assert_eq!(accounts[2].info.as_ref().unwrap().last_confirmed_height, 12);
for account in accounts {
api1.set_active_account(mask1, &account.label)?;
let (_, summary) = api1.retrieve_summary_info(mask1, false, confirmations)?;
assert_eq!(account.info.unwrap(), summary);
}
api1.set_active_account(mask1, "account1")?;
}
// check last confirmed height on this account is different from above (should be 0)
let (_, wallet1_info) = api1.retrieve_summary_info(mask1, false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 0);
@@ -159,10 +222,11 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?;
assert_eq!(txs.len(), 7);
}
// should be nothing in default account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("default")?;
w.set_account_by_name("default")?;
}
{
let (_, wallet1_info) = api1.retrieve_summary_info(mask1, false, 1)?;
@@ -180,7 +244,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Send a tx to another wallet
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
}
{
let args = InitTxArgs {
@@ -218,7 +282,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// other account should be untouched
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account2")?;
w.set_account_by_name("account2")?;
}
{
let (_, wallet1_info) = api1.retrieve_summary_info(mask1, false, 1)?;
@@ -241,7 +305,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Default account on wallet 2 should be untouched
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("default")?;
w.set_account_by_name("default")?;
}
{
let (_, wallet2_info) = api2.retrieve_summary_info(mask2, false, 1)?;
+2 -2
View File
@@ -88,7 +88,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(),
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
@@ -130,7 +130,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(),
// Get some mining done
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
}
let mut slate = PathToSlate((&send_file).into()).get_tx()?.0;
+1 -1
View File
@@ -80,7 +80,7 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let mut _bh = 10u64;
let _ =
+2 -2
View File
@@ -77,11 +77,11 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
}
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false)?;
+2 -2
View File
@@ -72,7 +72,7 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
@@ -121,7 +121,7 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Check total in 'listener' account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
w.set_account_by_name("listener")?;
}
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
assert!(wallet1_refreshed);
+6 -6
View File
@@ -86,7 +86,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
@@ -120,7 +120,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
// wallet 1 receives file to different account, completes
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
w.set_account_by_name("listener")?;
}
wallet::controller::foreign_single_use(
@@ -138,7 +138,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
// wallet 1 receives file to different account, completes
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
// wallet 1 finalize
@@ -164,7 +164,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
w.set_account_by_name("listener")?;
}
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
@@ -175,11 +175,11 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
// as above, but synchronously
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
}
let amount = 60_000_000_000;
+2 -2
View File
@@ -70,7 +70,7 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
@@ -119,7 +119,7 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Check total in 'listener' account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
w.set_account_by_name("listener")?;
}
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
assert!(wallet1_refreshed);
+2 -2
View File
@@ -137,7 +137,7 @@ fn slatepack_exchange_test_impl(
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
@@ -213,7 +213,7 @@ fn slatepack_exchange_test_impl(
// Get some mining done
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
w.set_account_by_name("account1")?;
}
let (mut slatepack, mut slate) =
+1 -1
View File
@@ -80,7 +80,7 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
w.set_account_by_name("mining")?;
}
let bh = 10u64;
let _ =
+22 -7
View File
@@ -52,19 +52,30 @@ use std::sync::mpsc::Sender;
use std::sync::Arc;
/// List of accounts
pub fn accounts<C, K>(w: &mut WalletBackend<C, K>) -> Result<Vec<AcctPathMapping>, Error>
pub fn accounts<C, K>(
w: &mut WalletBackend<C, K>,
minimum_confirmations: Option<u64>,
) -> Result<Vec<AcctPathMapping>, Error>
where
C: NodeClient,
K: Keychain,
{
let mut accounts = keys::accounts(w)?;
let mut accounts: Vec<AcctPathMapping> = if let Some(mc) = minimum_confirmations {
updater::retrieve_accounts_info(w, mc)?
} else {
keys::accounts(w)?
};
let active = w.parent_key_id();
for account in accounts.iter_mut() {
account.current = Some(account.path == active);
}
accounts.sort_by(|a, b| a.path.cmp(&b.path));
// Put active account on top.
accounts.sort_by_key(|k| k.path != w.parent_key_id());
accounts.sort_by_key(|k| k.path != active);
Ok(accounts)
}
/// new account path
/// New account path
pub fn create_account_path<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
@@ -77,13 +88,17 @@ where
keys::new_acct_path(w, keychain_mask, label)
}
/// set active account
pub fn set_active_account<C, K>(w: &mut WalletBackend<C, K>, label: &str) -> Result<(), Error>
/// Set active account
pub fn set_active_account<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
label: &str,
) -> Result<(), Error>
where
C: NodeClient,
K: Keychain,
{
w.set_parent_key_id_by_name(label)
w.set_active_account(keychain_mask, label)
}
/// Hash of the wallet root public key
+71 -24
View File
@@ -108,8 +108,8 @@ where
pub keychain: Option<K>,
/// Check value for XORed keychain seed
pub master_checksum: Box<Option<Blake2bResult>>,
/// Parent path to use by default for output operations
parent_key_id: Identifier,
/// BIP32 account path to use by default for output operations
active_account: AcctPathMapping,
/// wallet to node client
w2n_client: C,
}
@@ -138,30 +138,48 @@ where
// Make sure default wallet derivation path always exists
// as well as path (so it can be retrieved by batches to know where to store
// completed transactions, for reference
// completed transactions, for reference)
let default_account = AcctPathMapping {
label: "default".to_owned(),
label: "default".to_string(),
path: WalletBackend::<C, K>::default_path(),
info: None,
current: Some(true),
};
{
let mut batch = store.batch()?;
batch.put_ser(
let saved_default = batch.get_ser::<AcctPathMapping>(
Some(ACCOUNT_PATH_MAPPING_PREFIX),
default_account.label.as_bytes(),
&default_account,
None,
)?;
batch.commit()?;
if saved_default.is_none() {
batch.put_ser(
Some(ACCOUNT_PATH_MAPPING_PREFIX),
default_account.label.as_bytes(),
&default_account,
)?;
batch.commit()?;
}
}
let res = WalletBackend {
let mut res = WalletBackend {
db: store,
data_file_dir: data_file_dir.to_owned(),
keychain: None,
master_checksum: Box::new(None),
parent_key_id: WalletBackend::<C, K>::default_path(),
active_account: default_account,
w2n_client: n_client,
};
// Set current active account
let active = res
.acct_path_iter()?
.filter(|a| a.current.unwrap_or(false))
.collect::<Vec<AcctPathMapping>>();
if let Some(a) = active.get(0) {
res.active_account = a.clone();
}
Ok(res)
}
@@ -275,27 +293,55 @@ where
/*}*/
}
/// Set parent key id by stored account name.
pub fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> {
/// Set current active account by stored account name.
pub fn set_active_account(
&mut self,
keychain_mask: Option<&SecretKey>,
label: &str,
) -> Result<(), Error> {
let res = self.acct_path_iter()?.find(|l| l.label == label);
match res {
None => Err(Error::UnknownAccountLabel(label.to_string())),
Some(mut a) => {
let mut batch = self.batch(keychain_mask)?;
let current = batch
.acct_path_iter()?
.filter(|a| a.current.unwrap_or(false))
.collect::<Vec<AcctPathMapping>>();
for mut a in current {
a.current = None;
batch.save_acct_path(a.clone())?;
}
a.current = Some(true);
batch.save_acct_path(a.clone())?;
batch.commit()?;
self.active_account = a;
Ok(())
}
}
}
/// Set account by stored account name.
pub fn set_account_by_name(&mut self, label: &str) -> Result<(), Error> {
let label = label.to_owned();
let res = self.acct_path_iter()?.find(|l| l.label == label);
if let Some(a) = res {
self.set_parent_key_id(a.path);
self.active_account = a;
Ok(())
} else {
Err(Error::UnknownAccountLabel(label))
}
}
/// The BIP32 path of the parent path to use for all output-related
/// functions, essentially 'accounts' within a wallet.
pub fn set_parent_key_id(&mut self, id: Identifier) {
self.parent_key_id = id;
/// Get active account.
pub fn active_account(&self) -> AcctPathMapping {
self.active_account.clone()
}
/// Get the parent path.
pub fn parent_key_id(&mut self) -> Identifier {
self.parent_key_id.clone()
self.active_account.path.clone()
}
/// Get output data by id.
@@ -465,14 +511,14 @@ where
/// Next child ID when we want to create a new output, based on current parent.
pub fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error> {
let parent_key_id = self.parent_key_id.clone();
let parent_key_id = self.active_account.path.clone();
let mut deriv_idx = {
let batch = self.db.batch()?;
batch
.get_ser(Some(DERIV_PREFIX), &self.parent_key_id.to_bytes(), None)?
.get_ser(Some(DERIV_PREFIX), &parent_key_id.to_bytes(), None)?
.unwrap_or_else(|| 0)
};
let mut return_path = self.parent_key_id.to_path();
let mut return_path = parent_key_id.to_path();
return_path.depth += 1;
return_path.path[return_path.depth as usize - 1] = ChildNumber::from(deriv_idx);
deriv_idx += 1;
@@ -484,8 +530,8 @@ where
/// Last verified height of outputs directly descending from the current parent key.
pub fn last_confirmed_height(&mut self) -> Result<u64, Error> {
let parent_key_id = self.parent_key_id.clone();
self.last_confirmed_height_for_parent(&parent_key_id)
let parent_key_id = self.active_account.clone();
self.last_confirmed_height_for_parent(&parent_key_id.path)
}
/// Last verified height of outputs directly descending from the given parent key.
@@ -693,7 +739,8 @@ where
}
/// Save an account label -> path mapping.
pub fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error> {
pub fn save_acct_path(&mut self, mut mapping: AcctPathMapping) -> Result<(), Error> {
mapping.info = None;
self.db.put_ser(
Some(ACCOUNT_PATH_MAPPING_PREFIX),
mapping.label.as_bytes(),
+5 -1
View File
@@ -68,7 +68,7 @@ where
K: Keychain,
{
let label = label.to_owned();
if wallet.acct_path_iter()?.any(|l| l.label == label) {
if wallet.acct_path_iter()?.any(|a| a.label == label) {
return Err(Error::AccountLabelAlreadyExists(label));
}
@@ -93,6 +93,8 @@ where
let save_path = AcctPathMapping {
label,
path: return_id.clone(),
info: None,
current: None,
};
let mut batch = wallet.batch(keychain_mask)?;
@@ -116,6 +118,8 @@ where
let save_path = AcctPathMapping {
label,
path: path.clone(),
info: None,
current: None,
};
let mut batch = wallet.batch(keychain_mask)?;
+47 -16
View File
@@ -25,12 +25,12 @@ use crate::grin_core::global;
use crate::grin_core::libtx::proof::ProofBuilder;
use crate::grin_core::libtx::reward;
use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType};
use crate::grin_util as util;
use crate::grin_util::secp::key::SecretKey;
use crate::grin_util::secp::pedersen;
use crate::grin_util::static_secp_instance;
use crate::internal::keys;
use crate::types::{NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, WalletInfo};
use crate::{grin_util as util, AcctPathMapping};
use crate::{
BlockFees, CbData, OutputCommitMapping, RetrieveTxQueryArgs, RetrieveTxQuerySortField,
RetrieveTxQuerySortOrder, WalletBackend,
@@ -793,22 +793,12 @@ where
Ok(())
}
/// Retrieve summary info about the wallet
/// caller should refresh first if desired
pub fn retrieve_info<C, K>(
wallet: &mut WalletBackend<C, K>,
parent_key_id: &Identifier,
/// Return summary info about the wallet account from provided outputs.
fn account_outputs_info<T: std::borrow::Borrow<OutputData>>(
outputs: impl IntoIterator<Item = T>,
current_height: u64,
minimum_confirmations: u64,
) -> Result<WalletInfo, Error>
where
C: NodeClient,
K: Keychain,
{
let current_height = wallet.last_confirmed_height_for_parent(parent_key_id)?;
let outputs = wallet
.iter()?
.filter(|out| out.root_key_id == *parent_key_id);
) -> Result<WalletInfo, Error> {
let mut unspent_total = 0;
let mut immature_total = 0;
let mut awaiting_finalization_total = 0;
@@ -817,6 +807,7 @@ where
let mut reverted_total = 0;
for out in outputs {
let out = out.borrow();
match out.status {
OutputStatus::Unspent => {
if out.is_coinbase && out.lock_height > current_height {
@@ -859,6 +850,46 @@ where
})
}
/// Retrieve summary info about the wallet accounts
/// caller should refresh first if desired
pub fn retrieve_accounts_info<C, K>(
wallet: &mut WalletBackend<C, K>,
minimum_confirmations: u64,
) -> Result<Vec<AcctPathMapping>, Error>
where
C: NodeClient,
K: Keychain,
{
let outputs: Vec<OutputData> = wallet.iter()?.collect();
let mut accounts = keys::accounts(wallet)?;
for a in accounts.iter_mut() {
let os = outputs.iter().filter(|out| out.root_key_id == a.path);
let current_height = wallet.last_confirmed_height_for_parent(&a.path)?;
let info = account_outputs_info(os, current_height, minimum_confirmations)?;
a.info = Some(info);
}
Ok(accounts)
}
/// Retrieve summary info about the wallet
/// caller should refresh first if desired
pub fn retrieve_info<C, K>(
wallet: &mut WalletBackend<C, K>,
parent_key_id: &Identifier,
minimum_confirmations: u64,
) -> Result<WalletInfo, Error>
where
C: NodeClient,
K: Keychain,
{
let outputs = wallet
.iter()?
.filter(|out| out.root_key_id == *parent_key_id);
let current_height = wallet.last_confirmed_height_for_parent(&parent_key_id)?;
let info = account_outputs_info(outputs, current_height, minimum_confirmations)?;
Ok(info)
}
/// Build a coinbase output and insert into wallet
pub fn build_coinbase<C, K>(
wallet: &mut WalletBackend<C, K>,
+6 -2
View File
@@ -768,10 +768,14 @@ impl ser::Readable for StoredProofInfo {
/// Map of named accounts to BIP32 paths
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AcctPathMapping {
/// label used by user
/// Label used by user.
pub label: String,
/// Corresponding parent BIP32 derivation path
/// Corresponding parent BIP32 derivation path.
pub path: Identifier,
/// Wallet information for this account, can be loaded after deserialization.
pub info: Option<WalletInfo>,
/// Flag to check if this is current active account.
pub current: Option<bool>,
}
impl ser::Writeable for AcctPathMapping {
+12 -2
View File
@@ -21,7 +21,6 @@ args:
short: a
long: account
takes_value: true
default_value: default
- top_level_dir:
help: Top directory in which wallet files are stored (location of 'grin-wallet.toml')
short: t
@@ -41,13 +40,24 @@ subcommands:
- cli:
about: Start the wallet in interactive CLI mode (EXPERIMENTAL and UNDER DEVELOPMENT)
- account:
about: List wallet accounts or create a new account
about: List accounts, create an account or select the active account
args:
- create:
help: Create a new wallet account with provided name
short: c
long: create
takes_value: true
- minimum_confirmations:
help: Minimum number of confirmations required for an output to be spendable
short: m
long: min_conf
default_value: "10"
takes_value: true
- active:
help: Save the active account, used whenever --account is not given
short: a
long: active
takes_value: true
- rewind_hash:
about: Return the hash of the wallet root public key.
- scan_rewind_hash:
+7 -4
View File
@@ -186,7 +186,7 @@ where
keychain_mask = match args.subcommand() {
("open", Some(_)) => {
let mut wallet_lock = owner_api.wallet_inst.lock();
let lc = wallet_lock.lc_provider().unwrap();
let lc = wallet_lock.lc_provider()?;
let mask = match lc.open_wallet(
None,
wallet_args::prompt_password(&global_wallet_args.password)?,
@@ -202,17 +202,20 @@ where
None
}
};
if let Some(account) = args.value_of("account") {
if let Some(account) = args
.value_of("account")
.or(global_wallet_args.account.as_deref())
{
if wallet_opened {
let wallet_inst = lc.wallet_inst()?;
wallet_inst.set_parent_key_id_by_name(account)?;
wallet_inst.set_account_by_name(account)?;
}
}
mask
}
("close", Some(_)) => {
let mut wallet_lock = owner_api.wallet_inst.lock();
let lc = wallet_lock.lc_provider().unwrap();
let lc = wallet_lock.lc_provider()?;
lc.close_wallet(None)?;
None
}
+29 -8
View File
@@ -308,7 +308,11 @@ pub fn parse_global_args(
config: &WalletConfig,
args: &ArgMatches,
) -> Result<command::GlobalArgs, ParseError> {
let account = parse_required(args, "account")?;
let account = if args.subcommand_matches("account").is_some() {
None
} else {
parse_optional(args, "account")?
};
let mut show_spent = false;
if args.is_present("show_spent") {
show_spent = true;
@@ -335,7 +339,7 @@ pub fn parse_global_args(
};
Ok(command::GlobalArgs {
account: account.to_owned(),
account,
show_spent,
api_secret,
node_api_secret,
@@ -431,7 +435,26 @@ pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountA
None => None,
Some(s) => Some(s.to_owned()),
};
Ok(command::AccountArgs { create })
let active = match account_args.value_of("active") {
None => None,
Some(s) => Some(s.to_owned()),
};
if create.is_some() && active.is_some() {
let msg = "create and active cannot both be present".to_string();
return Err(ParseError::ArgumentError(msg));
}
// minimum_confirmations
let min_c = parse_required(account_args, "minimum_confirmations")?;
let min_c = parse_u64(min_c, "minimum_confirmations")?;
Ok(command::AccountArgs {
create,
minimum_confirmations: min_c,
active,
})
}
pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseError> {
@@ -1143,7 +1166,7 @@ where
)?;
if let Some(account) = wallet_args.value_of("account") {
let wallet_inst = lc.wallet_inst()?;
wallet_inst.set_parent_key_id_by_name(account)?;
wallet_inst.set_account_by_name(account)?;
}
Ok(mask)
}
@@ -1252,7 +1275,7 @@ where
}
("receive", Some(args)) => {
let a = arg_parse!(parse_receive_args(&args));
command::receive(owner_api, km, &global_wallet_args, a, tor_config, test_mode)
command::receive(owner_api, km, a, tor_config, test_mode)
}
("unpack", Some(args)) => {
let a = arg_parse!(parse_unpack_args(&args));
@@ -1288,7 +1311,6 @@ where
command::info(
owner_api,
km,
global_wallet_args,
a,
wallet_config.dark_background_color_scheme.unwrap_or(true),
)
@@ -1304,7 +1326,6 @@ where
command::txs(
owner_api,
km,
&global_wallet_args,
a,
wallet_config.dark_background_color_scheme.unwrap_or(true),
)
@@ -1329,7 +1350,7 @@ where
let a = arg_parse!(parse_verify_proof_args(&args));
command::proof_verify(owner_api, km, a)
}
("address", Some(_)) => command::address(owner_api, &global_wallet_args, km),
("address", Some(_)) => command::address(owner_api, km),
("scan", Some(args)) => {
let a = arg_parse!(parse_check_args(&args));
command::scan(owner_api, km, a)
+65 -1
View File
@@ -21,6 +21,8 @@ extern crate log;
extern crate grin_wallet;
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
use std::io::Write;
use std::process::{Command, Stdio};
use clap::App;
use std::sync::mpsc;
@@ -153,6 +155,36 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::
];
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
// Check account selection through the CLI loop
let mut cli = Command::new(env!("CARGO_BIN_EXE_grin-wallet"))
.args([
"-t",
&format!("{}/wallet2", test_dir),
"-p",
"password2",
"-a",
"account_1",
"-r",
"http://127.0.0.1:1",
"cli",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
cli.stdin.take().unwrap().write_all(
b"open\naddress\nclose\n-a default open\naddress\naccount -a account_1\naddress\nexit\n",
).unwrap();
let output = cli.wait_with_output().unwrap();
assert!(output.status.success());
let output = String::from_utf8(output.stdout).unwrap();
let accounts: Vec<_> = output
.lines()
.filter_map(|line| line.strip_prefix("Address for account - "))
.collect();
assert_eq!(accounts, ["account_1", "default", "account_1"]);
// let's see those accounts
let arg_vec = vec!["grin-wallet", "-p", "password1", "account"];
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
@@ -213,7 +245,35 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::
"-i",
&file_name,
];
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
// Reuse startup args as in interactive mode
{
let start = app
.clone()
.get_matches_from(vec!["grin-wallet", "-a", "default", "cli"]);
let global_args =
grin_wallet::cmd::wallet_args::parse_global_args(&wallet_config2, &start).unwrap();
let mut api = Owner::new(wallet2.clone(), None, config2.config_file_path.clone());
for command in [
vec!["grin-wallet", "account", "-a", "account_1"],
vec!["grin-wallet", "address"],
vec!["grin-wallet", "receive", "-i", &file_name],
] {
let args = app.clone().get_matches_from(command);
grin_wallet::cmd::wallet_args::parse_and_execute(
&mut api,
mask2_i.clone(),
&wallet_config2,
config2.tor_config(),
&global_args,
&args,
true,
true,
)?;
}
let (_, txs) = api.retrieve_txs(mask2_i.as_ref(), false, None, None, None)?;
assert_eq!(txs.len(), 1);
api.set_active_account(mask2_i.as_ref(), "default")?;
}
// shouldn't be allowed to receive twice
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
@@ -527,6 +587,10 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::
];
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
// set default account for wallet2
let arg_vec = vec!["grin-wallet", "-p", "password2", "account", "-a", "default"];
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
// issue an invoice tx, wallet 2
let arg_vec = vec!["grin-wallet", "-p", "password2", "invoice", "65"];
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
+4 -6
View File
@@ -241,7 +241,7 @@ pub fn instantiate_wallet(
grin_wallet_controller::Error,
> {
wallet_config.chain_type = None;
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client).unwrap())
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client)?)
as Box<
dyn WalletInst<
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
@@ -249,7 +249,7 @@ pub fn instantiate_wallet(
ExtKeychain,
>,
>;
let lc = wallet.lc_provider().unwrap();
let lc = wallet.lc_provider()?;
// legacy hack to avoid the need for changes in existing grin-wallet.toml files
// remove `wallet_data` from end of path as
// new lifecycle provider assumes grin_wallet.toml is in root of data directory
@@ -259,11 +259,9 @@ pub fn instantiate_wallet(
wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into();
}
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
let keychain_mask = lc
.open_wallet(None, ZeroingString::from(passphrase), true, false)
.unwrap();
let keychain_mask = lc.open_wallet(None, ZeroingString::from(passphrase), true, false)?;
let wallet_inst = lc.wallet_inst()?;
wallet_inst.set_parent_key_id_by_name(account)?;
wallet_inst.set_account_by_name(account)?;
Ok((Arc::new(Mutex::new(wallet)), keychain_mask))
}