mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-02-01 17:01:10 +03:00
Store and use last scanned PMMR height in check_repair
(#246)
* store last scanned PMMR check index * rustfmt * fix issue where account names will be overwritten on check_repair * rustfmt * attempts to include check_repair scan as part of normal update * rustfmt * fix error on restore due to incorrect parent key id being set * addition of calls to heigt_range_to_pmmr_indices traits and implementations * rustfmt * get_chain_height -> get_chain_tip * rustfmt * retrieve height+hash from node, modify check_repair to use block heights * rustfmt * fixes from live testing * rustfmt * test cleanup and change dependencies back to grin master * rustfmt
This commit is contained in:
parent
ba6c5ed0f8
commit
c518f35c8d
16 changed files with 855 additions and 358 deletions
510
Cargo.lock
generated
510
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1232,7 +1232,13 @@ where
|
||||||
let w = w_lock.lc_provider()?.wallet_inst()?;
|
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||||
// Test keychain mask, to keep API consistent
|
// Test keychain mask, to keep API consistent
|
||||||
let _ = w.keychain(keychain_mask)?;
|
let _ = w.keychain(keychain_mask)?;
|
||||||
owner::node_height(&mut **w, keychain_mask)
|
let mut res = owner::node_height(&mut **w, keychain_mask)?;
|
||||||
|
if self.doctest_mode {
|
||||||
|
// return a consistent hash for doctest
|
||||||
|
res.header_hash =
|
||||||
|
"d4b3d3c40695afd8c7760f8fc423565f7d41310b7a4e1c4a4a7950a66f16240d".to_owned();
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LIFECYCLE FUNCTIONS
|
// LIFECYCLE FUNCTIONS
|
||||||
|
|
|
@ -1239,6 +1239,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"Ok": {
|
"Ok": {
|
||||||
|
"header_hash": "d4b3d3c40695afd8c7760f8fc423565f7d41310b7a4e1c4a4a7950a66f16240d",
|
||||||
"height": "5",
|
"height": "5",
|
||||||
"updated_from_node": true
|
"updated_from_node": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1304,6 +1304,7 @@ pub trait OwnerRpcS {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"Ok": {
|
"Ok": {
|
||||||
|
"header_hash": "d4b3d3c40695afd8c7760f8fc423565f7d41310b7a4e1c4a4a7950a66f16240d",
|
||||||
"height": "5",
|
"height": "5",
|
||||||
"updated_from_node": true
|
"updated_from_node": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ use self::core::global;
|
||||||
use grin_wallet_libwallet as libwallet;
|
use grin_wallet_libwallet as libwallet;
|
||||||
use impls::test_framework::{self, LocalWalletClient};
|
use impls::test_framework::{self, LocalWalletClient};
|
||||||
use impls::{PathToSlate, SlatePutter as _};
|
use impls::{PathToSlate, SlatePutter as _};
|
||||||
use libwallet::InitTxArgs;
|
use libwallet::{InitTxArgs, NodeClient};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use util::ZeroingString;
|
use util::ZeroingString;
|
||||||
|
@ -90,10 +90,10 @@ fn check_repair_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// add some accounts
|
// add some accounts
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
api.create_account_path(m, "account_1")?;
|
api.create_account_path(m, "named_account_1")?;
|
||||||
api.create_account_path(m, "account_2")?;
|
api.create_account_path(m, "account_2")?;
|
||||||
api.create_account_path(m, "account_3")?;
|
api.create_account_path(m, "account_3")?;
|
||||||
api.set_active_account(m, "account_1")?;
|
api.set_active_account(m, "named_account_1")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -165,6 +165,11 @@ fn check_repair_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||||
assert!(wallet1_refreshed);
|
assert!(wallet1_refreshed);
|
||||||
assert_eq!(wallet1_info.total, bh * reward);
|
assert_eq!(wallet1_info.total, bh * reward);
|
||||||
|
// And check account names haven't been splatted
|
||||||
|
let accounts = api.accounts(m)?;
|
||||||
|
assert_eq!(accounts.len(), 4);
|
||||||
|
assert!(api.set_active_account(m, "account_1").is_err());
|
||||||
|
assert!(api.set_active_account(m, "named_account_1").is_ok());
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -190,7 +195,8 @@ fn check_repair_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// check we're all locked
|
// check we're all locked
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||||
|
assert!(wallet1_refreshed);
|
||||||
assert!(wallet1_info.amount_currently_spendable == 0);
|
assert!(wallet1_info.amount_currently_spendable == 0);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
@ -438,13 +444,15 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
|
||||||
bh += cm as u64;
|
bh += cm as u64;
|
||||||
|
|
||||||
// confirm balances
|
// confirm balances
|
||||||
|
// since info is now performing a partial check_repair, these should confirm
|
||||||
|
// as containing all outputs
|
||||||
let info = wallet_info!(wallet1.clone(), mask1)?;
|
let info = wallet_info!(wallet1.clone(), mask1)?;
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 6);
|
assert_eq!(info.amount_currently_spendable, base_amount * 21);
|
||||||
assert_eq!(info.total, base_amount * 6);
|
assert_eq!(info.total, base_amount * 21);
|
||||||
|
|
||||||
let info = wallet_info!(wallet2.clone(), mask2)?;
|
let info = wallet_info!(wallet2.clone(), mask2)?;
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 15);
|
assert_eq!(info.amount_currently_spendable, base_amount * 21);
|
||||||
assert_eq!(info.total, base_amount * 15);
|
assert_eq!(info.total, base_amount * 21);
|
||||||
|
|
||||||
// Now there should be outputs on the chain using the same
|
// Now there should be outputs on the chain using the same
|
||||||
// seed + BIP32 path.
|
// seed + BIP32 path.
|
||||||
|
@ -480,6 +488,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,
|
// 3) If I recover from seed and start using the wallet without restoring,
|
||||||
// check_repair should restore the older outputs
|
// check_repair should restore the older outputs
|
||||||
|
// update, again, since check_repair is run automatically, balances on both
|
||||||
|
// wallets should turn out the same
|
||||||
send_to_dest!(
|
send_to_dest!(
|
||||||
miner.clone(),
|
miner.clone(),
|
||||||
miner_mask,
|
miner_mask,
|
||||||
|
@ -509,8 +519,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
|
||||||
wallet::controller::owner_single_use(wallet4.clone(), mask4, |api, m| {
|
wallet::controller::owner_single_use(wallet4.clone(), mask4, |api, m| {
|
||||||
let info = wallet_info!(wallet4.clone(), m)?;
|
let info = wallet_info!(wallet4.clone(), m)?;
|
||||||
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
||||||
assert_eq!(outputs.len(), 3);
|
assert_eq!(outputs.len(), 9);
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 24);
|
assert_eq!(info.amount_currently_spendable, base_amount * 45);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -564,8 +574,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
|
||||||
wallet::controller::owner_single_use(wallet6.clone(), mask6, |api, m| {
|
wallet::controller::owner_single_use(wallet6.clone(), mask6, |api, m| {
|
||||||
let info = wallet_info!(wallet6.clone(), m)?;
|
let info = wallet_info!(wallet6.clone(), m)?;
|
||||||
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
||||||
assert_eq!(outputs.len(), 3);
|
assert_eq!(outputs.len(), 12);
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 33);
|
assert_eq!(info.amount_currently_spendable, base_amount * 78);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -650,8 +660,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
|
||||||
api.set_active_account(m, "default")?;
|
api.set_active_account(m, "default")?;
|
||||||
let info = wallet_info!(wallet7.clone(), m)?;
|
let info = wallet_info!(wallet7.clone(), m)?;
|
||||||
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
||||||
assert_eq!(outputs.len(), 3);
|
assert_eq!(outputs.len(), 15);
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 42);
|
assert_eq!(info.amount_currently_spendable, base_amount * 120);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -706,8 +716,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
|
||||||
wallet::controller::owner_single_use(wallet9.clone(), mask9, |api, m| {
|
wallet::controller::owner_single_use(wallet9.clone(), mask9, |api, m| {
|
||||||
let info = wallet_info!(wallet9.clone(), m)?;
|
let info = wallet_info!(wallet9.clone(), m)?;
|
||||||
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
||||||
assert_eq!(outputs.len(), 3);
|
assert_eq!(outputs.len(), 6);
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 15);
|
assert_eq!(info.amount_currently_spendable, base_amount * 21);
|
||||||
api.check_repair(m, true)?;
|
api.check_repair(m, true)?;
|
||||||
let info = wallet_info!(wallet9.clone(), m)?;
|
let info = wallet_info!(wallet9.clone(), m)?;
|
||||||
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
|
||||||
|
@ -745,9 +755,94 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er
|
||||||
thread::sleep(Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(200));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Testing output scanning functionality, easier here as the testing framework
|
||||||
|
// is all here
|
||||||
|
fn output_scanning_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||||
|
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||||
|
let chain = wallet_proxy.chain.clone();
|
||||||
|
// Create a new wallet test client, and set its queues to communicate with the
|
||||||
|
// proxy
|
||||||
|
create_wallet_and_add!(
|
||||||
|
client1,
|
||||||
|
wallet1,
|
||||||
|
mask1_i,
|
||||||
|
test_dir,
|
||||||
|
"wallet1",
|
||||||
|
None,
|
||||||
|
&mut wallet_proxy,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
let mask1 = (&mask1_i).as_ref();
|
||||||
|
thread::spawn(move || {
|
||||||
|
if let Err(e) = wallet_proxy.run() {
|
||||||
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Do some mining
|
||||||
|
let bh = 20u64;
|
||||||
|
let _ =
|
||||||
|
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||||
|
|
||||||
|
// Now some chain scanning
|
||||||
|
{
|
||||||
|
// Entire range should be correct
|
||||||
|
let ranges = client1.height_range_to_pmmr_indices(1, None)?;
|
||||||
|
assert_eq!(ranges, (1, 38));
|
||||||
|
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
|
||||||
|
assert_eq!(outputs.2.len(), 20);
|
||||||
|
|
||||||
|
// Basic range should be correct
|
||||||
|
let ranges = client1.height_range_to_pmmr_indices(1, Some(14))?;
|
||||||
|
assert_eq!(ranges, (1, 25));
|
||||||
|
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
|
||||||
|
println!(
|
||||||
|
"Last Index: {}, Max: {}, Outputs.len: {}",
|
||||||
|
outputs.0,
|
||||||
|
outputs.1,
|
||||||
|
outputs.2.len()
|
||||||
|
);
|
||||||
|
assert_eq!(outputs.2.len(), 14);
|
||||||
|
|
||||||
|
// mid range
|
||||||
|
let ranges = client1.height_range_to_pmmr_indices(5, Some(14))?;
|
||||||
|
assert_eq!(ranges, (8, 25));
|
||||||
|
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
|
||||||
|
println!(
|
||||||
|
"Last Index: {}, Max: {}, Outputs.len: {}",
|
||||||
|
outputs.0,
|
||||||
|
outputs.1,
|
||||||
|
outputs.2.len()
|
||||||
|
);
|
||||||
|
for o in outputs.2.clone() {
|
||||||
|
println!("height: {}, mmr_index: {}", o.3, o.4);
|
||||||
|
}
|
||||||
|
assert_eq!(outputs.2.len(), 10);
|
||||||
|
|
||||||
|
// end
|
||||||
|
let ranges = client1.height_range_to_pmmr_indices(5, None)?;
|
||||||
|
assert_eq!(ranges, (8, 38));
|
||||||
|
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
|
||||||
|
println!(
|
||||||
|
"Last Index: {}, Max: {}, Outputs.len: {}",
|
||||||
|
outputs.0,
|
||||||
|
outputs.1,
|
||||||
|
outputs.2.len()
|
||||||
|
);
|
||||||
|
for o in outputs.2.clone() {
|
||||||
|
println!("height: {}, mmr_index: {}", o.3, o.4);
|
||||||
|
}
|
||||||
|
assert_eq!(outputs.2.len(), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_repair() {
|
fn check_repair() {
|
||||||
let test_dir = "test_output/check_repair";
|
let test_dir = "test_output/check_repair";
|
||||||
|
setup(test_dir);
|
||||||
if let Err(e) = check_repair_impl(test_dir) {
|
if let Err(e) = check_repair_impl(test_dir) {
|
||||||
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||||
}
|
}
|
||||||
|
@ -763,3 +858,13 @@ fn two_wallets_one_seed() {
|
||||||
}
|
}
|
||||||
clean_output_dir(test_dir);
|
clean_output_dir(test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_scanning() {
|
||||||
|
let test_dir = "test_output/output_scanning";
|
||||||
|
setup(test_dir);
|
||||||
|
if let Err(e) = output_scanning_impl(test_dir) {
|
||||||
|
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||||
|
}
|
||||||
|
clean_output_dir(test_dir);
|
||||||
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ use crate::core::core::Transaction;
|
||||||
use crate::core::ser;
|
use crate::core::ser;
|
||||||
use crate::libwallet::{check_repair, restore};
|
use crate::libwallet::{check_repair, restore};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, Context, Error, ErrorKind, NodeClient, OutputData, TxLogEntry, WalletBackend,
|
AcctPathMapping, Context, Error, ErrorKind, NodeClient, OutputData, ScannedBlockInfo,
|
||||||
WalletOutputBatch,
|
TxLogEntry, WalletBackend, WalletOutputBatch,
|
||||||
};
|
};
|
||||||
use crate::util::secp::constants::SECRET_KEY_SIZE;
|
use crate::util::secp::constants::SECRET_KEY_SIZE;
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
|
@ -53,6 +53,8 @@ const PRIVATE_TX_CONTEXT_PREFIX: u8 = 'p' as u8;
|
||||||
const TX_LOG_ENTRY_PREFIX: u8 = 't' as u8;
|
const TX_LOG_ENTRY_PREFIX: u8 = 't' as u8;
|
||||||
const TX_LOG_ID_PREFIX: u8 = 'i' as u8;
|
const TX_LOG_ID_PREFIX: u8 = 'i' as u8;
|
||||||
const ACCOUNT_PATH_MAPPING_PREFIX: u8 = 'a' 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";
|
||||||
|
|
||||||
/// test to see if database files exist in the current directory. If so,
|
/// test to see if database files exist in the current directory. If so,
|
||||||
/// use a DB backend for all operations
|
/// use a DB backend for all operations
|
||||||
|
@ -395,6 +397,18 @@ where
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_child_index<'a>(&mut self, parent_key_id: &Identifier) -> Result<u32, Error> {
|
||||||
|
let index = {
|
||||||
|
let batch = self.db.batch()?;
|
||||||
|
let deriv_key = to_key(DERIV_PREFIX, &mut parent_key_id.to_bytes().to_vec());
|
||||||
|
match batch.get_ser(&deriv_key)? {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(index)
|
||||||
|
}
|
||||||
|
|
||||||
fn next_child<'a>(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error> {
|
fn next_child<'a>(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error> {
|
||||||
let parent_key_id = self.parent_key_id.clone();
|
let parent_key_id = self.parent_key_id.clone();
|
||||||
let mut deriv_idx = {
|
let mut deriv_idx = {
|
||||||
|
@ -428,18 +442,51 @@ where
|
||||||
Ok(last_confirmed_height)
|
Ok(last_confirmed_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore(&mut self, keychain_mask: Option<&SecretKey>) -> Result<(), Error> {
|
fn last_scanned_block<'a>(&mut self) -> Result<ScannedBlockInfo, Error> {
|
||||||
restore(self, keychain_mask).context(ErrorKind::Restore)?;
|
let batch = self.db.batch()?;
|
||||||
Ok(())
|
let scanned_block_key = to_key(
|
||||||
|
LAST_SCANNED_BLOCK,
|
||||||
|
&mut LAST_SCANNED_KEY.as_bytes().to_vec(),
|
||||||
|
);
|
||||||
|
let last_scanned_block = match batch.get_ser(&scanned_block_key)? {
|
||||||
|
Some(b) => b,
|
||||||
|
None => ScannedBlockInfo {
|
||||||
|
height: 0,
|
||||||
|
hash: "".to_owned(),
|
||||||
|
start_pmmr_index: 0,
|
||||||
|
last_pmmr_index: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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(
|
fn check_repair(
|
||||||
&mut self,
|
&mut self,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
delete_unconfirmed: bool,
|
delete_unconfirmed: bool,
|
||||||
) -> Result<(), Error> {
|
start_height: u64,
|
||||||
check_repair(self, keychain_mask, delete_unconfirmed).context(ErrorKind::Restore)?;
|
end_height: u64,
|
||||||
Ok(())
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +605,19 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save_last_scanned_block(&mut self, block_info: ScannedBlockInfo) -> Result<(), Error> {
|
||||||
|
let pmmr_index_key = to_key(
|
||||||
|
LAST_SCANNED_BLOCK,
|
||||||
|
&mut LAST_SCANNED_KEY.as_bytes().to_vec(),
|
||||||
|
);
|
||||||
|
self.db
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.put_ser(&pmmr_index_key, &block_info)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> {
|
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());
|
let deriv_key = to_key(DERIV_PREFIX, &mut parent_id.to_bytes().to_vec());
|
||||||
self.db
|
self.db
|
||||||
|
|
|
@ -47,8 +47,8 @@ impl HTTPNodeClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allow returning the chain height without needing a wallet instantiated
|
/// Allow returning the chain height without needing a wallet instantiated
|
||||||
pub fn chain_height(&self) -> Result<u64, libwallet::Error> {
|
pub fn chain_height(&self) -> Result<(u64, String), libwallet::Error> {
|
||||||
self.get_chain_height()
|
self.get_chain_tip()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ impl NodeClient for HTTPNodeClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the chain tip from a given node
|
/// Return the chain tip from a given node
|
||||||
fn get_chain_height(&self) -> Result<u64, libwallet::Error> {
|
fn get_chain_tip(&self) -> Result<(u64, String), libwallet::Error> {
|
||||||
let addr = self.node_url();
|
let addr = self.node_url();
|
||||||
let url = format!("{}/v1/chain", addr);
|
let url = format!("{}/v1/chain", addr);
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
@ -128,7 +128,7 @@ impl NodeClient for HTTPNodeClient {
|
||||||
error!("Get chain height error: {}", e);
|
error!("Get chain height error: {}", e);
|
||||||
Err(libwallet::ErrorKind::ClientCallback(report).into())
|
Err(libwallet::ErrorKind::ClientCallback(report).into())
|
||||||
}
|
}
|
||||||
Ok(r) => Ok(r.height),
|
Ok(r) => Ok((r.height, r.last_block_pushed)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,8 @@ impl NodeClient for HTTPNodeClient {
|
||||||
|
|
||||||
fn get_outputs_by_pmmr_index(
|
fn get_outputs_by_pmmr_index(
|
||||||
&self,
|
&self,
|
||||||
start_height: u64,
|
start_index: u64,
|
||||||
|
end_index: Option<u64>,
|
||||||
max_outputs: u64,
|
max_outputs: u64,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
|
@ -241,7 +242,11 @@ impl NodeClient for HTTPNodeClient {
|
||||||
libwallet::Error,
|
libwallet::Error,
|
||||||
> {
|
> {
|
||||||
let addr = self.node_url();
|
let addr = self.node_url();
|
||||||
let query_param = format!("start_index={}&max={}", start_height, max_outputs);
|
let mut query_param = format!("start_index={}&max={}", start_index, max_outputs);
|
||||||
|
|
||||||
|
if let Some(e) = end_index {
|
||||||
|
query_param = format!("{}&end_index={}", query_param, e);
|
||||||
|
};
|
||||||
|
|
||||||
let url = format!("{}/v1/txhashset/outputs?{}", addr, query_param,);
|
let url = format!("{}/v1/txhashset/outputs?{}", addr, query_param,);
|
||||||
|
|
||||||
|
@ -279,6 +284,33 @@ impl NodeClient for HTTPNodeClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn height_range_to_pmmr_indices(
|
||||||
|
&self,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: Option<u64>,
|
||||||
|
) -> Result<(u64, u64), libwallet::Error> {
|
||||||
|
debug!("Indices start");
|
||||||
|
let addr = self.node_url();
|
||||||
|
let mut query_param = format!("start_height={}", start_height);
|
||||||
|
if let Some(e) = end_height {
|
||||||
|
query_param = format!("{}&end_height={}", query_param, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = format!("{}/v1/txhashset/heightstopmmr?{}", addr, query_param,);
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
match client.get::<api::OutputListing>(url.as_str(), self.node_api_secret()) {
|
||||||
|
Ok(o) => Ok((o.last_retrieved_index, o.highest_index)),
|
||||||
|
Err(e) => {
|
||||||
|
// if we got anything other than 200 back from server, bye
|
||||||
|
error!("heightstopmmr: error contacting {}. Error: {}", addr, e);
|
||||||
|
let report = format!(": {}", e);
|
||||||
|
Err(libwallet::ErrorKind::ClientCallback(report))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -73,10 +73,11 @@ fn get_kernel_local(
|
||||||
fn get_outputs_by_pmmr_index_local(
|
fn get_outputs_by_pmmr_index_local(
|
||||||
chain: Arc<chain::Chain>,
|
chain: Arc<chain::Chain>,
|
||||||
start_index: u64,
|
start_index: u64,
|
||||||
|
end_index: Option<u64>,
|
||||||
max: u64,
|
max: u64,
|
||||||
) -> api::OutputListing {
|
) -> api::OutputListing {
|
||||||
let outputs = chain
|
let outputs = chain
|
||||||
.unspent_outputs_by_insertion_index(start_index, max)
|
.unspent_outputs_by_pmmr_index(start_index, max, end_index)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
api::OutputListing {
|
api::OutputListing {
|
||||||
last_retrieved_index: outputs.0,
|
last_retrieved_index: outputs.0,
|
||||||
|
@ -91,6 +92,22 @@ fn get_outputs_by_pmmr_index_local(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get output listing in a given block range
|
||||||
|
fn height_range_to_pmmr_indices_local(
|
||||||
|
chain: Arc<chain::Chain>,
|
||||||
|
start_index: u64,
|
||||||
|
end_index: Option<u64>,
|
||||||
|
) -> api::OutputListing {
|
||||||
|
let indices = chain
|
||||||
|
.block_height_range_to_pmmr_indices(start_index, end_index)
|
||||||
|
.unwrap();
|
||||||
|
api::OutputListing {
|
||||||
|
last_retrieved_index: indices.0,
|
||||||
|
highest_index: indices.1,
|
||||||
|
outputs: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a block with a given reward to the chain and mines it
|
/// Adds a block with a given reward to the chain and mines it
|
||||||
pub fn add_block_with_reward(
|
pub fn add_block_with_reward(
|
||||||
chain: &Chain,
|
chain: &Chain,
|
||||||
|
|
|
@ -146,9 +146,10 @@ where
|
||||||
let m = self.rx.recv().unwrap();
|
let m = self.rx.recv().unwrap();
|
||||||
trace!("Wallet Client Proxy Received: {:?}", m);
|
trace!("Wallet Client Proxy Received: {:?}", m);
|
||||||
let resp = match m.method.as_ref() {
|
let resp = match m.method.as_ref() {
|
||||||
"get_chain_height" => self.get_chain_height(m)?,
|
"get_chain_tip" => self.get_chain_tip(m)?,
|
||||||
"get_outputs_from_node" => self.get_outputs_from_node(m)?,
|
"get_outputs_from_node" => self.get_outputs_from_node(m)?,
|
||||||
"get_outputs_by_pmmr_index" => self.get_outputs_by_pmmr_index(m)?,
|
"get_outputs_by_pmmr_index" => self.get_outputs_by_pmmr_index(m)?,
|
||||||
|
"height_range_to_pmmr_indices" => self.height_range_to_pmmr_indices(m)?,
|
||||||
"send_tx_slate" => self.send_tx_slate(m)?,
|
"send_tx_slate" => self.send_tx_slate(m)?,
|
||||||
"post_tx" => self.post_tx(m)?,
|
"post_tx" => self.post_tx(m)?,
|
||||||
"get_kernel" => self.get_kernel(m)?,
|
"get_kernel" => self.get_kernel(m)?,
|
||||||
|
@ -243,15 +244,18 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get chain height
|
/// get chain height
|
||||||
fn get_chain_height(
|
fn get_chain_tip(
|
||||||
&mut self,
|
&mut self,
|
||||||
m: WalletProxyMessage,
|
m: WalletProxyMessage,
|
||||||
) -> Result<WalletProxyMessage, libwallet::Error> {
|
) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||||
|
let height = self.chain.head().unwrap().height;
|
||||||
|
let hash = util::to_hex(self.chain.head().unwrap().last_block_h.to_vec());
|
||||||
|
|
||||||
Ok(WalletProxyMessage {
|
Ok(WalletProxyMessage {
|
||||||
sender_id: "node".to_owned(),
|
sender_id: "node".to_owned(),
|
||||||
dest: m.sender_id,
|
dest: m.sender_id,
|
||||||
method: m.method,
|
method: m.method,
|
||||||
body: format!("{}", self.chain.head().unwrap().height).to_owned(),
|
body: format!("{},{}", height, hash),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +295,35 @@ where
|
||||||
let split = m.body.split(",").collect::<Vec<&str>>();
|
let split = m.body.split(",").collect::<Vec<&str>>();
|
||||||
let start_index = split[0].parse::<u64>().unwrap();
|
let start_index = split[0].parse::<u64>().unwrap();
|
||||||
let max = split[1].parse::<u64>().unwrap();
|
let max = split[1].parse::<u64>().unwrap();
|
||||||
let ol = super::get_outputs_by_pmmr_index_local(self.chain.clone(), start_index, max);
|
let end_index = split[2].parse::<u64>().unwrap();
|
||||||
|
let end_index = match end_index {
|
||||||
|
0 => None,
|
||||||
|
e => Some(e),
|
||||||
|
};
|
||||||
|
let ol =
|
||||||
|
super::get_outputs_by_pmmr_index_local(self.chain.clone(), start_index, end_index, max);
|
||||||
|
Ok(WalletProxyMessage {
|
||||||
|
sender_id: "node".to_owned(),
|
||||||
|
dest: m.sender_id,
|
||||||
|
method: m.method,
|
||||||
|
body: serde_json::to_string(&ol).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get api outputs by height
|
||||||
|
fn height_range_to_pmmr_indices(
|
||||||
|
&mut self,
|
||||||
|
m: WalletProxyMessage,
|
||||||
|
) -> Result<WalletProxyMessage, libwallet::Error> {
|
||||||
|
let split = m.body.split(",").collect::<Vec<&str>>();
|
||||||
|
let start_index = split[0].parse::<u64>().unwrap();
|
||||||
|
let end_index = split[1].parse::<u64>().unwrap();
|
||||||
|
let end_index = match end_index {
|
||||||
|
0 => None,
|
||||||
|
e => Some(e),
|
||||||
|
};
|
||||||
|
let ol =
|
||||||
|
super::height_range_to_pmmr_indices_local(self.chain.clone(), start_index, end_index);
|
||||||
Ok(WalletProxyMessage {
|
Ok(WalletProxyMessage {
|
||||||
sender_id: "node".to_owned(),
|
sender_id: "node".to_owned(),
|
||||||
dest: m.sender_id,
|
dest: m.sender_id,
|
||||||
|
@ -412,11 +444,11 @@ impl NodeClient for LocalWalletClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the chain tip from a given node
|
/// Return the chain tip from a given node
|
||||||
fn get_chain_height(&self) -> Result<u64, libwallet::Error> {
|
fn get_chain_tip(&self) -> Result<(u64, String), libwallet::Error> {
|
||||||
let m = WalletProxyMessage {
|
let m = WalletProxyMessage {
|
||||||
sender_id: self.id.clone(),
|
sender_id: self.id.clone(),
|
||||||
dest: self.node_url().to_owned(),
|
dest: self.node_url().to_owned(),
|
||||||
method: "get_chain_height".to_owned(),
|
method: "get_chain_tip".to_owned(),
|
||||||
body: "".to_owned(),
|
body: "".to_owned(),
|
||||||
};
|
};
|
||||||
{
|
{
|
||||||
|
@ -427,12 +459,15 @@ impl NodeClient for LocalWalletClient {
|
||||||
}
|
}
|
||||||
let r = self.rx.lock();
|
let r = self.rx.lock();
|
||||||
let m = r.recv().unwrap();
|
let m = r.recv().unwrap();
|
||||||
trace!("Received get_chain_height response: {:?}", m.clone());
|
trace!("Received get_chain_tip response: {:?}", m.clone());
|
||||||
Ok(m.body
|
let res = m
|
||||||
.parse::<u64>()
|
.body
|
||||||
|
.parse::<String>()
|
||||||
.context(libwallet::ErrorKind::ClientCallback(
|
.context(libwallet::ErrorKind::ClientCallback(
|
||||||
"Parsing get_height response".to_owned(),
|
"Parsing get_height response".to_owned(),
|
||||||
))?)
|
))?;
|
||||||
|
let split: Vec<&str> = res.split(",").collect();
|
||||||
|
Ok((split[0].parse::<u64>().unwrap(), split[1].to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve outputs from node
|
/// Retrieve outputs from node
|
||||||
|
@ -513,7 +548,8 @@ impl NodeClient for LocalWalletClient {
|
||||||
|
|
||||||
fn get_outputs_by_pmmr_index(
|
fn get_outputs_by_pmmr_index(
|
||||||
&self,
|
&self,
|
||||||
start_height: u64,
|
start_index: u64,
|
||||||
|
end_index: Option<u64>,
|
||||||
max_outputs: u64,
|
max_outputs: u64,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
|
@ -524,7 +560,11 @@ impl NodeClient for LocalWalletClient {
|
||||||
libwallet::Error,
|
libwallet::Error,
|
||||||
> {
|
> {
|
||||||
// start index, max
|
// start index, max
|
||||||
let query_str = format!("{},{}", start_height, max_outputs);
|
let mut query_str = format!("{},{}", start_index, max_outputs);
|
||||||
|
match end_index {
|
||||||
|
Some(e) => query_str = format!("{},{}", query_str, e),
|
||||||
|
None => query_str = format!("{},0", query_str),
|
||||||
|
};
|
||||||
let m = WalletProxyMessage {
|
let m = WalletProxyMessage {
|
||||||
sender_id: self.id.clone(),
|
sender_id: self.id.clone(),
|
||||||
dest: self.node_url().to_owned(),
|
dest: self.node_url().to_owned(),
|
||||||
|
@ -560,6 +600,36 @@ impl NodeClient for LocalWalletClient {
|
||||||
}
|
}
|
||||||
Ok((o.highest_index, o.last_retrieved_index, api_outputs))
|
Ok((o.highest_index, o.last_retrieved_index, api_outputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn height_range_to_pmmr_indices(
|
||||||
|
&self,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: Option<u64>,
|
||||||
|
) -> Result<(u64, u64), libwallet::Error> {
|
||||||
|
// start index, max
|
||||||
|
let mut query_str = format!("{}", start_height);
|
||||||
|
match end_height {
|
||||||
|
Some(e) => query_str = format!("{},{}", query_str, e),
|
||||||
|
None => query_str = format!("{},0", query_str),
|
||||||
|
};
|
||||||
|
let m = WalletProxyMessage {
|
||||||
|
sender_id: self.id.clone(),
|
||||||
|
dest: self.node_url().to_owned(),
|
||||||
|
method: "height_range_to_pmmr_indices".to_owned(),
|
||||||
|
body: query_str,
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let p = self.proxy_tx.lock();
|
||||||
|
p.send(m).context(libwallet::ErrorKind::ClientCallback(
|
||||||
|
"Get outputs within height range send".to_owned(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = self.rx.lock();
|
||||||
|
let m = r.recv().unwrap();
|
||||||
|
let o: api::OutputListing = serde_json::from_str(&m.body).unwrap();
|
||||||
|
Ok((o.last_retrieved_index, o.highest_index))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unsafe impl<'a, L, C, K> Send for WalletProxy<'a, L, C, K>
|
unsafe impl<'a, L, C, K> Send for WalletProxy<'a, L, C, K>
|
||||||
where
|
where
|
||||||
|
|
|
@ -84,7 +84,7 @@ where
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = update_outputs(w, keychain_mask, false)?;
|
validated = update_wallet_state(w, keychain_mask, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -116,14 +116,10 @@ where
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = update_outputs(w, keychain_mask, false)?;
|
validated = update_wallet_state(w, keychain_mask, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut txs = updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
|
let txs = updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
|
||||||
|
|
||||||
if refresh_from_node {
|
|
||||||
validated = update_txs_via_kernel(w, keychain_mask, &mut txs)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((validated, txs))
|
Ok((validated, txs))
|
||||||
}
|
}
|
||||||
|
@ -144,7 +140,7 @@ where
|
||||||
|
|
||||||
let mut validated = false;
|
let mut validated = false;
|
||||||
if refresh_from_node {
|
if refresh_from_node {
|
||||||
validated = update_outputs(w, keychain_mask, false)?;
|
validated = update_wallet_state(w, keychain_mask, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let wallet_info = updater::retrieve_info(&mut *w, &parent_key_id, minimum_confirmations)?;
|
let wallet_info = updater::retrieve_info(&mut *w, &parent_key_id, minimum_confirmations)?;
|
||||||
|
@ -336,7 +332,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
// update slate current height
|
// update slate current height
|
||||||
ret_slate.height = w.w2n_client().get_chain_height()?;
|
ret_slate.height = w.w2n_client().get_chain_tip()?.0;
|
||||||
|
|
||||||
let context = tx::add_inputs_to_slate(
|
let context = tx::add_inputs_to_slate(
|
||||||
&mut *w,
|
&mut *w,
|
||||||
|
@ -421,7 +417,7 @@ where
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
let parent_key_id = w.parent_key_id();
|
let parent_key_id = w.parent_key_id();
|
||||||
if !update_outputs(w, keychain_mask, false)? {
|
if !update_wallet_state(w, keychain_mask, false)? {
|
||||||
return Err(ErrorKind::TransactionCancellationError(
|
return Err(ErrorKind::TransactionCancellationError(
|
||||||
"Can't contact running Grin node. Not Cancelling.",
|
"Can't contact running Grin node. Not Cancelling.",
|
||||||
))?;
|
))?;
|
||||||
|
@ -478,7 +474,15 @@ where
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
w.restore(keychain_mask)
|
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
|
/// check repair
|
||||||
|
@ -493,7 +497,20 @@ where
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
update_outputs(w, keychain_mask, true)?;
|
update_outputs(w, keychain_mask, true)?;
|
||||||
w.check_repair(keychain_mask, delete_unconfirmed)
|
let status_fn: fn(&str) = |m| warn!("{}", m);
|
||||||
|
let tip = w.w2n_client().get_chain_tip()?;
|
||||||
|
|
||||||
|
// for now, just start from 1
|
||||||
|
// TODO: only do this if hashes of last stored block don't match chain
|
||||||
|
// TODO: Provide parameter to manually override on command line
|
||||||
|
let mut info = w.check_repair(keychain_mask, delete_unconfirmed, 1, tip.0, status_fn)?;
|
||||||
|
info.hash = tip.1;
|
||||||
|
|
||||||
|
let mut batch = w.batch(keychain_mask)?;
|
||||||
|
batch.save_last_scanned_block(info)?;
|
||||||
|
batch.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// node height
|
/// node height
|
||||||
|
@ -506,10 +523,11 @@ where
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
let res = w.w2n_client().get_chain_height();
|
let res = w.w2n_client().get_chain_tip();
|
||||||
match res {
|
match res {
|
||||||
Ok(height) => Ok(NodeHeightResult {
|
Ok(r) => Ok(NodeHeightResult {
|
||||||
height,
|
height: r.0,
|
||||||
|
header_hash: r.1,
|
||||||
updated_from_node: true,
|
updated_from_node: true,
|
||||||
}),
|
}),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -520,11 +538,62 @@ where
|
||||||
};
|
};
|
||||||
Ok(NodeHeightResult {
|
Ok(NodeHeightResult {
|
||||||
height,
|
height,
|
||||||
|
header_hash: "".to_owned(),
|
||||||
updated_from_node: false,
|
updated_from_node: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// 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,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
update_all: bool,
|
||||||
|
) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
let parent_key_id = w.parent_key_id().clone();
|
||||||
|
let mut result;
|
||||||
|
// Step 1: Update outputs and transactions purely based on UTXO state
|
||||||
|
result = update_outputs(w, 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)?;
|
||||||
|
if !result {
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Scan back a bit on the chain
|
||||||
|
let tip = w.w2n_client().get_chain_tip()?;
|
||||||
|
|
||||||
|
// 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 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)?;
|
||||||
|
info.hash = tip.1;
|
||||||
|
|
||||||
|
let mut batch = w.batch(keychain_mask)?;
|
||||||
|
batch.save_last_scanned_block(info)?;
|
||||||
|
batch.commit()?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to update outputs in wallet, return whether it was successful
|
/// Attempt to update outputs in wallet, return whether it was successful
|
||||||
fn update_outputs<'a, T: ?Sized, C, K>(
|
fn update_outputs<'a, T: ?Sized, C, K>(
|
||||||
|
@ -561,8 +630,8 @@ where
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
let parent_key_id = w.parent_key_id();
|
let parent_key_id = w.parent_key_id();
|
||||||
let height = match w.w2n_client().get_chain_height() {
|
let height = match w.w2n_client().get_chain_tip() {
|
||||||
Ok(h) => h,
|
Ok(h) => h.0,
|
||||||
Err(_) => return Ok(false),
|
Err(_) => return Ok(false),
|
||||||
};
|
};
|
||||||
for tx in txs.iter_mut() {
|
for tx in txs.iter_mut() {
|
||||||
|
|
|
@ -197,6 +197,8 @@ pub struct NodeHeightResult {
|
||||||
/// Last known height
|
/// Last known height
|
||||||
#[serde(with = "secp_ser::string_or_u64")]
|
#[serde(with = "secp_ser::string_or_u64")]
|
||||||
pub height: u64,
|
pub height: u64,
|
||||||
|
/// Hash
|
||||||
|
pub header_hash: String,
|
||||||
/// Whether this height was updated from the node
|
/// Whether this height was updated from the node
|
||||||
pub updated_from_node: bool,
|
pub updated_from_node: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
/// Utility struct for return values from below
|
/// Utility struct for return values from below
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct OutputResult {
|
struct OutputResult {
|
||||||
///
|
///
|
||||||
pub commit: pedersen::Commitment,
|
pub commit: pedersen::Commitment,
|
||||||
|
@ -59,22 +59,23 @@ struct RestoredTxStats {
|
||||||
pub num_outputs: usize,
|
pub num_outputs: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identify_utxo_outputs<'a, T, C, K>(
|
fn identify_utxo_outputs<'a, T, C, K, F>(
|
||||||
wallet: &mut T,
|
wallet: &mut T,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
|
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
|
||||||
|
status_cb: &F,
|
||||||
) -> Result<Vec<OutputResult>, Error>
|
) -> Result<Vec<OutputResult>, Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<'a, C, K>,
|
T: WalletBackend<'a, C, K>,
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
|
F: Fn(&str),
|
||||||
{
|
{
|
||||||
let mut wallet_outputs: Vec<OutputResult> = Vec::new();
|
let mut wallet_outputs: Vec<OutputResult> = Vec::new();
|
||||||
|
status_cb(&format!(
|
||||||
warn!(
|
|
||||||
"Scanning {} outputs in the current Grin utxo set",
|
"Scanning {} outputs in the current Grin utxo set",
|
||||||
outputs.len(),
|
outputs.len(),
|
||||||
);
|
));
|
||||||
|
|
||||||
let keychain = wallet.keychain(keychain_mask)?;
|
let keychain = wallet.keychain(keychain_mask)?;
|
||||||
let legacy_builder = proof::LegacyProofBuilder::new(&keychain);
|
let legacy_builder = proof::LegacyProofBuilder::new(&keychain);
|
||||||
|
@ -115,13 +116,13 @@ where
|
||||||
*height
|
*height
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
status_cb(&format!(
|
||||||
"Output found: {:?}, amount: {:?}, key_id: {:?}, mmr_index: {},",
|
"Output found: {:?}, amount: {:?}, key_id: {:?}, mmr_index: {},",
|
||||||
commit, amount, key_id, mmr_index,
|
commit, amount, key_id, mmr_index,
|
||||||
);
|
));
|
||||||
|
|
||||||
if switch != SwitchCommitmentType::Regular {
|
if switch != SwitchCommitmentType::Regular {
|
||||||
warn!("Unexpected switch commitment type {:?}", switch);
|
status_cb(&format!("Unexpected switch commitment type {:?}", switch))
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet_outputs.push(OutputResult {
|
wallet_outputs.push(OutputResult {
|
||||||
|
@ -138,41 +139,48 @@ where
|
||||||
Ok(wallet_outputs)
|
Ok(wallet_outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_chain_outputs<'a, T, C, K>(
|
fn collect_chain_outputs<'a, T, C, K, F>(
|
||||||
wallet: &mut T,
|
wallet: &mut T,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
) -> Result<Vec<OutputResult>, Error>
|
start_index: u64,
|
||||||
|
end_index: Option<u64>,
|
||||||
|
status_cb: &F,
|
||||||
|
) -> Result<(Vec<OutputResult>, u64), Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<'a, C, K>,
|
T: WalletBackend<'a, C, K>,
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
|
F: Fn(&str),
|
||||||
{
|
{
|
||||||
let batch_size = 1000;
|
let batch_size = 1000;
|
||||||
let mut start_index = 1;
|
let mut start_index = start_index;
|
||||||
let mut result_vec: Vec<OutputResult> = vec![];
|
let mut result_vec: Vec<OutputResult> = vec![];
|
||||||
|
let last_retrieved_return_index;
|
||||||
loop {
|
loop {
|
||||||
let (highest_index, last_retrieved_index, outputs) = wallet
|
let (highest_index, last_retrieved_index, outputs) = wallet
|
||||||
.w2n_client()
|
.w2n_client()
|
||||||
.get_outputs_by_pmmr_index(start_index, batch_size)?;
|
.get_outputs_by_pmmr_index(start_index, end_index, batch_size)?;
|
||||||
warn!(
|
status_cb(&format!(
|
||||||
"Checking {} outputs, up to index {}. (Highest index: {})",
|
"Checking {} outputs, up to index {}. (Highest index: {})",
|
||||||
outputs.len(),
|
outputs.len(),
|
||||||
highest_index,
|
highest_index,
|
||||||
last_retrieved_index,
|
last_retrieved_index,
|
||||||
);
|
));
|
||||||
|
|
||||||
result_vec.append(&mut identify_utxo_outputs(
|
result_vec.append(&mut identify_utxo_outputs(
|
||||||
wallet,
|
wallet,
|
||||||
keychain_mask,
|
keychain_mask,
|
||||||
outputs.clone(),
|
outputs.clone(),
|
||||||
|
status_cb,
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
if highest_index == last_retrieved_index {
|
if highest_index <= last_retrieved_index {
|
||||||
|
last_retrieved_return_index = last_retrieved_index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
start_index = last_retrieved_index + 1;
|
start_index = last_retrieved_index + 1;
|
||||||
}
|
}
|
||||||
Ok(result_vec)
|
Ok((result_vec, last_retrieved_return_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -191,6 +199,8 @@ where
|
||||||
let commit = wallet.calc_commit_for_cache(keychain_mask, output.value, &output.key_id)?;
|
let commit = wallet.calc_commit_for_cache(keychain_mask, output.value, &output.key_id)?;
|
||||||
let mut batch = wallet.batch(keychain_mask)?;
|
let mut batch = wallet.batch(keychain_mask)?;
|
||||||
|
|
||||||
|
error!("RESTORING OUTPUT: {:?}", output);
|
||||||
|
|
||||||
let parent_key_id = output.key_id.parent_path();
|
let parent_key_id = output.key_id.parent_path();
|
||||||
if !found_parents.contains_key(&parent_key_id) {
|
if !found_parents.contains_key(&parent_key_id) {
|
||||||
found_parents.insert(parent_key_id.clone(), 0);
|
found_parents.insert(parent_key_id.clone(), 0);
|
||||||
|
@ -304,23 +314,39 @@ where
|
||||||
/// Check / repair wallet contents
|
/// Check / repair wallet contents
|
||||||
/// assume wallet contents have been freshly updated with contents
|
/// assume wallet contents have been freshly updated with contents
|
||||||
/// of latest block
|
/// of latest block
|
||||||
pub fn check_repair<'a, T, C, K>(
|
pub fn check_repair<'a, T, C, K, F>(
|
||||||
wallet: &mut T,
|
wallet: &mut T,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
delete_unconfirmed: bool,
|
delete_unconfirmed: bool,
|
||||||
) -> Result<(), Error>
|
start_height: u64,
|
||||||
|
end_height: u64,
|
||||||
|
status_cb: F,
|
||||||
|
) -> Result<ScannedBlockInfo, Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<'a, C, K>,
|
T: WalletBackend<'a, C, K>,
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
|
F: Fn(&str),
|
||||||
{
|
{
|
||||||
// First, get a definitive list of outputs we own from the chain
|
// First, get a definitive list of outputs we own from the chain
|
||||||
warn!("Starting wallet check.");
|
status_cb("Starting UTXO scan");
|
||||||
let chain_outs = collect_chain_outputs(wallet, keychain_mask)?;
|
|
||||||
warn!(
|
// 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 (chain_outs, last_index) = collect_chain_outputs(
|
||||||
|
wallet,
|
||||||
|
keychain_mask,
|
||||||
|
pmmr_range.0,
|
||||||
|
Some(pmmr_range.1),
|
||||||
|
&status_cb,
|
||||||
|
)?;
|
||||||
|
status_cb(&format!(
|
||||||
"Identified {} wallet_outputs as belonging to this wallet",
|
"Identified {} wallet_outputs as belonging to this wallet",
|
||||||
chain_outs.len(),
|
chain_outs.len(),
|
||||||
);
|
));
|
||||||
|
|
||||||
// Now, get all outputs owned by this wallet (regardless of account)
|
// Now, get all outputs owned by this wallet (regardless of account)
|
||||||
let wallet_outputs = {
|
let wallet_outputs = {
|
||||||
|
@ -351,11 +377,11 @@ where
|
||||||
// mark problem spent outputs as unspent (confirmed against a short-lived fork, for example)
|
// mark problem spent outputs as unspent (confirmed against a short-lived fork, for example)
|
||||||
for m in accidental_spend_outs.into_iter() {
|
for m in accidental_spend_outs.into_iter() {
|
||||||
let mut o = m.0;
|
let mut o = m.0;
|
||||||
warn!(
|
status_cb(&format!(
|
||||||
"Output for {} with ID {} ({:?}) marked as spent but exists in UTXO set. \
|
"Output for {} with ID {} ({:?}) marked as spent but exists in UTXO set. \
|
||||||
Marking unspent and cancelling any associated transaction log entries.",
|
Marking unspent and cancelling any associated transaction log entries.",
|
||||||
o.value, o.key_id, m.1.commit,
|
o.value, o.key_id, m.1.commit,
|
||||||
);
|
));
|
||||||
o.status = OutputStatus::Unspent;
|
o.status = OutputStatus::Unspent;
|
||||||
// any transactions associated with this should be cancelled
|
// any transactions associated with this should be cancelled
|
||||||
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
|
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
|
||||||
|
@ -368,11 +394,11 @@ where
|
||||||
|
|
||||||
// Restore missing outputs, adding transaction for it back to the log
|
// Restore missing outputs, adding transaction for it back to the log
|
||||||
for m in missing_outs.into_iter() {
|
for m in missing_outs.into_iter() {
|
||||||
warn!(
|
status_cb(&format!(
|
||||||
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set but not in wallet. \
|
"Confirmed output for {} with ID {} ({:?}, index {}) exists in UTXO set but not in wallet. \
|
||||||
Restoring.",
|
Restoring.",
|
||||||
m.value, m.key_id, m.commit,
|
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, keychain_mask, m, &mut found_parents, &mut None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,11 +406,11 @@ where
|
||||||
// Unlock locked outputs
|
// Unlock locked outputs
|
||||||
for m in locked_outs.into_iter() {
|
for m in locked_outs.into_iter() {
|
||||||
let mut o = m.0;
|
let mut o = m.0;
|
||||||
warn!(
|
status_cb(&format!(
|
||||||
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set and is locked. \
|
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set and is locked. \
|
||||||
Unlocking and cancelling associated transaction log entries.",
|
Unlocking and cancelling associated transaction log entries.",
|
||||||
o.value, o.key_id, m.1.commit,
|
o.value, o.key_id, m.1.commit,
|
||||||
);
|
));
|
||||||
o.status = OutputStatus::Unspent;
|
o.status = OutputStatus::Unspent;
|
||||||
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
|
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
|
||||||
let mut batch = wallet.batch(keychain_mask)?;
|
let mut batch = wallet.batch(keychain_mask)?;
|
||||||
|
@ -399,11 +425,11 @@ where
|
||||||
// Delete unconfirmed outputs
|
// Delete unconfirmed outputs
|
||||||
for m in unconfirmed_outs.into_iter() {
|
for m in unconfirmed_outs.into_iter() {
|
||||||
let o = m.output.clone();
|
let o = m.output.clone();
|
||||||
warn!(
|
status_cb(&format!(
|
||||||
"Unconfirmed output for {} with ID {} ({:?}) not in UTXO set. \
|
"Unconfirmed output for {} with ID {} ({:?}) not in UTXO set. \
|
||||||
Deleting and cancelling associated transaction log entries.",
|
Deleting and cancelling associated transaction log entries.",
|
||||||
o.value, o.key_id, m.commit,
|
o.value, o.key_id, m.commit,
|
||||||
);
|
));
|
||||||
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
|
cancel_tx_log_entry(wallet, keychain_mask, &o)?;
|
||||||
let mut batch = wallet.batch(keychain_mask)?;
|
let mut batch = wallet.batch(keychain_mask)?;
|
||||||
batch.delete(&o.key_id, &o.mmr_index)?;
|
batch.delete(&o.key_id, &o.mmr_index)?;
|
||||||
|
@ -413,24 +439,39 @@ where
|
||||||
|
|
||||||
// restore labels, account paths and child derivation indices
|
// restore labels, account paths and child derivation indices
|
||||||
let label_base = "account";
|
let label_base = "account";
|
||||||
let mut acct_index = 1;
|
let accounts: Vec<Identifier> = wallet.acct_path_iter().map(|m| m.path).collect();
|
||||||
|
let mut acct_index = accounts.len();
|
||||||
for (path, max_child_index) in found_parents.iter() {
|
for (path, max_child_index) in found_parents.iter() {
|
||||||
// default path already exists
|
// Only restore paths that don't exist
|
||||||
if *path != ExtKeychain::derive_key_id(2, 0, 0, 0, 0) {
|
if !accounts.contains(path) {
|
||||||
let label = format!("{}_{}", label_base, acct_index);
|
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(wallet, keychain_mask, &label, path)?;
|
||||||
acct_index += 1;
|
acct_index += 1;
|
||||||
}
|
}
|
||||||
let mut batch = wallet.batch(keychain_mask)?;
|
let current_child_index = wallet.current_child_index(&path)?;
|
||||||
debug!("Next child for account {} is {}", path, max_child_index + 1);
|
if *max_child_index >= current_child_index {
|
||||||
batch.save_child_index(path, max_child_index + 1)?;
|
let mut batch = wallet.batch(keychain_mask)?;
|
||||||
batch.commit()?;
|
debug!("Next child for account {} is {}", path, max_child_index + 1);
|
||||||
|
batch.save_child_index(path, max_child_index + 1)?;
|
||||||
|
batch.commit()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
Ok(ScannedBlockInfo {
|
||||||
|
height: end_height,
|
||||||
|
hash: "".to_owned(),
|
||||||
|
start_pmmr_index: pmmr_range.0,
|
||||||
|
last_pmmr_index: last_index,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restore a wallet
|
/// Restore a wallet
|
||||||
pub fn restore<'a, T, C, K>(wallet: &mut T, keychain_mask: Option<&SecretKey>) -> Result<(), Error>
|
pub fn restore<'a, T, C, K>(
|
||||||
|
wallet: &mut T,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
end_height: u64,
|
||||||
|
) -> Result<Option<ScannedBlockInfo>, Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<'a, C, K>,
|
T: WalletBackend<'a, C, K>,
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
|
@ -440,13 +481,24 @@ where
|
||||||
let is_empty = wallet.iter().next().is_none();
|
let is_empty = wallet.iter().next().is_none();
|
||||||
if !is_empty {
|
if !is_empty {
|
||||||
error!("Not restoring. Please back up and remove existing db directory first.");
|
error!("Not restoring. Please back up and remove existing db directory first.");
|
||||||
return Ok(());
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
warn!("Starting restore.");
|
warn!("Starting restore.");
|
||||||
|
|
||||||
let result_vec = collect_chain_outputs(wallet, keychain_mask)?;
|
// 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!(
|
warn!(
|
||||||
"Identified {} wallet_outputs as belonging to this wallet",
|
"Identified {} wallet_outputs as belonging to this wallet",
|
||||||
|
@ -485,6 +537,7 @@ where
|
||||||
t.amount_credited = s.amount_credited;
|
t.amount_credited = s.amount_credited;
|
||||||
t.num_outputs = s.num_outputs;
|
t.num_outputs = s.num_outputs;
|
||||||
t.update_confirmation_ts();
|
t.update_confirmation_ts();
|
||||||
|
error!("SAVING TX RESTORE {:?}", t);
|
||||||
batch.save_tx_log_entry(t, &path)?;
|
batch.save_tx_log_entry(t, &path)?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
}
|
}
|
||||||
|
@ -499,5 +552,10 @@ where
|
||||||
sec %= 60;
|
sec %= 60;
|
||||||
info!("Restored wallet in {}m{}s", min, sec);
|
info!("Restored wallet in {}m{}s", min, sec);
|
||||||
|
|
||||||
Ok(())
|
Ok(Some(ScannedBlockInfo {
|
||||||
|
height: end_height,
|
||||||
|
hash: "".to_owned(),
|
||||||
|
start_pmmr_index: pmmr_range.0,
|
||||||
|
last_pmmr_index: last_index,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ where
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
let current_height = wallet.w2n_client().get_chain_height()?;
|
let current_height = wallet.w2n_client().get_chain_tip()?.0;
|
||||||
let mut slate = Slate::blank(num_participants);
|
let mut slate = Slate::blank(num_participants);
|
||||||
if use_test_rng {
|
if use_test_rng {
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@ where
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
// Get lock height
|
// Get lock height
|
||||||
let current_height = wallet.w2n_client().get_chain_height()?;
|
let current_height = wallet.w2n_client().get_chain_tip()?.0;
|
||||||
// ensure outputs we're selecting are up to date
|
// ensure outputs we're selecting are up to date
|
||||||
updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?;
|
updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?;
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ where
|
||||||
C: NodeClient + 'a,
|
C: NodeClient + 'a,
|
||||||
K: Keychain + 'a,
|
K: Keychain + 'a,
|
||||||
{
|
{
|
||||||
let height = wallet.w2n_client().get_chain_height()?;
|
let height = wallet.w2n_client().get_chain_tip()?.0;
|
||||||
refresh_output_state(wallet, keychain_mask, height, parent_key_id, update_all)?;
|
refresh_output_state(wallet, keychain_mask, height, parent_key_id, update_all)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -355,6 +355,7 @@ where
|
||||||
let api_outputs = wallet
|
let api_outputs = wallet
|
||||||
.w2n_client()
|
.w2n_client()
|
||||||
.get_outputs_from_node(wallet_output_keys)?;
|
.get_outputs_from_node(wallet_output_keys)?;
|
||||||
|
|
||||||
apply_api_outputs(
|
apply_api_outputs(
|
||||||
wallet,
|
wallet,
|
||||||
keychain_mask,
|
keychain_mask,
|
||||||
|
|
|
@ -64,6 +64,6 @@ pub use api_impl::types::{
|
||||||
pub use internal::restore::{check_repair, restore};
|
pub use internal::restore::{check_repair, restore};
|
||||||
pub use types::{
|
pub use types::{
|
||||||
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
||||||
OutputStatus, TxLogEntry, TxLogEntryType, TxWrapper, WalletBackend, WalletInfo, WalletInst,
|
OutputStatus, ScannedBlockInfo, TxLogEntry, TxLogEntryType, TxWrapper, WalletBackend,
|
||||||
WalletLCProvider, WalletOutputBatch,
|
WalletInfo, WalletInst, WalletLCProvider, WalletOutputBatch,
|
||||||
};
|
};
|
||||||
|
|
|
@ -213,21 +213,34 @@ where
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
|
) -> 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>;
|
||||||
|
|
||||||
/// Next child ID when we want to create a new output, based on current parent
|
/// Next child ID when we want to create a new output, based on current parent
|
||||||
fn next_child<'a>(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error>;
|
fn next_child<'a>(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error>;
|
||||||
|
|
||||||
/// last verified height of outputs directly descending from the given parent key
|
/// last verified height of outputs directly descending from the given parent key
|
||||||
fn last_confirmed_height<'a>(&mut self) -> Result<u64, Error>;
|
fn last_confirmed_height<'a>(&mut self) -> Result<u64, Error>;
|
||||||
|
|
||||||
|
/// last block scanned during check_repair or restore
|
||||||
|
fn last_scanned_block<'a>(&mut self) -> Result<ScannedBlockInfo, Error>;
|
||||||
|
|
||||||
/// Attempt to restore the contents of a wallet from seed
|
/// Attempt to restore the contents of a wallet from seed
|
||||||
fn restore(&mut self, keychain_mask: Option<&SecretKey>) -> Result<(), Error>;
|
fn restore(
|
||||||
|
&mut self,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
end_height: u64,
|
||||||
|
) -> Result<Option<ScannedBlockInfo>, Error>;
|
||||||
|
|
||||||
/// Attempt to check and fix wallet state
|
/// Attempt to check and fix wallet state
|
||||||
fn check_repair(
|
fn check_repair(
|
||||||
&mut self,
|
&mut self,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
delete_unconfirmed: bool,
|
delete_unconfirmed: bool,
|
||||||
) -> Result<(), Error>;
|
start_height: u64,
|
||||||
|
end_height: u64,
|
||||||
|
status_cb: fn(&str),
|
||||||
|
) -> Result<ScannedBlockInfo, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Batch trait to update the output data backend atomically. Trying to use a
|
/// Batch trait to update the output data backend atomically. Trying to use a
|
||||||
|
@ -264,6 +277,9 @@ where
|
||||||
height: u64,
|
height: u64,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Save the last PMMR index that was scanned via a check_repair operation
|
||||||
|
fn save_last_scanned_block(&mut self, block: ScannedBlockInfo) -> Result<(), Error>;
|
||||||
|
|
||||||
/// get next tx log entry for the parent
|
/// get next tx log entry for the parent
|
||||||
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
|
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
|
||||||
|
|
||||||
|
@ -323,8 +339,8 @@ pub trait NodeClient: Send + Sync + Clone {
|
||||||
/// by the node. Result can be cached for later use
|
/// by the node. Result can be cached for later use
|
||||||
fn get_version_info(&mut self) -> Option<NodeVersionInfo>;
|
fn get_version_info(&mut self) -> Option<NodeVersionInfo>;
|
||||||
|
|
||||||
/// retrieves the current tip from the specified grin node
|
/// retrieves the current tip (height, hash) from the specified grin node
|
||||||
fn get_chain_height(&self) -> Result<u64, Error>;
|
fn get_chain_tip(&self) -> Result<(u64, String), Error>;
|
||||||
|
|
||||||
/// Get a kernel and the height of the block it's included in. Returns
|
/// Get a kernel and the height of the block it's included in. Returns
|
||||||
/// (tx_kernel, height, mmr_index)
|
/// (tx_kernel, height, mmr_index)
|
||||||
|
@ -350,6 +366,7 @@ pub trait NodeClient: Send + Sync + Clone {
|
||||||
fn get_outputs_by_pmmr_index(
|
fn get_outputs_by_pmmr_index(
|
||||||
&self,
|
&self,
|
||||||
start_height: u64,
|
start_height: u64,
|
||||||
|
end_height: Option<u64>,
|
||||||
max_outputs: u64,
|
max_outputs: u64,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
|
@ -359,6 +376,15 @@ pub trait NodeClient: Send + Sync + Clone {
|
||||||
),
|
),
|
||||||
Error,
|
Error,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/// Return the pmmr indices representing the outputs between a given
|
||||||
|
/// set of block heights
|
||||||
|
/// (start pmmr index, end pmmr index)
|
||||||
|
fn height_range_to_pmmr_indices(
|
||||||
|
&self,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: Option<u64>,
|
||||||
|
) -> Result<(u64, u64), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node version info
|
/// Node version info
|
||||||
|
@ -847,6 +873,31 @@ pub struct TxWrapper {
|
||||||
pub tx_hex: String,
|
pub tx_hex: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store details of the last scanned block
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ScannedBlockInfo {
|
||||||
|
/// Node chain height (corresponding to the last PMMR index scanned)
|
||||||
|
pub height: u64,
|
||||||
|
/// Hash of tip
|
||||||
|
pub hash: String,
|
||||||
|
/// Starting PMMR Index
|
||||||
|
pub start_pmmr_index: u64,
|
||||||
|
/// Last PMMR Index
|
||||||
|
pub last_pmmr_index: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::Writeable for ScannedBlockInfo {
|
||||||
|
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 ScannedBlockInfo {
|
||||||
|
fn read(reader: &mut dyn ser::Reader) -> Result<ScannedBlockInfo, ser::Error> {
|
||||||
|
let data = reader.read_bytes_len_prefix()?;
|
||||||
|
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.
|
/// 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
|
/// Note: Not serializable, must be converted to necesssary "versioned" representation
|
||||||
/// before serializing to json to ensure compatibility with mining node.
|
/// before serializing to json to ensure compatibility with mining node.
|
||||||
|
|
Loading…
Reference in a new issue