Change check_repair + certain functions to lock more granularly ()

* Rename check-repair -> scan, make lock logic more granular

* rustfmt

* update owner api implementations where to allow granular locking where required

* rustfmt

* store init state on startup to determine whether full utxo scan is required for new wallets

* rustfmt

* fix for init status persist

* add start height argument to scan

* rustfmt
This commit is contained in:
Yeastplume 2019-11-06 10:04:42 +00:00 committed by GitHub
parent c518f35c8d
commit 021c34bf89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 415 additions and 932 deletions

View file

@ -691,10 +691,9 @@ pub fn run_doctest_foreign(
false,
);
//update local outputs after each block, so transaction IDs stay consistent
let mut w_lock = wallet1.lock();
let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap();
let (wallet_refreshed, _) =
api_impl::owner::retrieve_summary_info(&mut **w, (&mask1).as_ref(), true, 1).unwrap();
api_impl::owner::retrieve_summary_info(wallet1.clone(), (&mask1).as_ref(), true, 1)
.unwrap();
assert!(wallet_refreshed);
}

View file

@ -342,10 +342,8 @@ where
refresh_from_node: bool,
tx_id: Option<u32>,
) -> Result<(bool, Vec<OutputCommitMapping>), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::retrieve_outputs(
&mut **w,
self.wallet_inst.clone(),
keychain_mask,
include_spent,
refresh_from_node,
@ -402,10 +400,8 @@ where
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
) -> Result<(bool, Vec<TxLogEntry>), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let mut res = owner::retrieve_txs(
&mut **w,
self.wallet_inst.clone(),
keychain_mask,
refresh_from_node,
tx_id,
@ -468,10 +464,8 @@ where
refresh_from_node: bool,
minimum_confirmations: u64,
) -> Result<(bool, WalletInfo), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::retrieve_summary_info(
&mut **w,
self.wallet_inst.clone(),
keychain_mask,
refresh_from_node,
minimum_confirmations,
@ -970,9 +964,7 @@ where
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::cancel_tx(&mut **w, keychain_mask, tx_id, tx_slate_id)
owner::cancel_tx(self.wallet_inst.clone(), keychain_mask, tx_id, tx_slate_id)
}
/// Retrieves the stored transaction associated with a TxLogEntry. Can be used even after the
@ -1085,48 +1077,6 @@ where
owner::verify_slate_messages(slate)
}
/// Scans the entire UTXO set from the node, creating outputs for each scanned
/// output that matches the wallet's master seed. This function is intended to be called as part
/// of a recovery process (either from BIP32 phrase or backup seed files,) and will error if the
/// wallet is non-empty, i.e. contains any outputs at all.
///
/// 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
/// running.
///
/// A single [TxLogEntry](../grin_wallet_libwallet/types/struct.TxLogEntry.html) is created for
/// all non-coinbase outputs discovered and restored during this process. A separate entry
/// is created for each coinbase output.
///
/// # Arguments
///
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
/// being used.
///
/// # Returns
/// * `Ok(())` if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// let mut api_owner = Owner::new(wallet.clone());
/// let result = api_owner.restore(None);
///
/// if let Ok(_) = result {
/// // Wallet outputs should be consistent with what's on chain
/// // ...
/// }
/// ```
pub fn restore(&self, keychain_mask: Option<&SecretKey>) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let res = owner::restore(&mut **w, keychain_mask);
res
}
/// Scans the entire UTXO set from the node, identify which outputs belong to the given wallet
/// update the wallet state to be consistent with what's currently in the UTXO set.
///
@ -1145,7 +1095,9 @@ where
///
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
/// being used.
/// * `delete_unconfirmed` - if `false`, the check_repair process will be non-destructive, and
/// * `start_height` - If provided, the height of the first block from which to start scanning.
/// The scan will start from block 1 if this is not provided.
/// * `delete_unconfirmed` - if `false`, the scan process will be non-destructive, and
/// mostly limited to restoring missing outputs. It will leave unconfirmed transaction logs entries
/// and unconfirmed outputs intact. If `true`, the process will unlock all locked outputs,
/// restore all missing outputs, and mark any outputs that have been marked 'Spent' but are still
@ -1165,8 +1117,9 @@ where
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// let mut api_owner = Owner::new(wallet.clone());
/// let result = api_owner.check_repair(
/// let result = api_owner.scan(
/// None,
/// Some(20000),
/// false,
/// );
///
@ -1176,14 +1129,18 @@ where
/// }
/// ```
pub fn check_repair(
pub fn scan(
&self,
keychain_mask: Option<&SecretKey>,
start_height: Option<u64>,
delete_unconfirmed: bool,
) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::check_repair(&mut **w, keychain_mask, delete_unconfirmed)
owner::scan(
self.wallet_inst.clone(),
keychain_mask,
start_height,
delete_unconfirmed,
)
}
/// Retrieves the last known height known by the wallet. This is determined as follows:
@ -1228,11 +1185,13 @@ where
&self,
keychain_mask: Option<&SecretKey>,
) -> Result<NodeHeightResult, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
let mut res = owner::node_height(&mut **w, keychain_mask)?;
{
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
}
let mut res = owner::node_height(self.wallet_inst.clone(), keychain_mask)?;
if self.doctest_mode {
// return a consistent hash for doctest
res.header_hash =

View file

@ -1161,7 +1161,7 @@ pub trait OwnerRpc: Sync + Send {
fn verify_slate_messages(&self, slate: VersionedSlate) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::restore](struct.Owner.html#method.restore).
Networked version of [Owner::scan](struct.Owner.html#method.scan).
```
@ -1169,8 +1169,8 @@ pub trait OwnerRpc: Sync + Send {
# r#"
{
"jsonrpc": "2.0",
"method": "restore",
"params": [],
"method": "scan",
"params": [null, false],
"id": 1
}
# "#
@ -1187,36 +1187,7 @@ pub trait OwnerRpc: Sync + Send {
# , false, 1, false, false, false);
```
*/
fn restore(&self) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::check_repair](struct.Owner.html#method.check_repair).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "check_repair",
"params": [false],
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , false, 1, false, false, false);
```
*/
fn check_repair(&self, delete_unconfirmed: bool) -> Result<(), ErrorKind>;
fn scan(&self, start_height: Option<u64>, delete_unconfirmed: bool) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::node_height](struct.Owner.html#method.node_height).
@ -1355,12 +1326,8 @@ where
Owner::verify_slate_messages(self, None, &Slate::from(slate)).map_err(|e| e.kind())
}
fn restore(&self) -> Result<(), ErrorKind> {
Owner::restore(self, None).map_err(|e| e.kind())
}
fn check_repair(&self, delete_unconfirmed: bool) -> Result<(), ErrorKind> {
Owner::check_repair(self, None, delete_unconfirmed).map_err(|e| e.kind())
fn scan(&self, start_height: Option<u64>, delete_unconfirmed: bool) -> Result<(), ErrorKind> {
Owner::scan(self, None, start_height, delete_unconfirmed).map_err(|e| e.kind())
}
fn node_height(&self) -> Result<NodeHeightResult, ErrorKind> {
@ -1491,10 +1458,9 @@ pub fn run_doctest_owner(
false,
);
//update local outputs after each block, so transaction IDs stay consistent
let mut w_lock = wallet1.lock();
let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap();
let (wallet_refreshed, _) =
api_impl::owner::retrieve_summary_info(&mut **w, (&mask1).as_ref(), true, 1).unwrap();
api_impl::owner::retrieve_summary_info(wallet1.clone(), (&mask1).as_ref(), true, 1)
.unwrap();
assert!(wallet_refreshed);
}

View file

@ -1220,7 +1220,7 @@ pub trait OwnerRpcS {
fn verify_slate_messages(&self, token: Token, slate: VersionedSlate) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::restore](struct.Owner.html#method.restore).
Networked version of [Owner::scan](struct.Owner.html#method.scan).
```
@ -1228,40 +1228,10 @@ pub trait OwnerRpcS {
# r#"
{
"jsonrpc": "2.0",
"method": "restore",
"params": {
"token": "d202964900000000d302964900000000d402964900000000d502964900000000"
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 1, false, false, false);
```
*/
fn restore(&self, token: Token) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::check_repair](struct.Owner.html#method.check_repair).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "check_repair",
"method": "scan",
"params": {
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
"start_height": 1,
"delete_unconfirmed": false
},
"id": 1
@ -1280,7 +1250,12 @@ pub trait OwnerRpcS {
# , true, 1, false, false, false);
```
*/
fn check_repair(&self, token: Token, delete_unconfirmed: bool) -> Result<(), ErrorKind>;
fn scan(
&self,
token: Token,
start_height: Option<u64>,
delete_unconfirmed: bool,
) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::node_height](struct.Owner.html#method.node_height).
@ -1867,13 +1842,19 @@ where
.map_err(|e| e.kind())
}
fn restore(&self, token: Token) -> Result<(), ErrorKind> {
Owner::restore(self, (&token.keychain_mask).as_ref()).map_err(|e| e.kind())
}
fn check_repair(&self, token: Token, delete_unconfirmed: bool) -> Result<(), ErrorKind> {
Owner::check_repair(self, (&token.keychain_mask).as_ref(), delete_unconfirmed)
.map_err(|e| e.kind())
fn scan(
&self,
token: Token,
start_height: Option<u64>,
delete_unconfirmed: bool,
) -> Result<(), ErrorKind> {
Owner::scan(
self,
(&token.keychain_mask).as_ref(),
start_height,
delete_unconfirmed,
)
.map_err(|e| e.kind())
}
fn node_height(&self, token: Token) -> Result<NodeHeightResult, ErrorKind> {

View file

@ -805,38 +805,13 @@ where
Ok(())
}
pub fn restore<'a, L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
) -> Result<(), Error>
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let result = api.restore(m);
match result {
Ok(_) => {
warn!("Wallet restore complete",);
Ok(())
}
Err(e) => {
error!("Wallet restore failed: {}", e);
error!("Backtrace: {}", e.backtrace().unwrap());
Err(e)
}
}
})?;
Ok(())
}
/// wallet check
pub struct CheckArgs {
pub delete_unconfirmed: bool,
pub start_height: Option<u64>,
}
pub fn check_repair<'a, L, C, K>(
pub fn scan<'a, L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: CheckArgs,
@ -847,9 +822,8 @@ where
K: keychain::Keychain + 'a,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
warn!("Starting wallet check...",);
warn!("Updating all wallet outputs, please wait ...",);
let result = api.check_repair(m, args.delete_unconfirmed);
warn!("Starting output scan ...",);
let result = api.scan(m, args.start_height, args.delete_unconfirmed);
match result {
Ok(_) => {
warn!("Wallet check complete",);

View file

@ -47,7 +47,7 @@ macro_rules! wallet_info {
}
/// Various tests on checking functionality
fn check_repair_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
@ -156,7 +156,7 @@ fn check_repair_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// this should restore our missing outputs
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
api.check_repair(m, true)?;
api.scan(m, None, true)?;
Ok(())
})?;
@ -203,7 +203,7 @@ fn check_repair_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// unlock/restore
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
api.check_repair(m, true)?;
api.scan(m, None, true)?;
Ok(())
})?;
@ -409,7 +409,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
// 0) Check repair when all is okay should leave wallet contents alone
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
api.check_repair(m, true)?;
api.scan(m, None, true)?;
let info = wallet_info!(wallet1.clone(), m)?;
assert_eq!(info.amount_currently_spendable, base_amount * 6);
assert_eq!(info.total, base_amount * 6);
@ -444,7 +444,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
bh += cm as u64;
// confirm balances
// since info is now performing a partial check_repair, these should confirm
// since info is now performing a partial scan, these should confirm
// as containing all outputs
let info = wallet_info!(wallet1.clone(), mask1)?;
assert_eq!(info.amount_currently_spendable, base_amount * 21);
@ -459,7 +459,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
// 1) a full restore should recover all of them:
wallet::controller::owner_single_use(wallet3.clone(), mask3, |api, m| {
api.restore(m)?;
api.scan(m, None, false)?;
Ok(())
})?;
@ -472,9 +472,9 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
Ok(())
})?;
// 2) check_repair should recover them into a single wallet
// 2) scan should recover them into a single wallet
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
api.check_repair(m, true)?;
api.scan(m, None, true)?;
Ok(())
})?;
@ -487,8 +487,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
})?;
// 3) If I recover from seed and start using the wallet without restoring,
// check_repair should restore the older outputs
// update, again, since check_repair is run automatically, balances on both
// scan should restore the older outputs
// update, again, since scan is run automatically, balances on both
// wallets should turn out the same
send_to_dest!(
miner.clone(),
@ -525,7 +525,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
})?;
wallet::controller::owner_single_use(wallet5.clone(), mask5, |api, m| {
api.restore(m)?;
api.scan(m, None, false)?;
Ok(())
})?;
@ -538,7 +538,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
})?;
// 4) If I recover from seed and start using the wallet without restoring,
// check_repair should restore the older outputs
// scan should restore the older outputs
send_to_dest!(
miner.clone(),
miner_mask,
@ -580,7 +580,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
})?;
wallet::controller::owner_single_use(wallet6.clone(), mask6, |api, m| {
api.check_repair(m, true)?;
api.scan(m, None, true)?;
Ok(())
})?;
@ -666,7 +666,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
})?;
wallet::controller::owner_single_use(wallet8.clone(), mask8, |api, m| {
api.restore(m)?;
api.scan(m, None, false)?;
let info = wallet_info!(wallet8.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 15);
@ -680,7 +680,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
})?;
// 6) Start using same seed with a different account, now overwriting
// ids on account 2 as well, check_repair should get all outputs created
// ids on account 2 as well, scan should get all outputs created
// to now into 2 accounts
wallet::controller::owner_single_use(wallet9.clone(), mask9, |api, m| {
@ -718,7 +718,7 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.check_repair(m, true)?;
api.scan(m, None, true)?;
let info = wallet_info!(wallet9.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
@ -734,9 +734,9 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false);
// 7) Ensure check_repair creates missing accounts
// 7) Ensure scan creates missing accounts
wallet::controller::owner_single_use(wallet10.clone(), mask10, |api, m| {
api.check_repair(m, true)?;
api.scan(m, None, true)?;
api.set_active_account(m, "account_1")?;
let info = wallet_info!(wallet10.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
@ -840,10 +840,10 @@ fn output_scanning_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
}
#[test]
fn check_repair() {
let test_dir = "test_output/check_repair";
fn scan() {
let test_dir = "test_output/scan";
setup(test_dir);
if let Err(e) = check_repair_impl(test_dir) {
if let Err(e) = scan_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
clean_output_dir(test_dir);

View file

@ -1,428 +0,0 @@
// Copyright 2019 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! tests for wallet restore
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use grin_wallet_util::grin_keychain as keychain;
use self::keychain::{ExtKeychain, Identifier, Keychain};
use self::libwallet::{AcctPathMapping, InitTxArgs, Slate};
use impls::test_framework::{self, LocalWalletClient};
use std::fs;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
fn restore_wallet(base_dir: &'static str, wallet_dir: &str) -> Result<(), libwallet::Error> {
let source_seed = format!("{}/{}/wallet_data/wallet.seed", base_dir, wallet_dir);
let dest_wallet_name = format!("{}_restore", wallet_dir);
let dest_dir = format!("{}/{}/wallet_data", base_dir, dest_wallet_name);
let mut wallet_proxy = create_wallet_proxy(base_dir);
create_wallet_and_add!(
client,
wallet,
mask,
base_dir,
&dest_wallet_name,
None,
&mut wallet_proxy,
false
);
// close created wallet
let mut w_lock = wallet.lock();
let lc = w_lock.lc_provider()?;
lc.close_wallet(None)?;
let dest_seed = format!("{}/wallet.seed", dest_dir);
println!("Source: {}, Dest: {}", source_seed, dest_seed);
fs::copy(source_seed, dest_seed)?;
// reopen with new seed
open_wallet_and_add!(
client,
wallet,
mask_i,
&base_dir,
&dest_wallet_name,
&mut wallet_proxy,
false
);
let mask = (&mask_i).as_ref();
// Set the wallet proxy listener running
let wp_running = wallet_proxy.running.clone();
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// perform the restore and update wallet info
wallet::controller::owner_single_use(wallet.clone(), mask, |api, m| {
let _ = api.restore(m)?;
let _ = api.retrieve_summary_info(m, true, 1)?;
Ok(())
})?;
wp_running.store(false, Ordering::Relaxed);
//thread::sleep(Duration::from_millis(1000));
Ok(())
}
fn compare_wallet_restore(
base_dir: &'static str,
wallet_dir: &str,
account_path: &Identifier,
) -> Result<(), libwallet::Error> {
let restore_name = format!("{}_restore", wallet_dir);
let mut wallet_proxy = create_wallet_proxy(base_dir);
open_wallet_and_add!(
client,
wallet_source,
source_mask_i,
&base_dir,
&wallet_dir,
&mut wallet_proxy,
false
);
let source_mask = (&source_mask_i).as_ref();
open_wallet_and_add!(
client,
wallet_dest,
dest_mask_i,
&base_dir,
&restore_name,
&mut wallet_proxy,
false
);
let dest_mask = (&dest_mask_i).as_ref();
{
wallet_inst!(wallet_source, w);
w.set_parent_key_id(account_path.clone());
}
{
wallet_inst!(wallet_dest, w);
w.set_parent_key_id(account_path.clone());
}
// Set the wallet proxy listener running
let wp_running = wallet_proxy.running.clone();
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
let mut src_info: Option<libwallet::WalletInfo> = None;
let mut dest_info: Option<libwallet::WalletInfo> = None;
let mut src_txs: Option<Vec<libwallet::TxLogEntry>> = None;
let mut dest_txs: Option<Vec<libwallet::TxLogEntry>> = None;
let mut src_accts: Option<Vec<AcctPathMapping>> = None;
let mut dest_accts: Option<Vec<AcctPathMapping>> = None;
// Overall wallet info should be the same
wallet::controller::owner_single_use(wallet_source.clone(), source_mask, |api, m| {
src_info = Some(api.retrieve_summary_info(m, true, 1)?.1);
src_txs = Some(api.retrieve_txs(m, true, None, None)?.1);
src_accts = Some(api.accounts(m)?);
Ok(())
})?;
wallet::controller::owner_single_use(wallet_dest.clone(), dest_mask, |api, m| {
dest_info = Some(api.retrieve_summary_info(m, true, 1)?.1);
dest_txs = Some(api.retrieve_txs(m, true, None, None)?.1);
dest_accts = Some(api.accounts(m)?);
Ok(())
})?;
// Info should all be the same
assert_eq!(src_info, dest_info);
// Net differences in TX logs should be the same
let src_sum: i64 = src_txs
.clone()
.unwrap()
.iter()
.map(|t| t.amount_credited as i64 - t.amount_debited as i64)
.sum();
let dest_sum: i64 = dest_txs
.clone()
.unwrap()
.iter()
.map(|t| t.amount_credited as i64 - t.amount_debited as i64)
.sum();
assert_eq!(src_sum, dest_sum);
// Number of created accounts should be the same
assert_eq!(
src_accts.as_ref().unwrap().len(),
dest_accts.as_ref().unwrap().len()
);
wp_running.store(false, Ordering::Relaxed);
//thread::sleep(Duration::from_millis(1000));
Ok(())
}
/// Build up 2 wallets, perform a few transactions on them
/// Then attempt to restore them in separate directories and check contents are the same
fn setup_restore(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// wallet 2 will use another account
wallet::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
api.create_account_path(m, "account1")?;
api.create_account_path(m, "account2")?;
Ok(())
})?;
// Default wallet 2 to listen on that account
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
}
// Another wallet
create_wallet_and_add!(
client3,
wallet3,
mask3_i,
test_dir,
"wallet3",
None,
&mut wallet_proxy,
false
);
let mask3 = (&mask3_i).as_ref();
// Set the wallet proxy listener running
let wp_running = wallet_proxy.running.clone();
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// mine a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false);
// assert wallet contents
// and a single use api for a send command
let amount = 60_000_000_000;
let mut slate = Slate::blank(1);
wallet::controller::owner_single_use(wallet1.clone(), mask1, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate, 0)?;
slate = sender_api.finalize_tx(m, &slate)?;
sender_api.post_tx(m, &slate.tx, false)?;
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
// Send some to wallet 3
wallet::controller::owner_single_use(wallet1.clone(), mask1, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet3", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate, 0)?;
slate = sender_api.finalize_tx(m, &slate)?;
sender_api.post_tx(m, &slate.tx, false)?;
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet3.clone(), mask3, 10, false);
// Wallet3 to wallet 2
wallet::controller::owner_single_use(wallet3.clone(), mask3, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount * 3,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client3.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate, 0)?;
slate = sender_api.finalize_tx(m, &slate)?;
sender_api.post_tx(m, &slate.tx, false)?;
Ok(())
})?;
// Another listener account on wallet 2
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account2")?;
}
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false);
// Wallet3 to wallet 2 again (to another account)
wallet::controller::owner_single_use(wallet3.clone(), mask3, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount * 3,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client3.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate, 0)?;
slate = sender_api.finalize_tx(m, &slate)?;
sender_api.post_tx(m, &slate.tx, false)?;
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
// update everyone
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
let _ = api.retrieve_summary_info(m, true, 1)?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
let _ = api.retrieve_summary_info(m, true, 1)?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet3.clone(), mask3, |api, m| {
let _ = api.retrieve_summary_info(m, true, 1)?;
Ok(())
})?;
wp_running.store(false, Ordering::Relaxed);
Ok(())
}
fn perform_restore(test_dir: &'static str) -> Result<(), libwallet::Error> {
restore_wallet(test_dir, "wallet1")?;
compare_wallet_restore(
test_dir,
"wallet1",
&ExtKeychain::derive_key_id(2, 0, 0, 0, 0),
)?;
restore_wallet(test_dir, "wallet2")?;
compare_wallet_restore(
test_dir,
"wallet2",
&ExtKeychain::derive_key_id(2, 0, 0, 0, 0),
)?;
compare_wallet_restore(
test_dir,
"wallet2",
&ExtKeychain::derive_key_id(2, 1, 0, 0, 0),
)?;
compare_wallet_restore(
test_dir,
"wallet2",
&ExtKeychain::derive_key_id(2, 2, 0, 0, 0),
)?;
restore_wallet(test_dir, "wallet3")?;
compare_wallet_restore(
test_dir,
"wallet3",
&ExtKeychain::derive_key_id(2, 0, 0, 0, 0),
)?;
Ok(())
}
#[test]
fn wallet_restore() {
let test_dir = "test_output/wallet_restore";
setup(test_dir);
if let Err(e) = setup_restore(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
if let Err(e) = perform_restore(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
// let logging finish
thread::sleep(Duration::from_millis(200));
clean_output_dir(test_dir);
}

View file

@ -21,7 +21,6 @@ use std::io::{Read, Write};
use std::marker::PhantomData;
use std::path::Path;
use failure::ResultExt;
use uuid::Uuid;
use crate::blake2::blake2b::{Blake2b, Blake2bResult};
@ -31,10 +30,9 @@ use crate::store::{self, option_to_not_found, to_key, to_key_u64};
use crate::core::core::Transaction;
use crate::core::ser;
use crate::libwallet::{check_repair, restore};
use crate::libwallet::{
AcctPathMapping, Context, Error, ErrorKind, NodeClient, OutputData, ScannedBlockInfo,
TxLogEntry, WalletBackend, WalletOutputBatch,
TxLogEntry, WalletBackend, WalletInitStatus, WalletOutputBatch,
};
use crate::util::secp::constants::SECRET_KEY_SIZE;
use crate::util::secp::key::SecretKey;
@ -55,6 +53,8 @@ const TX_LOG_ID_PREFIX: u8 = 'i' as u8;
const ACCOUNT_PATH_MAPPING_PREFIX: u8 = 'a' as u8;
const LAST_SCANNED_BLOCK: u8 = 'l' as u8;
const LAST_SCANNED_KEY: &str = "LAST_SCANNED_KEY";
const WALLET_INIT_STATUS: u8 = 'w' as u8;
const WALLET_INIT_STATUS_KEY: &str = "WALLET_INIT_STATUS";
/// test to see if database files exist in the current directory. If so,
/// use a DB backend for all operations
@ -397,6 +397,14 @@ where
}))
}
fn batch_no_mask<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error> {
Ok(Box::new(Batch {
_store: self,
db: RefCell::new(Some(self.db.batch()?)),
keychain: None,
}))
}
fn current_child_index<'a>(&mut self, parent_key_id: &Identifier) -> Result<u32, Error> {
let index = {
let batch = self.db.batch()?;
@ -460,33 +468,17 @@ where
Ok(last_scanned_block)
}
fn restore(
&mut self,
keychain_mask: Option<&SecretKey>,
to_height: u64,
) -> Result<Option<ScannedBlockInfo>, Error> {
let res = restore(self, keychain_mask, to_height).context(ErrorKind::Restore)?;
Ok(res)
}
fn check_repair(
&mut self,
keychain_mask: Option<&SecretKey>,
delete_unconfirmed: bool,
start_height: u64,
end_height: u64,
status_fn: fn(&str),
) -> Result<ScannedBlockInfo, Error> {
let res = check_repair(
self,
keychain_mask,
delete_unconfirmed,
start_height,
end_height,
status_fn,
)
.context(ErrorKind::Restore)?;
Ok(res)
fn init_status<'a>(&mut self) -> Result<WalletInitStatus, Error> {
let batch = self.db.batch()?;
let init_status_key = to_key(
WALLET_INIT_STATUS,
&mut WALLET_INIT_STATUS_KEY.as_bytes().to_vec(),
);
let status = match batch.get_ser(&init_status_key)? {
Some(s) => s,
None => WalletInitStatus::InitComplete,
};
Ok(status)
}
}
@ -618,6 +610,19 @@ where
Ok(())
}
fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error> {
let init_status_key = to_key(
WALLET_INIT_STATUS,
&mut WALLET_INIT_STATUS_KEY.as_bytes().to_vec(),
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&init_status_key, &value)?;
Ok(())
}
fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> {
let deriv_key = to_key(DERIV_PREFIX, &mut parent_id.to_bytes().to_vec());
self.db

View file

@ -19,7 +19,9 @@ use crate::config::{
};
use crate::core::global;
use crate::keychain::Keychain;
use crate::libwallet::{Error, ErrorKind, NodeClient, WalletBackend, WalletLCProvider};
use crate::libwallet::{
Error, ErrorKind, NodeClient, WalletBackend, WalletInitStatus, WalletLCProvider,
};
use crate::lifecycle::seed::WalletSeed;
use crate::util::secp::key::SecretKey;
use crate::util::ZeroingString;
@ -181,9 +183,9 @@ where
return Err(ErrorKind::WalletSeedExists(msg))?;
}
}
let _ = WalletSeed::init_file(&data_dir_name, mnemonic_length, mnemonic, password);
let _ = WalletSeed::init_file(&data_dir_name, mnemonic_length, mnemonic.clone(), password);
info!("Wallet seed file created");
let _wallet: LMDBBackend<'a, C, K> =
let mut wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error creating wallet: {}, Data Dir: {}", e, &data_dir_name);
@ -192,6 +194,13 @@ where
}
Ok(d) => d,
};
// Save init status of this wallet, to determine whether it needs a full UTXO scan
let mut batch = wallet.batch_no_mask()?;
match mnemonic {
Some(_) => batch.save_init_status(WalletInitStatus::InitNeedsScanning)?,
None => batch.save_init_status(WalletInitStatus::InitNoScanning)?,
};
batch.commit()?;
info!("Wallet database backend created at {}", data_dir_name);
Ok(())
}

