diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 3d873edb5..f1156af79 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -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); } diff --git a/api/src/owner.rs b/api/src/owner.rs index d7025d111..e90670888 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -342,10 +342,8 @@ where refresh_from_node: bool, tx_id: Option, ) -> Result<(bool, Vec), 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, tx_slate_id: Option, ) -> Result<(bool, Vec), 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, tx_slate_id: Option, ) -> 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, 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 { - 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 = diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index aa3530ca3..4bf4171af 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -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, 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, delete_unconfirmed: bool) -> Result<(), ErrorKind> { + Owner::scan(self, None, start_height, delete_unconfirmed).map_err(|e| e.kind()) } fn node_height(&self) -> Result { @@ -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); } diff --git a/api/src/owner_rpc_s.rs b/api/src/owner_rpc_s.rs index aa8d6250f..6931eed68 100644 --- a/api/src/owner_rpc_s.rs +++ b/api/src/owner_rpc_s.rs @@ -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, + 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, + 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 { diff --git a/controller/src/command.rs b/controller/src/command.rs index d8a87349f..6e52037ff 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -805,38 +805,13 @@ where Ok(()) } -pub fn restore<'a, L, C, K>( - wallet: Arc>>>, - 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, } -pub fn check_repair<'a, L, C, K>( +pub fn scan<'a, L, C, K>( wallet: Arc>>>, 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",); diff --git a/controller/tests/check.rs b/controller/tests/check.rs index 249b9cd4b..7aa34d92e 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -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); diff --git a/controller/tests/restore.rs b/controller/tests/restore.rs deleted file mode 100644 index 4a1208bfb..000000000 --- a/controller/tests/restore.rs +++ /dev/null @@ -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 = None; - let mut dest_info: Option = None; - - let mut src_txs: Option> = None; - let mut dest_txs: Option> = None; - - let mut src_accts: Option> = None; - let mut dest_accts: Option> = 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); -} diff --git a/impls/src/backends/lmdb.rs b/impls/src/backends/lmdb.rs index ce5287420..41fd53193 100644 --- a/impls/src/backends/lmdb.rs +++ b/impls/src/backends/lmdb.rs @@ -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 + '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 { 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, 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 { - 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 { + 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 diff --git a/impls/src/lifecycle/default.rs b/impls/src/lifecycle/default.rs index a490bf408..76f9a9a5f 100644 --- a/impls/src/lifecycle/default.rs +++ b/impls/src/lifecycle/default.rs @@ -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(()) } diff --git a/impls/src/test_framework/mod.rs b/impls/src/test_framework/mod.rs index d5746ca90..6b6a06ba9 100644 --- a/impls/src/test_framework/mod.rs +++ b/impls/src/test_framework/mod.rs @@ -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) } diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index b72767b88..1f5cfb3fc 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -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>>>, keychain_mask: Option<&SecretKey>, include_spent: bool, refresh_from_node: bool, tx_id: Option, ) -> Result<(bool, Vec), 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>>>, keychain_mask: Option<&SecretKey>, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, ) -> Result<(bool, Vec), 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>>>, 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>>>, keychain_mask: Option<&SecretKey>, tx_id: Option, tx_slate_id: Option, ) -> 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>>>, keychain_mask: Option<&SecretKey>, + start_height: Option, 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)?; + update_outputs(wallet_inst.clone(), keychain_mask, true)?; + let tip = { + wallet_lock!(wallet_inst, w); + w.w2n_client().get_chain_tip()? + }; + let status_fn: fn(&str) = |m| warn!("{}", m); - let tip = 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 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>>>, keychain_mask: Option<&SecretKey>, ) -> Result 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>>>, keychain_mask: Option<&SecretKey>, update_all: bool, ) -> Result 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>>>, keychain_mask: Option<&SecretKey>, update_all: bool, ) -> Result 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>>>, keychain_mask: Option<&SecretKey>, txs: &mut Vec, ) -> Result 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(); diff --git a/libwallet/src/internal.rs b/libwallet/src/internal.rs index 90ba3a991..d2fc340be 100644 --- a/libwallet/src/internal.rs +++ b/libwallet/src/internal.rs @@ -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; diff --git a/libwallet/src/internal/restore.rs b/libwallet/src/internal/scan.rs similarity index 70% rename from libwallet/src/internal/restore.rs rename to libwallet/src/internal/scan.rs index 24267d253..1b17b5072 100644 --- a/libwallet/src/internal/restore.rs +++ b/libwallet/src/internal/scan.rs @@ -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, 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, status_cb: &F, ) -> Result<(Vec, 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 = 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>>>, keychain_mask: Option<&SecretKey>, output: OutputResult, found_parents: &mut HashMap, tx_stats: &mut Option<&mut HashMap>, ) -> 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>>>, 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>>>, 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 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 = wallet.acct_path_iter().map(|m| m.path).collect(); + let accounts: Vec = 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, 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 = 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, - })) -} diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index e4a3f3019..532ad36ae 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -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()?; + }; +} diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 8ec9f4210..6444f723b 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -213,6 +213,9 @@ where keychain_mask: Option<&SecretKey>, ) -> Result + 'a>, Error>; + /// Batch for use when keychain isn't available or required + fn batch_no_mask<'a>(&'a mut self) -> Result + 'a>, Error>; + /// Return the current child Index fn current_child_index<'a>(&mut self, parent_key_id: &Identifier) -> Result; @@ -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; - /// last block scanned during check_repair or restore + /// last block scanned during scan or restore fn last_scanned_block<'a>(&mut self) -> Result; - /// Attempt to restore the contents of a wallet from seed - fn restore( - &mut self, - keychain_mask: Option<&SecretKey>, - end_height: u64, - ) -> Result, 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; + /// Flag whether the wallet needs a full UTXO scan on next update attempt + fn init_status<'a>(&mut self) -> Result; } /// 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; @@ -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, } + +/// 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(&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 { + let data = reader.read_bytes_len_prefix()?; + serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData) + } +} diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 175712b23..1703631f4 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -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 diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 11bb9d304..1b9a7473a 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -257,6 +257,18 @@ fn parse_u64(arg: &str, name: &str) -> Result { } } +// As above, but optional +fn parse_u64_or_none(arg: Option<&str>) -> Option { + let val = match arg { + Some(a) => a.parse::(), + 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 Result { 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"); diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 464434578..2a23526f2 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -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 diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d1333123a..73e276d76 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -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);