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::api_impl::{owner, owner_updater};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
||||||
OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, WalletInfo,
|
OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, ViewWallet,
|
||||||
WalletInst, WalletLCProvider,
|
WalletInfo, WalletInst, WalletLCProvider,
|
||||||
};
|
};
|
||||||
use crate::util::logger::LoggingConfig;
|
use crate::util::logger::LoggingConfig;
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
|
@ -1181,6 +1181,88 @@ where
|
||||||
owner::get_stored_tx(&**w, tx_id, slate_id)
|
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
|
/// 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.
|
/// 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::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
||||||
OutputCommitMapping, PaymentProof, Slate, SlateVersion, Slatepack, SlatepackAddress,
|
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::logger::LoggingConfig;
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
|
@ -840,6 +840,115 @@ pub trait OwnerRpc {
|
||||||
slate_id: Option<Uuid>,
|
slate_id: Option<Uuid>,
|
||||||
) -> Result<Option<VersionedSlate>, ErrorKind>;
|
) -> 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).
|
Networked version of [Owner::scan](struct.Owner.html#method.scan).
|
||||||
|
|
||||||
|
@ -1896,6 +2005,18 @@ where
|
||||||
.map_err(|e| e.kind())
|
.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(
|
fn scan(
|
||||||
&self,
|
&self,
|
||||||
token: Token,
|
token: Token,
|
||||||
|
|
|
@ -29,6 +29,7 @@ use crate::libwallet::{
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
use crate::util::{Mutex, ZeroingString};
|
use crate::util::{Mutex, ZeroingString};
|
||||||
use crate::{controller, display};
|
use crate::{controller, display};
|
||||||
|
use ::core::time;
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -116,6 +117,80 @@ where
|
||||||
Ok(())
|
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
|
/// Arguments for listen command
|
||||||
pub struct ListenArgs {}
|
pub struct ListenArgs {}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ use crate::core::core::FeeFields;
|
||||||
use crate::core::core::{self, amount_to_hr_string};
|
use crate::core::core::{self, amount_to_hr_string};
|
||||||
use crate::core::global;
|
use crate::core::global;
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo,
|
AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, ViewWallet, WalletInfo,
|
||||||
};
|
};
|
||||||
use crate::util::ToHex;
|
use crate::util::ToHex;
|
||||||
use grin_wallet_util::OnionV3Address;
|
use grin_wallet_util::OnionV3Address;
|
||||||
|
@ -289,6 +289,98 @@ pub fn txs(
|
||||||
}
|
}
|
||||||
Ok(())
|
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
|
/// Display summary info in a pretty way
|
||||||
pub fn info(
|
pub fn info(
|
||||||
account: &str,
|
account: &str,
|
||||||
|
|
|
@ -18,8 +18,10 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::grin_core::core::hash::Hashed;
|
use crate::grin_core::core::hash::Hashed;
|
||||||
use crate::grin_core::core::Transaction;
|
use crate::grin_core::core::Transaction;
|
||||||
|
use crate::grin_keychain::ViewKey;
|
||||||
use crate::grin_util::secp::key::SecretKey;
|
use crate::grin_util::secp::key::SecretKey;
|
||||||
use crate::grin_util::Mutex;
|
use crate::grin_util::Mutex;
|
||||||
|
use crate::grin_util::ToHex;
|
||||||
use crate::util::{OnionV3Address, OnionV3AddressError};
|
use crate::util::{OnionV3Address, OnionV3AddressError};
|
||||||
|
|
||||||
use crate::api_impl::owner_updater::StatusMessage;
|
use crate::api_impl::owner_updater::StatusMessage;
|
||||||
|
@ -30,7 +32,7 @@ use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, Walle
|
||||||
use crate::{
|
use crate::{
|
||||||
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
||||||
PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs,
|
PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs,
|
||||||
TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
TxLogEntryType, ViewWallet, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||||
};
|
};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::{Error, ErrorKind};
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
@ -75,6 +77,23 @@ where
|
||||||
w.set_parent_key_id_by_name(label)
|
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
|
/// Retrieve the slatepack address for the current parent key at
|
||||||
/// the given index
|
/// the given index
|
||||||
pub fn get_slatepack_address<'a, L, C, K>(
|
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
|
/// check repair
|
||||||
/// Accepts a wallet inst instead of a raw wallet so it can
|
/// Accepts a wallet inst instead of a raw wallet so it can
|
||||||
/// lock as little as possible
|
/// lock as little as possible
|
||||||
|
|
|
@ -298,6 +298,14 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "Age error: {}", _0)]
|
#[fail(display = "Age error: {}", _0)]
|
||||||
Age(String),
|
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
|
/// Slatepack address parsing error
|
||||||
#[fail(display = "SlatepackAddress error: {}", _0)]
|
#[fail(display = "SlatepackAddress error: {}", _0)]
|
||||||
SlatepackAddress(String),
|
SlatepackAddress(String),
|
||||||
|
|
|
@ -21,10 +21,13 @@ use crate::grin_core::libtx::proof;
|
||||||
use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType};
|
use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType};
|
||||||
use crate::grin_util::secp::key::SecretKey;
|
use crate::grin_util::secp::key::SecretKey;
|
||||||
use crate::grin_util::secp::pedersen;
|
use crate::grin_util::secp::pedersen;
|
||||||
|
use crate::grin_util::secp::{ContextFlag, Secp256k1};
|
||||||
use crate::grin_util::Mutex;
|
use crate::grin_util::Mutex;
|
||||||
|
use crate::grin_util::{from_hex, ToHex};
|
||||||
use crate::internal::{keys, updater};
|
use crate::internal::{keys, updater};
|
||||||
use crate::types::*;
|
use crate::{types::*, ErrorKind};
|
||||||
use crate::{wallet_lock, Error, OutputCommitMapping};
|
use crate::{wallet_lock, Error, OutputCommitMapping};
|
||||||
|
use blake2_rfc::blake2b::blake2b;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
@ -142,6 +145,88 @@ where
|
||||||
Ok(wallet_outputs)
|
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>(
|
fn collect_chain_outputs<'a, C, K>(
|
||||||
keychain: &K,
|
keychain: &K,
|
||||||
client: C,
|
client: C,
|
||||||
|
@ -319,6 +404,53 @@ where
|
||||||
Ok(())
|
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
|
/// Check / repair wallet contents by scanning against chain
|
||||||
/// assume wallet contents have been freshly updated with contents
|
/// assume wallet contents have been freshly updated with contents
|
||||||
/// of latest block
|
/// of latest block
|
||||||
|
|
|
@ -75,7 +75,8 @@ pub use slate_versions::ser as dalek_ser;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
||||||
OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper,
|
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
|
/// 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
|
/// Serializes an Option<Duration> to and from a string
|
||||||
pub mod option_duration_as_secs {
|
pub mod option_duration_as_secs {
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
|
|
|
@ -48,6 +48,24 @@ subcommands:
|
||||||
short: c
|
short: c
|
||||||
long: create
|
long: create
|
||||||
takes_value: true
|
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:
|
- listen:
|
||||||
about: Runs the wallet in listening mode waiting for transactions
|
about: Runs the wallet in listening mode waiting for transactions
|
||||||
args:
|
args:
|
||||||
|
|
|
@ -413,6 +413,23 @@ pub fn parse_owner_api_args(
|
||||||
Ok(())
|
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> {
|
pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountArgs, ParseError> {
|
||||||
let create = match account_args.value_of("create") {
|
let create = match account_args.value_of("create") {
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -1110,6 +1127,15 @@ where
|
||||||
global_wallet_args,
|
global_wallet_args,
|
||||||
test_mode,
|
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)) => {
|
("account", Some(args)) => {
|
||||||
let a = arg_parse!(parse_account_args(&args));
|
let a = arg_parse!(parse_account_args(&args));
|
||||||
command::account(owner_api, km, a)
|
command::account(owner_api, km, a)
|
||||||
|
|
Loading…
Reference in a new issue