View file

@ -243,10 +243,8 @@ where
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
{
let mut w_lock = wallet.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let (wallet_refreshed, wallet_info) =
owner::retrieve_summary_info(&mut **w, keychain_mask, true, 1)?;
owner::retrieve_summary_info(wallet, keychain_mask, true, 1)?;
assert!(wallet_refreshed);
Ok(wallet_info)
}

View file

@ -21,15 +21,18 @@ use crate::grin_core::core::Transaction;
use crate::grin_core::ser;
use crate::grin_util;
use crate::grin_util::secp::key::SecretKey;
use crate::grin_util::Mutex;
use crate::grin_keychain::{Identifier, Keychain};
use crate::internal::{keys, selection, tx, updater};
use crate::internal::{keys, scan, selection, tx, updater};
use crate::slate::Slate;
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, TxWrapper, WalletBackend, WalletInfo};
use crate::{Error, ErrorKind};
use crate::{
InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping, TxLogEntryType,
wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
ScannedBlockInfo, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
};
use crate::{Error, ErrorKind};
use std::sync::Arc;
const USER_MESSAGE_MAX_LEN: usize = 256;
@ -68,29 +71,30 @@ where
}
/// retrieve outputs
pub fn retrieve_outputs<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn retrieve_outputs<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
include_spent: bool,
refresh_from_node: bool,
tx_id: Option<u32>,
) -> Result<(bool, Vec<OutputCommitMapping>), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = w.parent_key_id();
let mut validated = false;
if refresh_from_node {
validated = update_wallet_state(w, keychain_mask, false)?;
validated = update_wallet_state(wallet_inst.clone(), keychain_mask, false)?;
}
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
Ok((
validated,
updater::retrieve_outputs(
&mut *w,
&mut **w,
keychain_mask,
include_spent,
tx_id,
@ -100,50 +104,50 @@ where
}
/// Retrieve txs
pub fn retrieve_txs<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn retrieve_txs<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
refresh_from_node: bool,
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
) -> Result<(bool, Vec<TxLogEntry>), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = w.parent_key_id();
let mut validated = false;
if refresh_from_node {
validated = update_wallet_state(w, keychain_mask, false)?;
validated = update_wallet_state(wallet_inst.clone(), keychain_mask, false)?;
}
let txs = updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
let txs = updater::retrieve_txs(&mut **w, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
Ok((validated, txs))
}
/// Retrieve summary info
pub fn retrieve_summary_info<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn retrieve_summary_info<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
refresh_from_node: bool,
minimum_confirmations: u64,
) -> Result<(bool, WalletInfo), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = w.parent_key_id();
let mut validated = false;
if refresh_from_node {
validated = update_wallet_state(w, keychain_mask, false)?;
validated = update_wallet_state(wallet_inst.clone(), keychain_mask, false)?;
}
let wallet_info = updater::retrieve_info(&mut *w, &parent_key_id, minimum_confirmations)?;
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
let wallet_info = updater::retrieve_info(&mut **w, &parent_key_id, minimum_confirmations)?;
Ok((validated, wallet_info))
}
@ -405,24 +409,25 @@ where
}
/// cancel tx
pub fn cancel_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn cancel_tx<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = w.parent_key_id();
if !update_wallet_state(w, keychain_mask, false)? {
if !update_wallet_state(wallet_inst.clone(), keychain_mask, false)? {
return Err(ErrorKind::TransactionCancellationError(
"Can't contact running Grin node. Not Cancelling.",
))?;
}
tx::cancel_tx(&mut *w, keychain_mask, &parent_key_id, tx_id, tx_slate_id)
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, tx_id, tx_slate_id)
}
/// get stored tx
@ -464,48 +469,44 @@ pub fn verify_slate_messages(slate: &Slate) -> Result<(), Error> {
slate.verify_messages()
}
/// Attempt to restore contents of wallet
pub fn restore<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let tip = w.w2n_client().get_chain_tip()?;
let info_res = w.restore(keychain_mask, tip.0)?;
if let Some(mut i) = info_res {
let mut batch = w.batch(keychain_mask)?;
i.hash = tip.1;
batch.save_last_scanned_block(i)?;
batch.commit()?;
}
Ok(())
}
/// check repair
pub fn check_repair<'a, T: ?Sized, C, K>(
w: &mut T,
/// Accepts a wallet inst instead of a raw wallet so it can
/// lock as little as possible
pub fn scan<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
start_height: Option<u64>,
delete_unconfirmed: bool,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
update_outputs(w, keychain_mask, true)?;
let status_fn: fn(&str) = |m| warn!("{}", m);
let tip = w.w2n_client().get_chain_tip()?;
update_outputs(wallet_inst.clone(), keychain_mask, true)?;
let tip = {
wallet_lock!(wallet_inst, w);
w.w2n_client().get_chain_tip()?
};
// for now, just start from 1
// TODO: only do this if hashes of last stored block don't match chain
// TODO: Provide parameter to manually override on command line
let mut info = w.check_repair(keychain_mask, delete_unconfirmed, 1, tip.0, status_fn)?;
let status_fn: fn(&str) = |m| warn!("{}", m);
let start_height = match start_height {
Some(h) => h,
None => 1,
};
let mut info = scan::scan(
wallet_inst.clone(),
keychain_mask,
delete_unconfirmed,
start_height,
tip.0,
status_fn,
)?;
info.hash = tip.1;
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(keychain_mask)?;
batch.save_last_scanned_block(info)?;
batch.commit()?;
@ -514,16 +515,19 @@ where
}
/// node height
pub fn node_height<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn node_height<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
) -> Result<NodeHeightResult, Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let res = w.w2n_client().get_chain_tip();
let res = {
wallet_lock!(wallet_inst, w);
w.w2n_client().get_chain_tip()
};
match res {
Ok(r) => Ok(NodeHeightResult {
height: r.0,
@ -531,7 +535,7 @@ where
updated_from_node: true,
}),
Err(_) => {
let outputs = retrieve_outputs(w, keychain_mask, true, false, None)?;
let outputs = retrieve_outputs(wallet_inst, keychain_mask, true, false, None)?;
let height = match outputs.1.iter().map(|m| m.output.height).max() {
Some(height) => height,
None => 0,
@ -545,69 +549,114 @@ where
}
}
/// Experimental, wrap the entire definition of how a wallet's state is updated
fn update_wallet_state<'a, T: ?Sized, C, K>(
w: &mut T,
fn update_wallet_state<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
update_all: bool,
) -> Result<bool, Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = w.parent_key_id().clone();
let mut result;
let parent_key_id = {
wallet_lock!(wallet_inst, w);
w.parent_key_id().clone()
};
let client = {
wallet_lock!(wallet_inst, w);
w.w2n_client().clone()
};
// Step 1: Update outputs and transactions purely based on UTXO state
result = update_outputs(w, keychain_mask, update_all)?;
let mut result = update_outputs(wallet_inst.clone(), keychain_mask, update_all)?;
if !result {
return Ok(result);
}
// Step 2: Update outstanding transactions with no change outputs by kernel
let mut txs = updater::retrieve_txs(&mut *w, None, None, Some(&parent_key_id), true)?;
result = update_txs_via_kernel(w, keychain_mask, &mut txs)?;
let mut txs = {
wallet_lock!(wallet_inst, w);
updater::retrieve_txs(&mut **w, None, None, Some(&parent_key_id), true)?
};
result = update_txs_via_kernel(wallet_inst.clone(), keychain_mask, &mut txs)?;
if !result {
return Ok(result);
}
// Step 3: Scan back a bit on the chain
let tip = w.w2n_client().get_chain_tip()?;
let res = client.get_chain_tip();
// if we can't get the tip, don't continue
let tip = match res {
Ok(t) => t,
Err(_) => return Ok(false),
};
// Check if this is a restored wallet that needs a full scan
let last_scanned_block = {
wallet_lock!(wallet_inst, w);
match w.init_status()? {
WalletInitStatus::InitNeedsScanning => ScannedBlockInfo {
height: 0,
hash: "".to_owned(),
start_pmmr_index: 0,
last_pmmr_index: 0,
},
WalletInitStatus::InitNoScanning => ScannedBlockInfo {
height: tip.clone().0,
hash: tip.clone().1,
start_pmmr_index: 0,
last_pmmr_index: 0,
},
WalletInitStatus::InitComplete => w.last_scanned_block()?,
}
};
// for now, just go back 100 blocks from last scanned block
// TODO: only do this if hashes of last stored block don't match chain
let last_scanned_block = w.last_scanned_block()?;
let start_index = last_scanned_block.height.saturating_sub(100);
let mut status_fn: fn(&str) = |m| debug!("{}", m);
if last_scanned_block.height == 0 {
warn!("This wallet's contents has not been verified with a full chain scan, performing scan now.");
warn!("This wallet's contents has not been initialized with a full chain scan, performing scan now.");
warn!("This operation may take a while for the first scan, but should be much quicker once the initial scan is done.");
status_fn = |m| warn!("{}", m);
}
let mut info = w.check_repair(keychain_mask, false, start_index, tip.0, status_fn)?;
let mut info = scan::scan(
wallet_inst.clone(),
keychain_mask,
false,
start_index,
tip.0,
status_fn,
)?;
info.hash = tip.1;
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(keychain_mask)?;
batch.save_last_scanned_block(info)?;
// init considered complete after first successful update
batch.save_init_status(WalletInitStatus::InitComplete)?;
batch.commit()?;
Ok(result)
}
/// Attempt to update outputs in wallet, return whether it was successful
fn update_outputs<'a, T: ?Sized, C, K>(
w: &mut T,
fn update_outputs<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
update_all: bool,
) -> Result<bool, Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
match updater::refresh_outputs(&mut *w, keychain_mask, &parent_key_id, update_all) {
match updater::refresh_outputs(&mut **w, keychain_mask, &parent_key_id, update_all) {
Ok(_) => Ok(true),
Err(e) => {
if let ErrorKind::InvalidKeychainMask = e.kind() {
@ -619,21 +668,31 @@ where
}
/// Update transactions that need to be validated via kernel lookup
fn update_txs_via_kernel<'a, T: ?Sized, C, K>(
w: &mut T,
fn update_txs_via_kernel<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
txs: &mut Vec<TxLogEntry>,
) -> Result<bool, Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = w.parent_key_id();
let height = match w.w2n_client().get_chain_tip() {
let parent_key_id = {
wallet_lock!(wallet_inst, w);
w.parent_key_id().clone()
};
let mut client = {
wallet_lock!(wallet_inst, w);
w.w2n_client().clone()
};
let height = match client.get_chain_tip() {
Ok(h) => h.0,
Err(_) => return Ok(false),
};
for tx in txs.iter_mut() {
if tx.confirmed {
continue;
@ -642,15 +701,14 @@ where
continue;
}
if let Some(e) = tx.kernel_excess {
let res = w
.w2n_client()
.get_kernel(&e, tx.kernel_lookup_min_height, Some(height));
let res = client.get_kernel(&e, tx.kernel_lookup_min_height, Some(height));
let kernel = match res {
Ok(k) => k,
Err(_) => return Ok(false),
};
if let Some(k) = kernel {
debug!("Kernel Retrieved: {:?}", k);
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(keychain_mask)?;
tx.confirmed = true;
tx.update_confirmation_ts();

View file

@ -22,7 +22,7 @@
#![warn(missing_docs)]
pub mod keys;
pub mod restore;
pub mod scan;
pub mod selection;
pub mod tx;
pub mod updater;

View file

@ -17,14 +17,15 @@ use crate::grin_core::consensus::{valid_header_version, WEEK_HEIGHT};
use crate::grin_core::core::HeaderVersion;
use crate::grin_core::global;
use crate::grin_core::libtx::proof;
use crate::grin_keychain::{ExtKeychain, Identifier, Keychain, SwitchCommitmentType};
use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType};
use crate::grin_util::secp::key::SecretKey;
use crate::grin_util::secp::pedersen;
use crate::grin_util::Mutex;
use crate::internal::{keys, updater};
use crate::types::*;
use crate::{Error, OutputCommitMapping};
use crate::{wallet_lock, Error, OutputCommitMapping};
use std::collections::HashMap;
use std::time::Instant;
use std::sync::Arc;
/// Utility struct for return values from below
#[derive(Debug, Clone)]
@ -59,15 +60,12 @@ struct RestoredTxStats {
pub num_outputs: usize,
}
fn identify_utxo_outputs<'a, T, C, K, F>(
wallet: &mut T,
keychain_mask: Option<&SecretKey>,
fn identify_utxo_outputs<'a, K, F>(
keychain: &K,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
status_cb: &F,
) -> Result<Vec<OutputResult>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
F: Fn(&str),
{
@ -77,9 +75,8 @@ where
outputs.len(),
));
let keychain = wallet.keychain(keychain_mask)?;
let legacy_builder = proof::LegacyProofBuilder::new(&keychain);
let builder = proof::ProofBuilder::new(&keychain);
let legacy_builder = proof::LegacyProofBuilder::new(keychain);
let builder = proof::ProofBuilder::new(keychain);
let legacy_version = HeaderVersion(1);
for output in outputs.iter() {
@ -139,15 +136,14 @@ where
Ok(wallet_outputs)
}
fn collect_chain_outputs<'a, T, C, K, F>(
wallet: &mut T,
keychain_mask: Option<&SecretKey>,
fn collect_chain_outputs<'a, C, K, F>(
keychain: &K,
client: C,
start_index: u64,
end_index: Option<u64>,
status_cb: &F,
) -> Result<(Vec<OutputResult>, u64), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
F: Fn(&str),
@ -157,9 +153,8 @@ where
let mut result_vec: Vec<OutputResult> = vec![];
let last_retrieved_return_index;
loop {
let (highest_index, last_retrieved_index, outputs) = wallet
.w2n_client()
.get_outputs_by_pmmr_index(start_index, end_index, batch_size)?;
let (highest_index, last_retrieved_index, outputs) =
client.get_outputs_by_pmmr_index(start_index, end_index, batch_size)?;
status_cb(&format!(
"Checking {} outputs, up to index {}. (Highest index: {})",
outputs.len(),
@ -168,8 +163,7 @@ where
));
result_vec.append(&mut identify_utxo_outputs(
wallet,
keychain_mask,
keychain,
outputs.clone(),
status_cb,
)?);
@ -184,20 +178,22 @@ where
}
///
fn restore_missing_output<'a, T, C, K>(
wallet: &mut T,
fn restore_missing_output<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
output: OutputResult,
found_parents: &mut HashMap<Identifier, u32>,
tx_stats: &mut Option<&mut HashMap<Identifier, RestoredTxStats>>,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let commit = wallet.calc_commit_for_cache(keychain_mask, output.value, &output.key_id)?;
let mut batch = wallet.batch(keychain_mask)?;
wallet_lock!(wallet_inst, w);
let commit = w.calc_commit_for_cache(keychain_mask, output.value, &output.key_id)?;
let mut batch = w.batch(keychain_mask)?;
error!("RESTORING OUTPUT: {:?}", output);
@ -270,20 +266,21 @@ where
}
///
fn cancel_tx_log_entry<'a, T, C, K>(
wallet: &mut T,
fn cancel_tx_log_entry<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
output: &OutputData,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = output.key_id.parent_path();
wallet_lock!(wallet_inst, w);
let updated_tx_entry = if output.tx_log_entry.is_some() {
let entries = updater::retrieve_txs(
wallet,
&mut **w,
output.tx_log_entry.clone(),
None,
Some(&parent_key_id),
@ -303,7 +300,7 @@ where
} else {
None
};
let mut batch = wallet.batch(keychain_mask)?;
let mut batch = w.batch(keychain_mask)?;
if let Some(t) = updated_tx_entry {
batch.save_tx_log_entry(t, &parent_key_id)?;
}
@ -311,11 +308,11 @@ where
Ok(())
}
/// Check / repair wallet contents
/// Check / repair wallet contents by scanning against chain
/// assume wallet contents have been freshly updated with contents
/// of latest block
pub fn check_repair<'a, T, C, K, F>(
wallet: &mut T,
pub fn scan<'a, L, C, K, F>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
delete_unconfirmed: bool,
start_height: u64,
@ -323,22 +320,24 @@ pub fn check_repair<'a, T, C, K, F>(
status_cb: F,
) -> Result<ScannedBlockInfo, Error>
where
T: WalletBackend<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
F: Fn(&str),
{
// First, get a definitive list of outputs we own from the chain
status_cb("Starting UTXO scan");
let (client, keychain) = {
wallet_lock!(wallet_inst, w);
(w.w2n_client().clone(), w.keychain(keychain_mask)?.clone())
};
// Retrieve the actual PMMR index range we're looking for
let pmmr_range = wallet
.w2n_client()
.height_range_to_pmmr_indices(start_height, Some(end_height))?;
let pmmr_range = client.height_range_to_pmmr_indices(start_height, Some(end_height))?;
let (chain_outs, last_index) = collect_chain_outputs(
wallet,
keychain_mask,
&keychain,
client,
pmmr_range.0,
Some(pmmr_range.1),
&status_cb,
@ -350,8 +349,8 @@ where
// Now, get all outputs owned by this wallet (regardless of account)
let wallet_outputs = {
let res = updater::retrieve_outputs(&mut *wallet, keychain_mask, true, None, None)?;
res
wallet_lock!(wallet_inst, w);
updater::retrieve_outputs(&mut **w, keychain_mask, true, None, None)?
};
let mut missing_outs = vec![];
@ -384,8 +383,9 @@ where
));
o.status = OutputStatus::Unspent;
// any transactions associated with this should be cancelled
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
let mut batch = wallet.batch(keychain_mask)?;
cancel_tx_log_entry(wallet_inst.clone(), keychain_mask, &o)?;
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(keychain_mask)?;
batch.save(o)?;
batch.commit()?;
}
@ -399,7 +399,13 @@ where
Restoring.",
m.value, m.key_id, m.commit, m.mmr_index
));
restore_missing_output(wallet, keychain_mask, m, &mut found_parents, &mut None)?;
restore_missing_output(
wallet_inst.clone(),
keychain_mask,
m,
&mut found_parents,
&mut None,
)?;
}
if delete_unconfirmed {
@ -412,8 +418,9 @@ where
o.value, o.key_id, m.1.commit,
));
o.status = OutputStatus::Unspent;
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
let mut batch = wallet.batch(keychain_mask)?;
cancel_tx_log_entry(wallet_inst.clone(), keychain_mask, &o)?;
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(keychain_mask)?;
batch.save(o)?;
batch.commit()?;
}
@ -430,28 +437,30 @@ where
Deleting and cancelling associated transaction log entries.",
o.value, o.key_id, m.commit,
));
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
let mut batch = wallet.batch(keychain_mask)?;
cancel_tx_log_entry(wallet_inst.clone(), keychain_mask, &o)?;
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(keychain_mask)?;
batch.delete(&o.key_id, &o.mmr_index)?;
batch.commit()?;
}
}
// restore labels, account paths and child derivation indices
wallet_lock!(wallet_inst, w);
let label_base = "account";
let accounts: Vec<Identifier> = wallet.acct_path_iter().map(|m| m.path).collect();
let accounts: Vec<Identifier> = w.acct_path_iter().map(|m| m.path).collect();
let mut acct_index = accounts.len();
for (path, max_child_index) in found_parents.iter() {
// Only restore paths that don't exist
if !accounts.contains(path) {
let label = format!("{}_{}", label_base, acct_index);
status_cb(&format!("Setting account {} at path {}", label, path));
keys::set_acct_path(wallet, keychain_mask, &label, path)?;
keys::set_acct_path(&mut **w, keychain_mask, &label, path)?;
acct_index += 1;
}
let current_child_index = wallet.current_child_index(&path)?;
let current_child_index = w.current_child_index(&path)?;
if *max_child_index >= current_child_index {
let mut batch = wallet.batch(keychain_mask)?;
let mut batch = w.batch(keychain_mask)?;
debug!("Next child for account {} is {}", path, max_child_index + 1);
batch.save_child_index(path, max_child_index + 1)?;
batch.commit()?;
@ -465,97 +474,3 @@ where
last_pmmr_index: last_index,
})
}
/// Restore a wallet
pub fn restore<'a, T, C, K>(
wallet: &mut T,
keychain_mask: Option<&SecretKey>,
end_height: u64,
) -> Result<Option<ScannedBlockInfo>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
// Don't proceed if wallet_data has anything in it
let is_empty = wallet.iter().next().is_none();
if !is_empty {
error!("Not restoring. Please back up and remove existing db directory first.");
return Ok(None);
}
let now = Instant::now();
warn!("Starting restore.");
// Retrieve the actual PMMR index range we're looking for
let pmmr_range = wallet
.w2n_client()
.height_range_to_pmmr_indices(1, Some(end_height))?;
let (result_vec, last_index) = collect_chain_outputs(
wallet,
keychain_mask,
pmmr_range.0,
Some(pmmr_range.1),
&|m| warn!("{}", m),
)?;
warn!(
"Identified {} wallet_outputs as belonging to this wallet",
result_vec.len(),
);
let mut found_parents: HashMap<Identifier, u32> = HashMap::new();
let mut restore_stats = HashMap::new();
// Now save what we have
for output in result_vec {
restore_missing_output(
wallet,
keychain_mask,
output,
&mut found_parents,
&mut Some(&mut restore_stats),
)?;
}
// restore labels, account paths and child derivation indices
let label_base = "account";
let mut acct_index = 1;
for (path, max_child_index) in found_parents.iter() {
// default path already exists
if *path != ExtKeychain::derive_key_id(2, 0, 0, 0, 0) {
let label = format!("{}_{}", label_base, acct_index);
keys::set_acct_path(wallet, keychain_mask, &label, path)?;
acct_index += 1;
}
// restore tx log entry for non-coinbase outputs
if let Some(s) = restore_stats.get(path) {
let mut batch = wallet.batch(keychain_mask)?;
let mut t = TxLogEntry::new(path.clone(), TxLogEntryType::TxReceived, s.log_id);
t.confirmed = true;
t.amount_credited = s.amount_credited;
t.num_outputs = s.num_outputs;
t.update_confirmation_ts();
error!("SAVING TX RESTORE {:?}", t);
batch.save_tx_log_entry(t, &path)?;
batch.commit()?;
}
let mut batch = wallet.batch(keychain_mask)?;
batch.save_child_index(path, max_child_index + 1)?;
debug!("Next child for account {} is {}", path, max_child_index + 1);
batch.commit()?;
}
let mut sec = now.elapsed().as_secs();
let min = sec / 60;
sec %= 60;
info!("Restored wallet in {}m{}s", min, sec);
Ok(Some(ScannedBlockInfo {
height: end_height,
hash: "".to_owned(),
start_pmmr_index: pmmr_range.0,
last_pmmr_index: last_index,
}))
}

View file

@ -61,9 +61,20 @@ pub use api_impl::types::{
BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult,
OutputCommitMapping, SendTXArgs, VersionInfo,
};
pub use internal::restore::{check_repair, restore};
pub use internal::scan::scan;
pub use types::{
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
OutputStatus, ScannedBlockInfo, TxLogEntry, TxLogEntryType, TxWrapper, WalletBackend,
WalletInfo, WalletInst, WalletLCProvider, WalletOutputBatch,
WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch,
};
/// Helper for taking a lock on the wallet instance
#[macro_export]
macro_rules! wallet_lock {
($wallet_inst: expr, $wallet: ident) => {
let inst = $wallet_inst.clone();
let mut w_lock = inst.lock();
let w_provider = w_lock.lc_provider()?;
let $wallet = w_provider.wallet_inst()?;
};
}

View file

@ -213,6 +213,9 @@ where
keychain_mask: Option<&SecretKey>,
) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
/// Batch for use when keychain isn't available or required
fn batch_no_mask<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
/// Return the current child Index
fn current_child_index<'a>(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
@ -222,25 +225,11 @@ where
/// last verified height of outputs directly descending from the given parent key
fn last_confirmed_height<'a>(&mut self) -> Result<u64, Error>;
/// last block scanned during check_repair or restore
/// last block scanned during scan or restore
fn last_scanned_block<'a>(&mut self) -> Result<ScannedBlockInfo, Error>;
/// Attempt to restore the contents of a wallet from seed
fn restore(
&mut self,
keychain_mask: Option<&SecretKey>,
end_height: u64,
) -> Result<Option<ScannedBlockInfo>, Error>;
/// Attempt to check and fix wallet state
fn check_repair(
&mut self,
keychain_mask: Option<&SecretKey>,
delete_unconfirmed: bool,
start_height: u64,
end_height: u64,
status_cb: fn(&str),
) -> Result<ScannedBlockInfo, Error>;
/// Flag whether the wallet needs a full UTXO scan on next update attempt
fn init_status<'a>(&mut self) -> Result<WalletInitStatus, Error>;
}
/// Batch trait to update the output data backend atomically. Trying to use a
@ -277,9 +266,12 @@ where
height: u64,
) -> Result<(), Error>;
/// Save the last PMMR index that was scanned via a check_repair operation
/// Save the last PMMR index that was scanned via a scan operation
fn save_last_scanned_block(&mut self, block: ScannedBlockInfo) -> Result<(), Error>;
/// Save flag indicating whether wallet needs a full UTXO scan
fn save_init_status<'a>(&mut self, value: WalletInitStatus) -> Result<(), Error>;
/// get next tx log entry for the parent
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
@ -898,6 +890,7 @@ impl ser::Readable for ScannedBlockInfo {
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
/// Wrapper for reward output and kernel used when building a coinbase for a mining node.
/// Note: Not serializable, must be converted to necesssary "versioned" representation
/// before serializing to json to ensure compatibility with mining node.
@ -910,3 +903,27 @@ pub struct CbData {
/// Key Id
pub key_id: Option<Identifier>,
}
/// Enum to determine what amount of scanning is required for a new wallet
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WalletInitStatus {
/// Wallet is newly created and needs scanning
InitNeedsScanning,
/// Wallet is new but doesn't need scanning
InitNoScanning,
/// Wallet scan checks have been completed
InitComplete,
}
impl ser::Writeable for WalletInitStatus {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for WalletInitStatus {
fn read(reader: &mut dyn ser::Reader) -> Result<WalletInitStatus, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}

View file

@ -324,9 +324,7 @@ subcommands:
short: d
long: display
takes_value: false
- restore:
about: Restores a wallet contents from a seed file
- check:
- scan:
about: Checks a wallet's outputs against a live node, repairing and restoring missing outputs if required
args:
- delete_unconfirmed:
@ -334,3 +332,9 @@ subcommands:
short: d
long: delete_unconfirmed
takes_value: false
- start_height:
help: If given, the first block from which to start the scan (default 1)
short: h
long: start_height
default_value: "1"
takes_value: true

View file

@ -257,6 +257,18 @@ fn parse_u64(arg: &str, name: &str) -> Result<u64, ParseError> {
}
}
// As above, but optional
fn parse_u64_or_none(arg: Option<&str>) -> Option<u64> {
let val = match arg {
Some(a) => a.parse::<u64>(),
None => return None,
};
match val {
Ok(v) => Some(v),
Err(_) => None,
}
}
pub fn parse_global_args(
config: &WalletConfig,
args: &ArgMatches,
@ -694,7 +706,9 @@ pub fn parse_info_args(args: &ArgMatches) -> Result<command::InfoArgs, ParseErro
pub fn parse_check_args(args: &ArgMatches) -> Result<command::CheckArgs, ParseError> {
let delete_unconfirmed = args.is_present("delete_unconfirmed");
let start_height = parse_u64_or_none(args.value_of("start_height"));
Ok(command::CheckArgs {
start_height: start_height,
delete_unconfirmed: delete_unconfirmed,
})
}
@ -1006,10 +1020,9 @@ where
let a = arg_parse!(parse_cancel_args(&args));
command::cancel(wallet, km, a)
}
("restore", Some(_)) => command::restore(wallet, km),
("check", Some(args)) => {
("scan", Some(args)) => {
let a = arg_parse!(parse_check_args(&args));
command::check_repair(wallet, km, a)
command::scan(wallet, km, a)
}
_ => {
let msg = format!("Unknown wallet command, use 'grin-wallet help' for details");

View file

@ -393,7 +393,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::
];
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
let arg_vec = vec!["grin-wallet", "-p", "password", "check", "-d"];
let arg_vec = vec!["grin-wallet", "-p", "password", "scan", "-d"];
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
// Another file exchange, cancel this time

View file

@ -122,10 +122,12 @@ macro_rules! setup_proxy {
};
}
#[allow(dead_code)]
pub fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
#[allow(dead_code)]
pub fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);