mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-20 19:11:09 +03:00
View Wallet - fn rewind_hash & scan_rewind_hash (#632)
* fn rewind_hash & scan_rewind_hash * update comments * update doctest
This commit is contained in:
parent
b425107368
commit
d70423af57
11 changed files with 660 additions and 7 deletions
|
@ -27,8 +27,8 @@ use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, Status
|
|||
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||
use crate::libwallet::{
|
||||
AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
||||
OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, WalletInfo,
|
||||
WalletInst, WalletLCProvider,
|
||||
OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, ViewWallet,
|
||||
WalletInfo, WalletInst, WalletLCProvider,
|
||||
};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
use crate::util::secp::key::SecretKey;
|
||||
|
@ -1181,6 +1181,88 @@ where
|
|||
owner::get_stored_tx(&**w, tx_id, slate_id)
|
||||
}
|
||||
|
||||
/// Return the rewind hash of the wallet.
|
||||
/// The rewind hash when shared, help third-party to retrieve informations (outputs, balance, ...) that belongs to this wallet.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// being used.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(String)` 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(), None);
|
||||
/// let result = api_owner.scan(
|
||||
/// None,
|
||||
/// Some(20000),
|
||||
/// false,
|
||||
/// );
|
||||
///
|
||||
/// if let Ok(_) = result {
|
||||
/// // Wallet outputs should be consistent with what's on chain
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn get_rewind_hash(&self, keychain_mask: Option<&SecretKey>) -> Result<String, Error> {
|
||||
owner::get_rewind_hash(self.wallet_inst.clone(), keychain_mask)
|
||||
}
|
||||
|
||||
/// Scans the entire UTXO set from the node, identify which outputs belong to the given rewind hash view wallet.
|
||||
///
|
||||
/// This function can be used to retrieve outputs informations (outputs, balance, ...) from a rewind hash view wallet.
|
||||
///
|
||||
/// This 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.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `rewind_hash` - Rewind hash of a wallet, used to retrieve the output of a third-party wallet.
|
||||
/// * `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.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(ViewWallet)` 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(), None);
|
||||
/// let result = api_owner.scan(
|
||||
/// None,
|
||||
/// Some(20000),
|
||||
/// false,
|
||||
/// );
|
||||
///
|
||||
/// if let Ok(_) = result {
|
||||
/// // Wallet outputs should be consistent with what's on chain
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn scan_rewind_hash(
|
||||
&self,
|
||||
rewind_hash: String,
|
||||
start_height: Option<u64>,
|
||||
) -> Result<ViewWallet, Error> {
|
||||
let tx = {
|
||||
let t = self.status_tx.lock();
|
||||
t.clone()
|
||||
};
|
||||
owner::scan_rewind_hash(self.wallet_inst.clone(), rewind_hash, start_height, &tx)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::keychain::{Identifier, Keychain};
|
|||
use crate::libwallet::{
|
||||
AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
||||
OutputCommitMapping, PaymentProof, Slate, SlateVersion, Slatepack, SlatepackAddress,
|
||||
StatusMessage, TxLogEntry, VersionedSlate, WalletInfo, WalletLCProvider,
|
||||
StatusMessage, TxLogEntry, VersionedSlate, ViewWallet, WalletInfo, WalletLCProvider,
|
||||
};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||
|
@ -840,6 +840,115 @@ pub trait OwnerRpc {
|
|||
slate_id: Option<Uuid>,
|
||||
) -> Result<Option<VersionedSlate>, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::get_rewind_hash](struct.Owner.html#method.get_rewind_hash).
|
||||
```
|
||||
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||
# r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_rewind_hash",
|
||||
"params": {
|
||||
"token": "d202964900000000d302964900000000d402964900000000d502964900000000"
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,
|
||||
# r#"
|
||||
{
|
||||
"id":1,
|
||||
"jsonrpc":"2.0",
|
||||
"result":{
|
||||
"Ok":"c820c52a492b7db511c752035483d0e50e8fd3ec62544f1b99638e220a4682de"
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn get_rewind_hash(&self, token: Token) -> Result<String, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::scan_rewind_hash](struct.Owner.html#method.scan_rewind_hash).
|
||||
```
|
||||
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||
# r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "scan_rewind_hash",
|
||||
"params": {
|
||||
"rewind_hash": "c820c52a492b7db511c752035483d0e50e8fd3ec62544f1b99638e220a4682de",
|
||||
"start_height": 1
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,
|
||||
# r#"
|
||||
{
|
||||
"id":1,
|
||||
"jsonrpc":"2.0",
|
||||
"result":{
|
||||
"Ok":{
|
||||
"last_pmmr_index":8,
|
||||
"output_result":[
|
||||
{
|
||||
"commit":"08e1da9e6dc4d6e808a718b2f110a991dd775d65ce5ae408a4e1f002a4961aa9e7",
|
||||
"height":1,
|
||||
"is_coinbase":true,
|
||||
"lock_height":4,
|
||||
"mmr_index":1,
|
||||
"value":60000000000
|
||||
},
|
||||
{
|
||||
"commit":"087df32304c5d4ae8b2af0bc31e700019d722910ef87dd4eec3197b80b207e3045",
|
||||
"height":2,
|
||||
"is_coinbase":true,
|
||||
"lock_height":5,
|
||||
"mmr_index":2,
|
||||
"value":60000000000
|
||||
},
|
||||
{
|
||||
"commit":"084219d64014223a205431acfa8f8cc3e8cb8c6d04df80b26713314becf83861c7",
|
||||
"height":3,
|
||||
"is_coinbase":true,
|
||||
"lock_height":6,
|
||||
"mmr_index":4,
|
||||
"value":60000000000
|
||||
},
|
||||
{
|
||||
"commit":"09c5efc4dab05d7d16fc90168c484c13f15a142ea4e1bf93c3fad12f5e8a402598",
|
||||
"height":4,
|
||||
"is_coinbase":true,
|
||||
"lock_height":7,
|
||||
"mmr_index":5,
|
||||
"value":60000000000
|
||||
},
|
||||
{
|
||||
"commit":"08fe198e525a5937d0c5d01fa354394d2679be6df5d42064a0f7550c332fce3d9d",
|
||||
"height":5,
|
||||
"is_coinbase":true,
|
||||
"lock_height":8,
|
||||
"mmr_index":8,
|
||||
"value":60000000000
|
||||
}
|
||||
],
|
||||
"rewind_hash":"c820c52a492b7db511c752035483d0e50e8fd3ec62544f1b99638e220a4682de",
|
||||
"total_balance":300000000000
|
||||
}
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , 5, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn scan_rewind_hash(
|
||||
&self,
|
||||
rewind_hash: String,
|
||||
start_height: Option<u64>,
|
||||
) -> Result<ViewWallet, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::scan](struct.Owner.html#method.scan).
|
||||
|
||||
|
@ -1896,6 +2005,18 @@ where
|
|||
.map_err(|e| e.kind())
|
||||
}
|
||||
|
||||
fn get_rewind_hash(&self, token: Token) -> Result<String, ErrorKind> {
|
||||
Owner::get_rewind_hash(self, (&token.keychain_mask).as_ref()).map_err(|e| e.kind())
|
||||
}
|
||||
|
||||
fn scan_rewind_hash(
|
||||
&self,
|
||||
rewind_hash: String,
|
||||
start_height: Option<u64>,
|
||||
) -> Result<ViewWallet, ErrorKind> {
|
||||
Owner::scan_rewind_hash(self, rewind_hash, start_height).map_err(|e| e.kind())
|
||||
}
|
||||
|
||||
fn scan(
|
||||
&self,
|
||||
token: Token,
|
||||
|
|
|
@ -29,6 +29,7 @@ use crate::libwallet::{
|
|||
use crate::util::secp::key::SecretKey;
|
||||
use crate::util::{Mutex, ZeroingString};
|
||||
use crate::{controller, display};
|
||||
use ::core::time;
|
||||
use serde_json as json;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
|
@ -116,6 +117,80 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rewind_hash<'a, L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K>,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
|
||||
let rewind_hash = api.get_rewind_hash(m)?;
|
||||
println!();
|
||||
println!("Wallet Rewind Hash");
|
||||
println!("-------------------------------------");
|
||||
println!("{}", rewind_hash);
|
||||
println!();
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Arguments for rewind hash view wallet scan command
|
||||
pub struct ViewWalletScanArgs {
|
||||
pub rewind_hash: String,
|
||||
pub start_height: Option<u64>,
|
||||
pub backwards_from_tip: Option<u64>,
|
||||
}
|
||||
|
||||
pub fn scan_rewind_hash<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
args: ViewWalletScanArgs,
|
||||
dark_scheme: bool,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(None, None, Some(owner_api), |api, m| {
|
||||
let rewind_hash = args.rewind_hash;
|
||||
let tip_height = api.node_height(m)?.height;
|
||||
let start_height = match args.backwards_from_tip {
|
||||
Some(b) => tip_height.saturating_sub(b),
|
||||
None => match args.start_height {
|
||||
Some(s) => s,
|
||||
None => 1,
|
||||
},
|
||||
};
|
||||
warn!(
|
||||
"Starting view wallet output scan from height {} ...",
|
||||
start_height
|
||||
);
|
||||
let result = api.scan_rewind_hash(rewind_hash, Some(start_height));
|
||||
let deci_sec = time::Duration::from_millis(100);
|
||||
thread::sleep(deci_sec);
|
||||
match result {
|
||||
Ok(res) => {
|
||||
warn!("View wallet check complete");
|
||||
if res.total_balance != 0 {
|
||||
display::view_wallet_output(res.clone(), tip_height, dark_scheme)?;
|
||||
}
|
||||
display::view_wallet_balance(res.clone(), tip_height, dark_scheme);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("View wallet check failed: {}", e);
|
||||
error!("Backtrace: {}", e.backtrace().unwrap());
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Arguments for listen command
|
||||
pub struct ListenArgs {}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::core::core::FeeFields;
|
|||
use crate::core::core::{self, amount_to_hr_string};
|
||||
use crate::core::global;
|
||||
use crate::libwallet::{
|
||||
AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo,
|
||||
AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, ViewWallet, WalletInfo,
|
||||
};
|
||||
use crate::util::ToHex;
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
|
@ -289,6 +289,98 @@ pub fn txs(
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn view_wallet_balance(w: ViewWallet, cur_height: u64, dark_background_color_scheme: bool) {
|
||||
println!(
|
||||
"\n____ View Wallet Summary Info - Block Height: {} ____\n Rewind Hash - {}\n",
|
||||
cur_height, w.rewind_hash
|
||||
);
|
||||
let mut table = table!();
|
||||
|
||||
if dark_background_color_scheme {
|
||||
table.add_row(row![
|
||||
bFG->"Total Balance",
|
||||
FG->amount_to_hr_string(w.total_balance, false)
|
||||
]);
|
||||
} else {
|
||||
table.add_row(row![
|
||||
bFG->"Total Balance",
|
||||
FG->amount_to_hr_string(w.total_balance, false)
|
||||
]);
|
||||
};
|
||||
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
||||
table.printstd();
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn view_wallet_output(
|
||||
view_wallet: ViewWallet,
|
||||
cur_height: u64,
|
||||
dark_background_color_scheme: bool,
|
||||
) -> Result<(), Error> {
|
||||
println!();
|
||||
let title = format!("View Wallet Outputs - Block Height: {}", cur_height);
|
||||
|
||||
if term::stdout().is_none() {
|
||||
println!("Could not open terminal");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut t = term::stdout().unwrap();
|
||||
t.fg(term::color::MAGENTA).unwrap();
|
||||
writeln!(t, "{}", title).unwrap();
|
||||
t.reset().unwrap();
|
||||
|
||||
let mut table = table!();
|
||||
|
||||
table.set_titles(row![
|
||||
bMG->"Output Commitment",
|
||||
bMG->"MMR Index",
|
||||
bMG->"Block Height",
|
||||
bMG->"Locked Until",
|
||||
bMG->"Coinbase?",
|
||||
bMG->"# Confirms",
|
||||
bMG->"Value",
|
||||
]);
|
||||
|
||||
for m in view_wallet.output_result {
|
||||
let commit = format!("{}", m.commit);
|
||||
let index = m.mmr_index;
|
||||
let height = format!("{}", m.height);
|
||||
let lock_height = format!("{}", m.lock_height);
|
||||
let is_coinbase = format!("{}", m.is_coinbase);
|
||||
let num_confirmations = format!("{}", m.num_confirmations(cur_height));
|
||||
let value = format!("{}", core::amount_to_hr_string(m.value, false));
|
||||
|
||||
if dark_background_color_scheme {
|
||||
table.add_row(row![
|
||||
bFC->commit,
|
||||
bFB->index,
|
||||
bFB->height,
|
||||
bFB->lock_height,
|
||||
bFY->is_coinbase,
|
||||
bFB->num_confirmations,
|
||||
bFG->value,
|
||||
]);
|
||||
} else {
|
||||
table.add_row(row![
|
||||
bFD->commit,
|
||||
bFB->index,
|
||||
bFB->height,
|
||||
bFB->lock_height,
|
||||
bFD->is_coinbase,
|
||||
bFB->num_confirmations,
|
||||
bFG->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
|
||||
table.printstd();
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Display summary info in a pretty way
|
||||
pub fn info(
|
||||
account: &str,
|
||||
|
|
|
@ -18,8 +18,10 @@ use uuid::Uuid;
|
|||
|
||||
use crate::grin_core::core::hash::Hashed;
|
||||
use crate::grin_core::core::Transaction;
|
||||
use crate::grin_keychain::ViewKey;
|
||||
use crate::grin_util::secp::key::SecretKey;
|
||||
use crate::grin_util::Mutex;
|
||||
use crate::grin_util::ToHex;
|
||||
use crate::util::{OnionV3Address, OnionV3AddressError};
|
||||
|
||||
use crate::api_impl::owner_updater::StatusMessage;
|
||||
|
@ -30,7 +32,7 @@ use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, Walle
|
|||
use crate::{
|
||||
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
||||
PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs,
|
||||
TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||
TxLogEntryType, ViewWallet, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||
};
|
||||
use crate::{Error, ErrorKind};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
|
@ -75,6 +77,23 @@ where
|
|||
w.set_parent_key_id_by_name(label)
|
||||
}
|
||||
|
||||
/// Hash of the wallet root public key
|
||||
pub fn get_rewind_hash<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
wallet_lock!(wallet_inst, w);
|
||||
let keychain = w.keychain(keychain_mask)?;
|
||||
let root_public_key = keychain.public_root_key();
|
||||
let rewind_hash = ViewKey::rewind_hash(keychain.secp(), root_public_key).to_hex();
|
||||
Ok(rewind_hash)
|
||||
}
|
||||
|
||||
/// Retrieve the slatepack address for the current parent key at
|
||||
/// the given index
|
||||
pub fn get_slatepack_address<'a, L, C, K>(
|
||||
|
@ -936,6 +955,46 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Scan outputs with the rewind hash of a third-party wallet.
|
||||
/// Help to retrieve outputs information that belongs it
|
||||
pub fn scan_rewind_hash<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
rewind_hash: String,
|
||||
start_height: Option<u64>,
|
||||
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||
) -> Result<ViewWallet, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let is_hex = rewind_hash.chars().all(|c| c.is_ascii_hexdigit());
|
||||
let rewind_hash = rewind_hash.to_lowercase();
|
||||
if !(is_hex && rewind_hash.len() == 64) {
|
||||
let msg = format!("Invalid Rewind Hash");
|
||||
return Err(ErrorKind::RewindHash(msg).into());
|
||||
}
|
||||
|
||||
let tip = {
|
||||
wallet_lock!(wallet_inst, w);
|
||||
w.w2n_client().get_chain_tip()?
|
||||
};
|
||||
|
||||
let start_height = match start_height {
|
||||
Some(h) => h,
|
||||
None => 1,
|
||||
};
|
||||
|
||||
let info = scan::scan_rewind_hash(
|
||||
wallet_inst,
|
||||
rewind_hash,
|
||||
start_height,
|
||||
tip.0,
|
||||
status_send_channel,
|
||||
)?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// check repair
|
||||
/// Accepts a wallet inst instead of a raw wallet so it can
|
||||
/// lock as little as possible
|
||||
|
|
|
@ -298,6 +298,14 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Age error: {}", _0)]
|
||||
Age(String),
|
||||
|
||||
/// Rewind Hash parsing error
|
||||
#[fail(display = "Rewind Hash error: {}", _0)]
|
||||
RewindHash(String),
|
||||
|
||||
/// Nonce creation error
|
||||
#[fail(display = "Nonce error: {}", _0)]
|
||||
Nonce(String),
|
||||
|
||||
/// Slatepack address parsing error
|
||||
#[fail(display = "SlatepackAddress error: {}", _0)]
|
||||
SlatepackAddress(String),
|
||||
|
|
|
@ -21,10 +21,13 @@ use crate::grin_core::libtx::proof;
|
|||
use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType};
|
||||
use crate::grin_util::secp::key::SecretKey;
|
||||
use crate::grin_util::secp::pedersen;
|
||||
use crate::grin_util::secp::{ContextFlag, Secp256k1};
|
||||
use crate::grin_util::Mutex;
|
||||
use crate::grin_util::{from_hex, ToHex};
|
||||
use crate::internal::{keys, updater};
|
||||
use crate::types::*;
|
||||
use crate::{types::*, ErrorKind};
|
||||
use crate::{wallet_lock, Error, OutputCommitMapping};
|
||||
use blake2_rfc::blake2b::blake2b;
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
@ -142,6 +145,88 @@ where
|
|||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
fn collect_chain_outputs_rewind_hash<'a, C>(
|
||||
client: C,
|
||||
rewind_hash: String,
|
||||
start_index: u64,
|
||||
end_index: Option<u64>,
|
||||
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||
) -> Result<ViewWallet, Error>
|
||||
where
|
||||
C: NodeClient + 'a,
|
||||
{
|
||||
let batch_size = 1000;
|
||||
let start_index_stat = start_index;
|
||||
let mut start_index = start_index;
|
||||
let mut vw = ViewWallet {
|
||||
rewind_hash: rewind_hash,
|
||||
output_result: vec![],
|
||||
total_balance: 0,
|
||||
last_pmmr_index: 0,
|
||||
};
|
||||
let secp = Secp256k1::with_caps(ContextFlag::VerifyOnly);
|
||||
|
||||
loop {
|
||||
let (highest_index, last_retrieved_index, outputs) =
|
||||
client.get_outputs_by_pmmr_index(start_index, end_index, batch_size)?;
|
||||
|
||||
let range = highest_index as f64 - start_index_stat as f64;
|
||||
let progress = last_retrieved_index as f64 - start_index_stat as f64;
|
||||
let percentage_complete = cmp::min(((progress / range) * 100.0) as u8, 99);
|
||||
|
||||
let msg = format!(
|
||||
"Checking {} outputs, up to index {}. (Highest index: {})",
|
||||
outputs.len(),
|
||||
highest_index,
|
||||
last_retrieved_index,
|
||||
);
|
||||
if let Some(ref s) = status_send_channel {
|
||||
let _ = s.send(StatusMessage::Scanning(msg, percentage_complete));
|
||||
}
|
||||
|
||||
// Scanning outputs
|
||||
for output in outputs.iter() {
|
||||
let (commit, proof, is_coinbase, height, mmr_index) = output;
|
||||
let rewind_hash = from_hex(vw.rewind_hash.as_str()).map_err(|e| {
|
||||
ErrorKind::RewindHash(format!("Unable to decode rewind hash: {}", e))
|
||||
})?;
|
||||
let rewind_nonce = blake2b(32, &commit.0, &rewind_hash);
|
||||
let nonce = SecretKey::from_slice(&secp, rewind_nonce.as_bytes())
|
||||
.map_err(|e| ErrorKind::Nonce(format!("Unable to create nonce: {}", e)))?;
|
||||
let info = secp.rewind_bullet_proof(*commit, nonce.clone(), None, *proof);
|
||||
|
||||
if info.is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let info = info.unwrap();
|
||||
vw.total_balance += info.value;
|
||||
let lock_height = if *is_coinbase {
|
||||
*height + global::coinbase_maturity()
|
||||
} else {
|
||||
*height
|
||||
};
|
||||
|
||||
let output_info = ViewWalletOutputResult {
|
||||
commit: commit.to_hex(),
|
||||
value: info.value,
|
||||
height: *height,
|
||||
mmr_index: *mmr_index,
|
||||
is_coinbase: *is_coinbase,
|
||||
lock_height: lock_height,
|
||||
};
|
||||
|
||||
vw.output_result.push(output_info);
|
||||
}
|
||||
if highest_index <= last_retrieved_index {
|
||||
vw.last_pmmr_index = last_retrieved_index;
|
||||
break;
|
||||
}
|
||||
start_index = last_retrieved_index + 1;
|
||||
}
|
||||
Ok(vw)
|
||||
}
|
||||
|
||||
fn collect_chain_outputs<'a, C, K>(
|
||||
keychain: &K,
|
||||
client: C,
|
||||
|
@ -319,6 +404,53 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Scan outputs with a given rewind hash view wallet.
|
||||
/// Retrieve all outputs information that belongs to it.
|
||||
pub fn scan_rewind_hash<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
rewind_hash: String,
|
||||
start_height: u64,
|
||||
end_height: u64,
|
||||
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||
) -> Result<ViewWallet, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
if let Some(ref s) = status_send_channel {
|
||||
let _ = s.send(StatusMessage::Scanning("Starting UTXO scan".to_owned(), 0));
|
||||
}
|
||||
let client = {
|
||||
wallet_lock!(wallet_inst, w);
|
||||
w.w2n_client().clone()
|
||||
};
|
||||
// Retrieve the actual PMMR index range we're looking for
|
||||
let pmmr_range = client.height_range_to_pmmr_indices(start_height, Some(end_height))?;
|
||||
|
||||
let chain_outs = collect_chain_outputs_rewind_hash(
|
||||
client,
|
||||
rewind_hash,
|
||||
pmmr_range.0,
|
||||
Some(pmmr_range.1),
|
||||
status_send_channel,
|
||||
)?;
|
||||
|
||||
let msg = format!(
|
||||
"Identified {} wallet_outputs as belonging to this wallet",
|
||||
chain_outs.output_result.len(),
|
||||
);
|
||||
if let Some(ref s) = status_send_channel {
|
||||
let _ = s.send(StatusMessage::Scanning(msg, 99));
|
||||
}
|
||||
if let Some(ref s) = status_send_channel {
|
||||
let _ = s.send(StatusMessage::ScanningComplete(
|
||||
"Scanning Complete".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(chain_outs)
|
||||
}
|
||||
|
||||
/// Check / repair wallet contents by scanning against chain
|
||||
/// assume wallet contents have been freshly updated with contents
|
||||
/// of latest block
|
||||
|
|
|
@ -75,7 +75,8 @@ pub use slate_versions::ser as dalek_ser;
|
|||
pub use types::{
|
||||
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
||||
OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper,
|
||||
WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch,
|
||||
ViewWallet, WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||
WalletOutputBatch,
|
||||
};
|
||||
|
||||
/// Helper for taking a lock on the wallet instance
|
||||
|
|
|
@ -1019,6 +1019,45 @@ impl ser::Readable for WalletInitStatus {
|
|||
}
|
||||
}
|
||||
|
||||
/// Utility struct for return values from below
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ViewWallet {
|
||||
/// Rewind Hash used to retrieve the outputs
|
||||
pub rewind_hash: String,
|
||||
/// All outputs information that belongs to the rewind hash
|
||||
pub output_result: Vec<ViewWalletOutputResult>,
|
||||
/// total balance
|
||||
pub total_balance: u64,
|
||||
/// last pmmr index
|
||||
pub last_pmmr_index: u64,
|
||||
}
|
||||
/// Utility struct for return values from below
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ViewWalletOutputResult {
|
||||
///
|
||||
pub commit: String,
|
||||
///
|
||||
pub value: u64,
|
||||
///
|
||||
pub height: u64,
|
||||
///
|
||||
pub mmr_index: u64,
|
||||
///
|
||||
pub is_coinbase: bool,
|
||||
///
|
||||
pub lock_height: u64,
|
||||
}
|
||||
|
||||
impl ViewWalletOutputResult {
|
||||
pub fn num_confirmations(&self, tip_height: u64) -> u64 {
|
||||
if self.height > tip_height {
|
||||
return 0;
|
||||
} else {
|
||||
1 + (tip_height - self.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an Option<Duration> to and from a string
|
||||
pub mod option_duration_as_secs {
|
||||
use serde::de::Error;
|
||||
|
|
|
@ -48,6 +48,24 @@ subcommands:
|
|||
short: c
|
||||
long: create
|
||||
takes_value: true
|
||||
- rewind_hash:
|
||||
about: Return the hash of the wallet root public key.
|
||||
- scan_rewind_hash:
|
||||
about: Scan the UTXO set and return the outputs and the total of grin owned by a view wallet rewind hash.
|
||||
args:
|
||||
- rewind_hash:
|
||||
help: Rewind hash of the wallet to be scanned in order to retrieve all the outputs and balance.
|
||||
index: 1
|
||||
- start_height:
|
||||
help: If given, the first block from which to start the scan (default 1)
|
||||
short: h
|
||||
long: start_height
|
||||
takes_value: true
|
||||
- backwards_from_tip:
|
||||
help: If given, start scan b blocks back from the tip
|
||||
short: b
|
||||
long: backwards_from_tip,
|
||||
takes_value: true
|
||||
- listen:
|
||||
about: Runs the wallet in listening mode waiting for transactions
|
||||
args:
|
||||
|
|
|
@ -413,6 +413,23 @@ pub fn parse_owner_api_args(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_scan_rewind_hash_args(
|
||||
args: &ArgMatches,
|
||||
) -> Result<command::ViewWalletScanArgs, ParseError> {
|
||||
let rewind_hash = parse_required(args, "rewind_hash")?;
|
||||
let start_height = parse_u64_or_none(args.value_of("start_height"));
|
||||
let backwards_from_tip = parse_u64_or_none(args.value_of("backwards_from_tip"));
|
||||
if backwards_from_tip.is_some() && start_height.is_some() {
|
||||
let msg = format!("backwards_from tip and start_height cannot both be present");
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::ViewWalletScanArgs {
|
||||
rewind_hash: rewind_hash.into(),
|
||||
start_height,
|
||||
backwards_from_tip,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountArgs, ParseError> {
|
||||
let create = match account_args.value_of("create") {
|
||||
None => None,
|
||||
|
@ -1110,6 +1127,15 @@ where
|
|||
global_wallet_args,
|
||||
test_mode,
|
||||
),
|
||||
("rewind_hash", Some(_)) => command::rewind_hash(owner_api, km),
|
||||
("scan_rewind_hash", Some(args)) => {
|
||||
let a = arg_parse!(parse_scan_rewind_hash_args(&args));
|
||||
command::scan_rewind_hash(
|
||||
owner_api,
|
||||
a,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
)
|
||||
}
|
||||
("account", Some(args)) => {
|
||||
let a = arg_parse!(parse_account_args(&args));
|
||||
command::account(owner_api, km, a)
|
||||
|
|
Loading…
Reference in a new issue