mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-25 09:58:32 +01:00
Accounts balances (#815)
Co-authored-by: wiesche <wiesche89@googlemail.com>
This commit is contained in:
committed by
GitHub
co-authored by
wiesche
parent
452a3ec51b
commit
2d87b8c894
+39
-27
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 _ =
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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 _ =
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user