View Wallet - fn rewind_hash & scan_rewind_hash (#632)

* fn rewind_hash & scan_rewind_hash

* update comments

* update doctest
This commit is contained in:
deevope 2021-12-14 13:23:17 +01:00 committed by GitHub
parent b425107368
commit d70423af57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 660 additions and 7 deletions

View file

@ -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.
///

View file

@ -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,

View file

@ -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 {}

View file

@ -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,

View file

@ -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

View file

@ -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),

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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:

View file

@ -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)