From 9e0b3b68621ba4b75110557855090473ffd3bdc9 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Thu, 14 Jun 2018 17:02:05 +0100 Subject: [PATCH] Store additional wallet detail and WalletInfo cleanup (#1167) * adding wallet detail file, clean up wallet info output * rustfmt * ensure change outputs aren't written early * rustfmt * travis problems AGAIN * file wallet explicit types --- servers/tests/dandelion.rs | 13 +++-- servers/tests/framework/mod.rs | 3 +- servers/tests/wallet.rs | 14 ++--- src/bin/grin.rs | 5 +- wallet/src/display.rs | 20 ++++--- wallet/src/file_wallet.rs | 63 ++++++++++++++++++---- wallet/src/libwallet/api.rs | 8 ++- wallet/src/libwallet/controller.rs | 2 +- wallet/src/libwallet/internal/selection.rs | 63 ++++++++++++---------- wallet/src/libwallet/internal/tx.rs | 5 +- wallet/src/libwallet/internal/updater.rs | 43 +++++++-------- wallet/src/libwallet/types.rs | 41 ++++++++++---- wallet/tests/common/mod.rs | 3 +- 13 files changed, 177 insertions(+), 106 deletions(-) diff --git a/servers/tests/dandelion.rs b/servers/tests/dandelion.rs index 3f7d91f6e..937c12086 100644 --- a/servers/tests/dandelion.rs +++ b/servers/tests/dandelion.rs @@ -27,15 +27,16 @@ extern crate grin_wallet as wallet; mod framework; -use std::{thread, time}; -use std::sync::{Arc, Mutex}; use framework::{LocalServerContainer, LocalServerContainerConfig}; +use std::sync::{Arc, Mutex}; +use std::{thread, time}; use util::LOGGER; /// Start 1 node mining, 1 non mining node and two wallets. -/// Then send a transaction from one wallet to another and propagate it a stem transaction -/// but without stem relay and check if the transaction is still broadcasted. +/// Then send a transaction from one wallet to another and propagate it a stem +/// transaction but without stem relay and check if the transaction is still +/// broadcasted. #[test] #[ignore] fn test_dandelion_timeout() { @@ -155,9 +156,7 @@ fn test_dandelion_timeout() { // The transaction should be waiting in the node stempool thus cannot be mined. println!("Recipient wallet info: {:?}", recipient_info); - assert!( - recipient_info.data_confirmed && recipient_info.amount_awaiting_confirmation == 50000000000 - ); + assert!(recipient_info.amount_awaiting_confirmation == 50000000000); // Wait for stem timeout thread::sleep(time::Duration::from_millis(35000)); diff --git a/servers/tests/framework/mod.rs b/servers/tests/framework/mod.rs index 254bd0c1f..672008fa4 100644 --- a/servers/tests/framework/mod.rs +++ b/servers/tests/framework/mod.rs @@ -303,7 +303,8 @@ impl LocalServerContainer { let mut wallet = FileWallet::new(config.clone(), "") .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); wallet.keychain = Some(keychain); - wallet::libwallet::internal::updater::retrieve_info(&mut wallet, true).unwrap() + let _ = wallet::libwallet::internal::updater::refresh_outputs(&mut wallet); + wallet::libwallet::internal::updater::retrieve_info(&mut wallet).unwrap() } pub fn send_amount_to( diff --git a/servers/tests/wallet.rs b/servers/tests/wallet.rs index b70c877c8..c0fdd59f5 100644 --- a/servers/tests/wallet.rs +++ b/servers/tests/wallet.rs @@ -114,7 +114,7 @@ fn basic_wallet_transactions() { 1, "not_all", "http://127.0.0.1:20002", - true, + false, ); //Wait for a confirmation @@ -125,9 +125,7 @@ fn basic_wallet_transactions() { let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); println!("Recipient wallet info: {:?}", recipient_info); - assert!( - recipient_info.data_confirmed && recipient_info.amount_currently_spendable == 50000000000 - ); + assert!(recipient_info.amount_currently_spendable == 50000000000); warn!( LOGGER, @@ -140,7 +138,7 @@ fn basic_wallet_transactions() { 1, "not_all", "http://127.0.0.1:20002", - true, + false, ); } @@ -151,9 +149,7 @@ fn basic_wallet_transactions() { recipient_info ); - assert!( - recipient_info.data_confirmed && recipient_info.amount_currently_spendable == 60000000000 - ); + assert!(recipient_info.amount_currently_spendable == 60000000000); //send some cash right back LocalServerContainer::send_amount_to( &recp_wallet_config, @@ -161,7 +157,7 @@ fn basic_wallet_transactions() { 1, "all", "http://127.0.0.1:10002", - true, + false, ); thread::sleep(time::Duration::from_millis(5000)); diff --git a/src/bin/grin.rs b/src/bin/grin.rs index 7314f1d06..438d2469c 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -681,13 +681,14 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { Ok(()) } ("info", Some(_)) => { - let _res = wallet::display::info(&api.retrieve_summary_info(true)?) - .unwrap_or_else(|e| { + let (validated, wallet_info) = + api.retrieve_summary_info(true).unwrap_or_else(|e| { panic!( "Error getting wallet info: {:?} Config: {:?}", e, wallet_config ) }); + wallet::display::info(&wallet_info, validated); Ok(()) } ("outputs", Some(_)) => { diff --git a/wallet/src/display.rs b/wallet/src/display.rs index 369956ce1..920421154 100644 --- a/wallet/src/display.rs +++ b/wallet/src/display.rs @@ -74,15 +74,15 @@ pub fn outputs(cur_height: u64, validated: bool, outputs: Vec) -> Re } /// Display summary info in a pretty way -pub fn info(wallet_info: &WalletInfo) -> Result<(), Error> { +pub fn info(wallet_info: &WalletInfo, validated: bool) { println!( - "\n____ Wallet Summary Info at {} ({}) ____\n", - wallet_info.current_height, wallet_info.data_confirmed_from + "\n____ Wallet Summary Info as of {} ____\n", + wallet_info.last_confirmed_height ); let mut table = table!( [bFG->"Total", FG->amount_to_hr_string(wallet_info.total)], [bFY->"Awaiting Confirmation", FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation)], - [bFY->"Confirmed but Still Locked", FY->amount_to_hr_string(wallet_info.amount_confirmed_but_locked)], + [bFY->"Immature Coinbase", FY->amount_to_hr_string(wallet_info.amount_immature)], [bFG->"Currently Spendable", FG->amount_to_hr_string(wallet_info.amount_currently_spendable)], [Fw->"---------", Fw->"---------"], [Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(wallet_info.amount_locked)] @@ -90,13 +90,11 @@ pub fn info(wallet_info: &WalletInfo) -> Result<(), Error> { table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.printstd(); println!(); - - if !wallet_info.data_confirmed { + if !validated { println!( - "\nWARNING: Failed to verify wallet contents with grin server. \ - Above info is maybe not fully updated or invalid! \ - Check that your `grin server` is OK, or see `wallet help restore`" + "\nWARNING: Wallet failed to verify data against a live chain. \ + The above is from local cache and only valid up to the given height! \ + (is your `grin server` offline or broken?)" ); - }; - Ok(()) + } } diff --git a/wallet/src/file_wallet.rs b/wallet/src/file_wallet.rs index cfdf0022f..9f3c80190 100644 --- a/wallet/src/file_wallet.rs +++ b/wallet/src/file_wallet.rs @@ -37,8 +37,10 @@ use client; use libtx::slate::Slate; use libwallet; use libwallet::types::{BlockFees, BlockIdentifier, CbData, MerkleProofWrapper, OutputData, - TxWrapper, WalletBackend, WalletClient}; + TxWrapper, WalletBackend, WalletClient, WalletDetails}; +const DETAIL_FILE: &'static str = "wallet.det"; +const DET_BCK_FILE: &'static str = "wallet.detbck"; const DAT_FILE: &'static str = "wallet.dat"; const BCK_FILE: &'static str = "wallet.bck"; const LOCK_FILE: &'static str = "wallet.lock"; @@ -176,12 +178,18 @@ pub struct FileWallet { passphrase: String, /// List of outputs pub outputs: HashMap, + /// Details + pub details: WalletDetails, /// Data file path pub data_file_path: String, /// Backup file path pub backup_file_path: String, /// lock file path pub lock_file_path: String, + /// details file path + pub details_file_path: String, + /// Details backup file path + pub details_bak_path: String, } impl WalletBackend for FileWallet @@ -274,10 +282,10 @@ where // We successfully acquired the lock - so do what needs to be done. self.read_or_create_paths() .context(libwallet::ErrorKind::CallbackImpl("Lock Error"))?; - self.write(&self.backup_file_path) + self.write(&self.backup_file_path, &self.details_bak_path) .context(libwallet::ErrorKind::CallbackImpl("Write Error"))?; let res = f(self); - self.write(&self.data_file_path) + self.write(&self.data_file_path, &self.details_file_path) .context(libwallet::ErrorKind::CallbackImpl("Write Error"))?; // delete the lock file @@ -318,14 +326,20 @@ where } /// Next child index when we want to create a new output. - fn next_child(&self, root_key_id: keychain::Identifier) -> u32 { + fn next_child(&mut self, root_key_id: keychain::Identifier) -> u32 { let mut max_n = 0; for out in self.outputs.values() { if max_n < out.n_child && out.root_key_id == root_key_id { max_n = out.n_child; } } - max_n + 1 + + if self.details.last_child_index <= max_n { + self.details.last_child_index = max_n + 1; + } else { + self.details.last_child_index += 1; + } + self.details.last_child_index } /// Select spendable coins from the wallet. @@ -394,6 +408,11 @@ where eligible.iter().take(max_outputs).cloned().collect() } + /// Return current metadata + fn details(&mut self) -> &mut WalletDetails { + &mut self.details + } + /// Restore wallet contents fn restore(&mut self) -> Result<(), libwallet::Error> { libwallet::internal::restore::restore(self).context(libwallet::ErrorKind::Restore)?; @@ -480,8 +499,8 @@ impl WalletClient for FileWallet { /// retrieve merkle proof for a commit from a node fn get_merkle_proof_for_commit( &self, - addr: &str, - commit: &str, + _addr: &str, + _commit: &str, ) -> Result { Err(libwallet::ErrorKind::GenericError("Not Implemented"))? } @@ -498,9 +517,12 @@ where config: config.clone(), passphrase: String::from(passphrase), outputs: HashMap::new(), + details: WalletDetails::default(), data_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DAT_FILE), backup_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, BCK_FILE), lock_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, LOCK_FILE), + details_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DETAIL_FILE), + details_bak_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DET_BCK_FILE), }; match retval.read_or_create_paths() { Ok(_) => Ok(retval), @@ -519,9 +541,21 @@ where if Path::new(&self.data_file_path.clone()).exists() { self.read()?; } + if Path::new(&self.details_file_path.clone()).exists() { + self.details = self.read_details()?; + } Ok(()) } + /// Read details file from disk + fn read_details(&self) -> Result { + let details_file = File::open(self.details_file_path.clone()) + .context(ErrorKind::FileWallet(&"Could not open wallet details file"))?; + serde_json::from_reader(details_file) + .context(ErrorKind::Format) + .map_err(|e| e.into()) + } + /// Read output_data vec from disk. fn read_outputs(&self) -> Result, Error> { let data_file = File::open(self.data_file_path.clone()) @@ -541,8 +575,8 @@ where Ok(()) } - /// Write the wallet data to disk. - fn write(&self, data_file_path: &str) -> Result<(), Error> { + /// Write the wallet and details data to disk. + fn write(&self, data_file_path: &str, details_file_path: &str) -> Result<(), Error> { let mut data_file = File::create(data_file_path).context(ErrorKind::FileWallet(&"Could not create "))?; let mut outputs = self.outputs.values().collect::>(); @@ -551,7 +585,16 @@ where .context(ErrorKind::FileWallet("Error serializing wallet data"))?; data_file .write_all(res_json.as_slice()) - .context(ErrorKind::FileWallet(&"Error writing wallet file")) + .context(ErrorKind::FileWallet(&"Error writing wallet file"))?; + // write details file + let mut details_file = + File::create(details_file_path).context(ErrorKind::FileWallet(&"Could not create "))?; + let res_json = serde_json::to_string_pretty(&self.details).context(ErrorKind::FileWallet( + "Error serializing wallet details file", + ))?; + details_file + .write_all(res_json.into_bytes().as_slice()) + .context(ErrorKind::FileWallet(&"Error writing wallet details file")) .map_err(|e| e.into()) } diff --git a/wallet/src/libwallet/api.rs b/wallet/src/libwallet/api.rs index 9867ffc6d..2bd92dbb3 100644 --- a/wallet/src/libwallet/api.rs +++ b/wallet/src/libwallet/api.rs @@ -72,12 +72,16 @@ where } /// Retrieve summary info for wallet - pub fn retrieve_summary_info(&mut self, refresh_from_node: bool) -> Result { + pub fn retrieve_summary_info( + &mut self, + refresh_from_node: bool, + ) -> Result<(bool, WalletInfo), Error> { let mut validated = false; if refresh_from_node { validated = self.update_outputs(); } - updater::retrieve_info(self.wallet, validated) + let wallet_info = updater::retrieve_info(self.wallet)?; + Ok((validated, wallet_info)) } /// Issues a send transaction and sends to recipient diff --git a/wallet/src/libwallet/controller.rs b/wallet/src/libwallet/controller.rs index c5e6b89cd..de3c656df 100644 --- a/wallet/src/libwallet/controller.rs +++ b/wallet/src/libwallet/controller.rs @@ -164,7 +164,7 @@ where &self, req: &mut Request, api: &mut APIOwner, - ) -> Result { + ) -> Result<(bool, WalletInfo), Error> { let mut update_from_node = false; if let Ok(params) = req.get_ref::() { if let Some(_) = params.get("refresh") { diff --git a/wallet/src/libwallet/internal/selection.rs b/wallet/src/libwallet/internal/selection.rs index 0e7b7b1b2..1f2fe1eab 100644 --- a/wallet/src/libwallet/internal/selection.rs +++ b/wallet/src/libwallet/internal/selection.rs @@ -46,7 +46,7 @@ where T: WalletBackend, K: Keychain, { - let (elems, inputs, change_id, amount, fee) = select_send_tx( + let (elems, inputs, change, change_derivation, amount, fee) = select_send_tx( wallet, amount, current_height, @@ -78,8 +78,9 @@ where } // Store change output - if change_id.is_some() { - context.add_output(&change_id.unwrap()); + if change_derivation.is_some() { + let change_id = keychain.derive_key_id(change_derivation.unwrap()).unwrap(); + context.add_output(&change_id); } let lock_inputs = context.get_inputs().clone(); @@ -89,10 +90,28 @@ where // so we avoid accidental double spend attempt. let update_sender_wallet_fn = move |wallet: &mut T| { wallet.with_wallet(|wallet_data| { + // Lock the inputs we've selected for id in lock_inputs { let coin = wallet_data.get_output(&id).unwrap().clone(); wallet_data.lock_output(&coin); } + if let Some(d) = change_derivation { + // Add our change output to the wallet + let root_key_id = keychain.root_key_id(); + let change_id = keychain.derive_key_id(change_derivation.unwrap()).unwrap(); + wallet_data.add_output(OutputData { + root_key_id: root_key_id.clone(), + key_id: change_id, + n_child: d, + value: change as u64, + status: OutputStatus::Unconfirmed, + height: current_height, + lock_height: 0, + is_coinbase: false, + block: None, + merkle_proof: None, + }); + } }) }; @@ -176,9 +195,10 @@ pub fn select_send_tx( ( Vec>>, Vec, - Option, - u64, // amount - u64, // fee + u64, //change + Option, //change derivation + u64, // amount + u64, // fee ), Error, > @@ -264,13 +284,14 @@ where } // build transaction skeleton with inputs and change - let (mut parts, change_key) = inputs_and_change(&coins, wallet, current_height, amount, fee)?; + let (mut parts, change, change_derivation) = + inputs_and_change(&coins, wallet, current_height, amount, fee)?; // This is more proof of concept than anything but here we set lock_height // on tx being sent (based on current chain height via api). parts.push(build::with_lock_height(lock_height)); - Ok((parts, coins, change_key, amount, fee)) + Ok((parts, coins, change, change_derivation, amount, fee)) } /// coins proof count @@ -285,7 +306,7 @@ pub fn inputs_and_change( height: u64, amount: u64, fee: u64, -) -> Result<(Vec>>, Option), Error> +) -> Result<(Vec>>, u64, Option), Error> where T: WalletBackend, K: Keychain, @@ -321,34 +342,20 @@ where } } let change_key; + let mut change_derivation = None; if change != 0 { // track the output representing our change change_key = wallet.with_wallet(|wallet_data| { let keychain = wallet_data.keychain().clone(); let root_key_id = keychain.root_key_id(); - let change_derivation = wallet_data.next_child(root_key_id.clone()); - let change_key = keychain.derive_key_id(change_derivation).unwrap(); - - wallet_data.add_output(OutputData { - root_key_id: root_key_id.clone(), - key_id: change_key.clone(), - n_child: change_derivation, - value: change as u64, - status: OutputStatus::Unconfirmed, - height: height, - lock_height: 0, - is_coinbase: false, - block: None, - merkle_proof: None, - }); - + let cd = wallet_data.next_child(root_key_id.clone()); + let change_key = keychain.derive_key_id(cd).unwrap(); + change_derivation = Some(cd); Some(change_key) })?; parts.push(build::output(change, change_key.clone().unwrap())); - } else { - change_key = None } - Ok((parts, change_key)) + Ok((parts, change, change_derivation)) } diff --git a/wallet/src/libwallet/internal/tx.rs b/wallet/src/libwallet/internal/tx.rs index acdc88223..8328c7aab 100644 --- a/wallet/src/libwallet/internal/tx.rs +++ b/wallet/src/libwallet/internal/tx.rs @@ -165,7 +165,10 @@ where debug!(LOGGER, "selected some coins - {}", coins.len()); let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None); - let (mut parts, _) = selection::inputs_and_change(&coins, wallet, current_height, amount, fee)?; + let (mut parts, _, _) = + selection::inputs_and_change(&coins, wallet, current_height, amount, fee)?; + + //TODO: If we end up using this, create change output here // add burn output and fees parts.push(build::output(amount - fee, Identifier::zero())); diff --git a/wallet/src/libwallet/internal/updater.rs b/wallet/src/libwallet/internal/updater.rs index 7d679faf1..18f91f343 100644 --- a/wallet/src/libwallet/internal/updater.rs +++ b/wallet/src/libwallet/internal/updater.rs @@ -24,6 +24,7 @@ use core::core::{Output, TxKernel}; use core::{global, ser}; use keychain::{Identifier, Keychain}; use libtx::reward; +use libwallet; use libwallet::error::{Error, ErrorKind}; use libwallet::internal::keys; use libwallet::types::{BlockFees, CbData, OutputData, OutputStatus, WalletBackend, WalletClient, @@ -181,7 +182,8 @@ pub fn apply_api_outputs( wallet: &mut T, wallet_outputs: &HashMap, api_outputs: &HashMap, -) -> Result<(), Error> + height: u64, +) -> Result<(), libwallet::Error> where T: WalletBackend, K: Keychain, @@ -199,7 +201,10 @@ where }; } } - }) + let details = wallet_data.details(); + details.last_confirmed_height = height; + })?; + Ok(()) } /// Builds a single api query to retrieve the latest output data from the node. @@ -218,7 +223,7 @@ where let wallet_output_keys = wallet_outputs.keys().map(|commit| commit.clone()).collect(); let api_outputs = wallet.get_outputs_from_node(wallet.node_url(), wallet_output_keys)?; - apply_api_outputs(wallet, &wallet_outputs, &api_outputs)?; + apply_api_outputs(wallet, &wallet_outputs, &api_outputs, height)?; clean_old_unconfirmed(wallet, height)?; Ok(()) } @@ -241,23 +246,15 @@ where /// Retrieve summary info about the wallet /// caller should refresh first if desired -pub fn retrieve_info(wallet: &mut T, refreshed: bool) -> Result +pub fn retrieve_info(wallet: &mut T) -> Result where T: WalletBackend + WalletClient, K: Keychain, { - let height_res = wallet.get_chain_height(&wallet.node_url()); - let ret_val = wallet.read_wallet(|wallet_data| { - let (current_height, from) = match height_res { - Ok(height) => (height, "from server node"), - Err(_) => match wallet_data.outputs().values().map(|out| out.height).max() { - Some(height) => (height, "from wallet"), - None => (0, "node/wallet unavailable"), - }, - }; + let current_height = wallet_data.details().last_confirmed_height; let mut unspent_total = 0; - let mut unspent_but_locked_total = 0; + let mut immature_total = 0; let mut unconfirmed_total = 0; let mut locked_total = 0; for out in wallet_data @@ -266,11 +263,11 @@ where .values() .filter(|out| out.root_key_id == wallet_data.keychain().root_key_id()) { - if out.status == OutputStatus::Unspent { + if out.status == OutputStatus::Unspent && out.lock_height <= current_height { unspent_total += out.value; - if out.lock_height > current_height { - unspent_but_locked_total += out.value; - } + } + if out.status == OutputStatus::Unspent && out.lock_height > current_height { + immature_total += out.value; } if out.status == OutputStatus::Unconfirmed && !out.is_coinbase { unconfirmed_total += out.value; @@ -281,14 +278,12 @@ where } Ok(WalletInfo { - current_height: current_height, - total: unspent_total + unconfirmed_total, + last_confirmed_height: wallet_data.details().last_confirmed_height, + total: unspent_total + unconfirmed_total + immature_total, amount_awaiting_confirmation: unconfirmed_total, - amount_confirmed_but_locked: unspent_but_locked_total, - amount_currently_spendable: unspent_total - unspent_but_locked_total, + amount_immature: immature_total, amount_locked: locked_total, - data_confirmed: refreshed, - data_confirmed_from: String::from(from), + amount_currently_spendable: unspent_total, }) }); ret_val diff --git a/wallet/src/libwallet/types.rs b/wallet/src/libwallet/types.rs index 2cc6efa07..7f65d65b2 100644 --- a/wallet/src/libwallet/types.rs +++ b/wallet/src/libwallet/types.rs @@ -76,7 +76,11 @@ where fn get_output(&self, key_id: &Identifier) -> Option<&OutputData>; /// Next child ID when we want to create a new output - fn next_child(&self, root_key_id: Identifier) -> u32; + /// Should also increment index + fn next_child(&mut self, root_key_id: Identifier) -> u32; + + /// Return current details + fn details(&mut self) -> &mut WalletDetails; /// Select spendable coins from the wallet fn select_coins( @@ -399,21 +403,40 @@ pub struct CbData { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct WalletInfo { /// height from which info was taken - pub current_height: u64, + pub last_confirmed_height: u64, /// total amount in the wallet pub total: u64, /// amount awaiting confirmation pub amount_awaiting_confirmation: u64, - /// confirmed but locked - pub amount_confirmed_but_locked: u64, + /// coinbases waiting for lock height + pub amount_immature: u64, /// amount currently spendable pub amount_currently_spendable: u64, - /// amount locked by previous transactions + /// amount locked via previous transactions pub amount_locked: u64, - /// whether the data was confirmed against a live node - pub data_confirmed: bool, - /// node confirming the data - pub data_confirmed_from: String, +} + +/// Separate data for a wallet, containing fields +/// that are needed but not necessarily represented +/// via simple rows of OutputData +/// If a wallet is restored from seed this is obvious +/// lost and re-populated as well as possible +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct WalletDetails { + /// The last block height at which the wallet data + /// was confirmed against a node + pub last_confirmed_height: u64, + /// The last child index used + pub last_child_index: u32, +} + +impl Default for WalletDetails { + fn default() -> WalletDetails { + WalletDetails { + last_confirmed_height: 0, + last_child_index: 0, + } + } } /// Dummy wrapper for the hex-encoded serialized transaction. diff --git a/wallet/tests/common/mod.rs b/wallet/tests/common/mod.rs index 476a2291e..012992e4a 100644 --- a/wallet/tests/common/mod.rs +++ b/wallet/tests/common/mod.rs @@ -61,7 +61,8 @@ where None => {} } } - updater::apply_api_outputs(wallet, &wallet_outputs, &api_outputs)?; + let height = chain.head().unwrap().height; + updater::apply_api_outputs(wallet, &wallet_outputs, &api_outputs, height)?; Ok(()) }