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:
Yeastplume 2019-11-04 21:10:05 +00:00 committed by GitHub
parent ba6c5ed0f8
commit c518f35c8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 855 additions and 358 deletions

510
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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))?
}
}
}
} }
/* /*

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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