mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-05-09 02:21:14 +03:00
Change check_repair + certain functions to lock more granularly (#252)
* 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:
parent
c518f35c8d
commit
021c34bf89
19 changed files with 415 additions and 932 deletions
api/src
controller
impls/src
libwallet/src
src
tests
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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",);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
}))
|
||||
}
|
|
@ -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()?;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue