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
This commit is contained in:
Yeastplume 2018-06-14 17:02:05 +01:00 committed by GitHub
parent a30ee88236
commit 9e0b3b6862
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 177 additions and 106 deletions

View file

@ -27,15 +27,16 @@ extern crate grin_wallet as wallet;
mod framework; mod framework;
use std::{thread, time};
use std::sync::{Arc, Mutex};
use framework::{LocalServerContainer, LocalServerContainerConfig}; use framework::{LocalServerContainer, LocalServerContainerConfig};
use std::sync::{Arc, Mutex};
use std::{thread, time};
use util::LOGGER; use util::LOGGER;
/// Start 1 node mining, 1 non mining node and two wallets. /// 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 /// Then send a transaction from one wallet to another and propagate it a stem
/// but without stem relay and check if the transaction is still broadcasted. /// transaction but without stem relay and check if the transaction is still
/// broadcasted.
#[test] #[test]
#[ignore] #[ignore]
fn test_dandelion_timeout() { 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. // The transaction should be waiting in the node stempool thus cannot be mined.
println!("Recipient wallet info: {:?}", recipient_info); println!("Recipient wallet info: {:?}", recipient_info);
assert!( assert!(recipient_info.amount_awaiting_confirmation == 50000000000);
recipient_info.data_confirmed && recipient_info.amount_awaiting_confirmation == 50000000000
);
// Wait for stem timeout // Wait for stem timeout
thread::sleep(time::Duration::from_millis(35000)); thread::sleep(time::Duration::from_millis(35000));

View file

@ -303,7 +303,8 @@ impl LocalServerContainer {
let mut wallet = FileWallet::new(config.clone(), "") let mut wallet = FileWallet::new(config.clone(), "")
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
wallet.keychain = Some(keychain); 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( pub fn send_amount_to(

View file

@ -114,7 +114,7 @@ fn basic_wallet_transactions() {
1, 1,
"not_all", "not_all",
"http://127.0.0.1:20002", "http://127.0.0.1:20002",
true, false,
); );
//Wait for a confirmation //Wait for a confirmation
@ -125,9 +125,7 @@ fn basic_wallet_transactions() {
let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed);
println!("Recipient wallet info: {:?}", recipient_info); println!("Recipient wallet info: {:?}", recipient_info);
assert!( assert!(recipient_info.amount_currently_spendable == 50000000000);
recipient_info.data_confirmed && recipient_info.amount_currently_spendable == 50000000000
);
warn!( warn!(
LOGGER, LOGGER,
@ -140,7 +138,7 @@ fn basic_wallet_transactions() {
1, 1,
"not_all", "not_all",
"http://127.0.0.1:20002", "http://127.0.0.1:20002",
true, false,
); );
} }
@ -151,9 +149,7 @@ fn basic_wallet_transactions() {
recipient_info recipient_info
); );
assert!( assert!(recipient_info.amount_currently_spendable == 60000000000);
recipient_info.data_confirmed && recipient_info.amount_currently_spendable == 60000000000
);
//send some cash right back //send some cash right back
LocalServerContainer::send_amount_to( LocalServerContainer::send_amount_to(
&recp_wallet_config, &recp_wallet_config,
@ -161,7 +157,7 @@ fn basic_wallet_transactions() {
1, 1,
"all", "all",
"http://127.0.0.1:10002", "http://127.0.0.1:10002",
true, false,
); );
thread::sleep(time::Duration::from_millis(5000)); thread::sleep(time::Duration::from_millis(5000));

View file

@ -681,13 +681,14 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
Ok(()) Ok(())
} }
("info", Some(_)) => { ("info", Some(_)) => {
let _res = wallet::display::info(&api.retrieve_summary_info(true)?) let (validated, wallet_info) =
.unwrap_or_else(|e| { api.retrieve_summary_info(true).unwrap_or_else(|e| {
panic!( panic!(
"Error getting wallet info: {:?} Config: {:?}", "Error getting wallet info: {:?} Config: {:?}",
e, wallet_config e, wallet_config
) )
}); });
wallet::display::info(&wallet_info, validated);
Ok(()) Ok(())
} }
("outputs", Some(_)) => { ("outputs", Some(_)) => {

View file

@ -74,15 +74,15 @@ pub fn outputs(cur_height: u64, validated: bool, outputs: Vec<OutputData>) -> Re
} }
/// Display summary info in a pretty way /// Display summary info in a pretty way
pub fn info(wallet_info: &WalletInfo) -> Result<(), Error> { pub fn info(wallet_info: &WalletInfo, validated: bool) {
println!( println!(
"\n____ Wallet Summary Info at {} ({}) ____\n", "\n____ Wallet Summary Info as of {} ____\n",
wallet_info.current_height, wallet_info.data_confirmed_from wallet_info.last_confirmed_height
); );
let mut table = table!( let mut table = table!(
[bFG->"Total", FG->amount_to_hr_string(wallet_info.total)], [bFG->"Total", FG->amount_to_hr_string(wallet_info.total)],
[bFY->"Awaiting Confirmation", FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation)], [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)], [bFG->"Currently Spendable", FG->amount_to_hr_string(wallet_info.amount_currently_spendable)],
[Fw->"---------", Fw->"---------"], [Fw->"---------", Fw->"---------"],
[Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(wallet_info.amount_locked)] [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.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.printstd(); table.printstd();
println!(); println!();
if !validated {
if !wallet_info.data_confirmed {
println!( println!(
"\nWARNING: Failed to verify wallet contents with grin server. \ "\nWARNING: Wallet failed to verify data against a live chain. \
Above info is maybe not fully updated or invalid! \ The above is from local cache and only valid up to the given height! \
Check that your `grin server` is OK, or see `wallet help restore`" (is your `grin server` offline or broken?)"
); );
}; }
Ok(())
} }

View file

@ -37,8 +37,10 @@ use client;
use libtx::slate::Slate; use libtx::slate::Slate;
use libwallet; use libwallet;
use libwallet::types::{BlockFees, BlockIdentifier, CbData, MerkleProofWrapper, OutputData, 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 DAT_FILE: &'static str = "wallet.dat";
const BCK_FILE: &'static str = "wallet.bck"; const BCK_FILE: &'static str = "wallet.bck";
const LOCK_FILE: &'static str = "wallet.lock"; const LOCK_FILE: &'static str = "wallet.lock";
@ -176,12 +178,18 @@ pub struct FileWallet<K> {
passphrase: String, passphrase: String,
/// List of outputs /// List of outputs
pub outputs: HashMap<String, OutputData>, pub outputs: HashMap<String, OutputData>,
/// Details
pub details: WalletDetails,
/// Data file path /// Data file path
pub data_file_path: String, pub data_file_path: String,
/// Backup file path /// Backup file path
pub backup_file_path: String, pub backup_file_path: String,
/// lock file path /// lock file path
pub lock_file_path: String, pub lock_file_path: String,
/// details file path
pub details_file_path: String,
/// Details backup file path
pub details_bak_path: String,
} }
impl<K> WalletBackend<K> for FileWallet<K> impl<K> WalletBackend<K> for FileWallet<K>
@ -274,10 +282,10 @@ where
// We successfully acquired the lock - so do what needs to be done. // We successfully acquired the lock - so do what needs to be done.
self.read_or_create_paths() self.read_or_create_paths()
.context(libwallet::ErrorKind::CallbackImpl("Lock Error"))?; .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"))?; .context(libwallet::ErrorKind::CallbackImpl("Write Error"))?;
let res = f(self); 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"))?; .context(libwallet::ErrorKind::CallbackImpl("Write Error"))?;
// delete the lock file // delete the lock file
@ -318,14 +326,20 @@ where
} }
/// Next child index when we want to create a new output. /// 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; let mut max_n = 0;
for out in self.outputs.values() { for out in self.outputs.values() {
if max_n < out.n_child && out.root_key_id == root_key_id { if max_n < out.n_child && out.root_key_id == root_key_id {
max_n = out.n_child; 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. /// Select spendable coins from the wallet.
@ -394,6 +408,11 @@ where
eligible.iter().take(max_outputs).cloned().collect() eligible.iter().take(max_outputs).cloned().collect()
} }
/// Return current metadata
fn details(&mut self) -> &mut WalletDetails {
&mut self.details
}
/// Restore wallet contents /// Restore wallet contents
fn restore(&mut self) -> Result<(), libwallet::Error> { fn restore(&mut self) -> Result<(), libwallet::Error> {
libwallet::internal::restore::restore(self).context(libwallet::ErrorKind::Restore)?; libwallet::internal::restore::restore(self).context(libwallet::ErrorKind::Restore)?;
@ -480,8 +499,8 @@ impl<K> WalletClient for FileWallet<K> {
/// retrieve merkle proof for a commit from a node /// retrieve merkle proof for a commit from a node
fn get_merkle_proof_for_commit( fn get_merkle_proof_for_commit(
&self, &self,
addr: &str, _addr: &str,
commit: &str, _commit: &str,
) -> Result<MerkleProofWrapper, libwallet::Error> { ) -> Result<MerkleProofWrapper, libwallet::Error> {
Err(libwallet::ErrorKind::GenericError("Not Implemented"))? Err(libwallet::ErrorKind::GenericError("Not Implemented"))?
} }
@ -498,9 +517,12 @@ where
config: config.clone(), config: config.clone(),
passphrase: String::from(passphrase), passphrase: String::from(passphrase),
outputs: HashMap::new(), outputs: HashMap::new(),
details: WalletDetails::default(),
data_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DAT_FILE), data_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DAT_FILE),
backup_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, BCK_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), 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() { match retval.read_or_create_paths() {
Ok(_) => Ok(retval), Ok(_) => Ok(retval),
@ -519,9 +541,21 @@ where
if Path::new(&self.data_file_path.clone()).exists() { if Path::new(&self.data_file_path.clone()).exists() {
self.read()?; self.read()?;
} }
if Path::new(&self.details_file_path.clone()).exists() {
self.details = self.read_details()?;
}
Ok(()) Ok(())
} }
/// Read details file from disk
fn read_details(&self) -> Result<WalletDetails, Error> {
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. /// Read output_data vec from disk.
fn read_outputs(&self) -> Result<Vec<OutputData>, Error> { fn read_outputs(&self) -> Result<Vec<OutputData>, Error> {
let data_file = File::open(self.data_file_path.clone()) let data_file = File::open(self.data_file_path.clone())
@ -541,8 +575,8 @@ where
Ok(()) Ok(())
} }
/// Write the wallet data to disk. /// Write the wallet and details data to disk.
fn write(&self, data_file_path: &str) -> Result<(), Error> { fn write(&self, data_file_path: &str, details_file_path: &str) -> Result<(), Error> {
let mut data_file = let mut data_file =
File::create(data_file_path).context(ErrorKind::FileWallet(&"Could not create "))?; File::create(data_file_path).context(ErrorKind::FileWallet(&"Could not create "))?;
let mut outputs = self.outputs.values().collect::<Vec<_>>(); let mut outputs = self.outputs.values().collect::<Vec<_>>();
@ -551,7 +585,16 @@ where
.context(ErrorKind::FileWallet("Error serializing wallet data"))?; .context(ErrorKind::FileWallet("Error serializing wallet data"))?;
data_file data_file
.write_all(res_json.as_slice()) .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()) .map_err(|e| e.into())
} }

View file

@ -72,12 +72,16 @@ where
} }
/// Retrieve summary info for wallet /// Retrieve summary info for wallet
pub fn retrieve_summary_info(&mut self, refresh_from_node: bool) -> Result<WalletInfo, Error> { pub fn retrieve_summary_info(
&mut self,
refresh_from_node: bool,
) -> Result<(bool, WalletInfo), Error> {
let mut validated = false; let mut validated = false;
if refresh_from_node { if refresh_from_node {
validated = self.update_outputs(); 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 /// Issues a send transaction and sends to recipient

View file

@ -164,7 +164,7 @@ where
&self, &self,
req: &mut Request, req: &mut Request,
api: &mut APIOwner<T, K>, api: &mut APIOwner<T, K>,
) -> Result<WalletInfo, Error> { ) -> Result<(bool, WalletInfo), Error> {
let mut update_from_node = false; let mut update_from_node = false;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() { if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
if let Some(_) = params.get("refresh") { if let Some(_) = params.get("refresh") {

View file

@ -46,7 +46,7 @@ where
T: WalletBackend<K>, T: WalletBackend<K>,
K: Keychain, K: Keychain,
{ {
let (elems, inputs, change_id, amount, fee) = select_send_tx( let (elems, inputs, change, change_derivation, amount, fee) = select_send_tx(
wallet, wallet,
amount, amount,
current_height, current_height,
@ -78,8 +78,9 @@ where
} }
// Store change output // Store change output
if change_id.is_some() { if change_derivation.is_some() {
context.add_output(&change_id.unwrap()); let change_id = keychain.derive_key_id(change_derivation.unwrap()).unwrap();
context.add_output(&change_id);
} }
let lock_inputs = context.get_inputs().clone(); let lock_inputs = context.get_inputs().clone();
@ -89,10 +90,28 @@ where
// so we avoid accidental double spend attempt. // so we avoid accidental double spend attempt.
let update_sender_wallet_fn = move |wallet: &mut T| { let update_sender_wallet_fn = move |wallet: &mut T| {
wallet.with_wallet(|wallet_data| { wallet.with_wallet(|wallet_data| {
// Lock the inputs we've selected
for id in lock_inputs { for id in lock_inputs {
let coin = wallet_data.get_output(&id).unwrap().clone(); let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin); 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<T, K>(
( (
Vec<Box<build::Append<K>>>, Vec<Box<build::Append<K>>>,
Vec<OutputData>, Vec<OutputData>,
Option<Identifier>, u64, //change
u64, // amount Option<u32>, //change derivation
u64, // fee u64, // amount
u64, // fee
), ),
Error, Error,
> >
@ -264,13 +284,14 @@ where
} }
// build transaction skeleton with inputs and change // 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 // 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). // on tx being sent (based on current chain height via api).
parts.push(build::with_lock_height(lock_height)); 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 /// coins proof count
@ -285,7 +306,7 @@ pub fn inputs_and_change<T, K>(
height: u64, height: u64,
amount: u64, amount: u64,
fee: u64, fee: u64,
) -> Result<(Vec<Box<build::Append<K>>>, Option<Identifier>), Error> ) -> Result<(Vec<Box<build::Append<K>>>, u64, Option<u32>), Error>
where where
T: WalletBackend<K>, T: WalletBackend<K>,
K: Keychain, K: Keychain,
@ -321,34 +342,20 @@ where
} }
} }
let change_key; let change_key;
let mut change_derivation = None;
if change != 0 { if change != 0 {
// track the output representing our change // track the output representing our change
change_key = wallet.with_wallet(|wallet_data| { change_key = wallet.with_wallet(|wallet_data| {
let keychain = wallet_data.keychain().clone(); let keychain = wallet_data.keychain().clone();
let root_key_id = keychain.root_key_id(); let root_key_id = keychain.root_key_id();
let change_derivation = wallet_data.next_child(root_key_id.clone()); let cd = wallet_data.next_child(root_key_id.clone());
let change_key = keychain.derive_key_id(change_derivation).unwrap(); let change_key = keychain.derive_key_id(cd).unwrap();
change_derivation = Some(cd);
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,
});
Some(change_key) Some(change_key)
})?; })?;
parts.push(build::output(change, change_key.clone().unwrap())); parts.push(build::output(change, change_key.clone().unwrap()));
} else {
change_key = None
} }
Ok((parts, change_key)) Ok((parts, change, change_derivation))
} }

View file

@ -165,7 +165,10 @@ where
debug!(LOGGER, "selected some coins - {}", coins.len()); debug!(LOGGER, "selected some coins - {}", coins.len());
let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None); 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 // add burn output and fees
parts.push(build::output(amount - fee, Identifier::zero())); parts.push(build::output(amount - fee, Identifier::zero()));

View file

@ -24,6 +24,7 @@ use core::core::{Output, TxKernel};
use core::{global, ser}; use core::{global, ser};
use keychain::{Identifier, Keychain}; use keychain::{Identifier, Keychain};
use libtx::reward; use libtx::reward;
use libwallet;
use libwallet::error::{Error, ErrorKind}; use libwallet::error::{Error, ErrorKind};
use libwallet::internal::keys; use libwallet::internal::keys;
use libwallet::types::{BlockFees, CbData, OutputData, OutputStatus, WalletBackend, WalletClient, use libwallet::types::{BlockFees, CbData, OutputData, OutputStatus, WalletBackend, WalletClient,
@ -181,7 +182,8 @@ pub fn apply_api_outputs<T, K>(
wallet: &mut T, wallet: &mut T,
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>, wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
api_outputs: &HashMap<pedersen::Commitment, String>, api_outputs: &HashMap<pedersen::Commitment, String>,
) -> Result<(), Error> height: u64,
) -> Result<(), libwallet::Error>
where where
T: WalletBackend<K>, T: WalletBackend<K>,
K: Keychain, 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. /// 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 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)?; 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)?; clean_old_unconfirmed(wallet, height)?;
Ok(()) Ok(())
} }
@ -241,23 +246,15 @@ where
/// Retrieve summary info about the wallet /// Retrieve summary info about the wallet
/// caller should refresh first if desired /// caller should refresh first if desired
pub fn retrieve_info<T, K>(wallet: &mut T, refreshed: bool) -> Result<WalletInfo, Error> pub fn retrieve_info<T, K>(wallet: &mut T) -> Result<WalletInfo, Error>
where where
T: WalletBackend<K> + WalletClient, T: WalletBackend<K> + WalletClient,
K: Keychain, K: Keychain,
{ {
let height_res = wallet.get_chain_height(&wallet.node_url());
let ret_val = wallet.read_wallet(|wallet_data| { let ret_val = wallet.read_wallet(|wallet_data| {
let (current_height, from) = match height_res { let current_height = wallet_data.details().last_confirmed_height;
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 mut unspent_total = 0; let mut unspent_total = 0;
let mut unspent_but_locked_total = 0; let mut immature_total = 0;
let mut unconfirmed_total = 0; let mut unconfirmed_total = 0;
let mut locked_total = 0; let mut locked_total = 0;
for out in wallet_data for out in wallet_data
@ -266,11 +263,11 @@ where
.values() .values()
.filter(|out| out.root_key_id == wallet_data.keychain().root_key_id()) .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; 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 { if out.status == OutputStatus::Unconfirmed && !out.is_coinbase {
unconfirmed_total += out.value; unconfirmed_total += out.value;
@ -281,14 +278,12 @@ where
} }
Ok(WalletInfo { Ok(WalletInfo {
current_height: current_height, last_confirmed_height: wallet_data.details().last_confirmed_height,
total: unspent_total + unconfirmed_total, total: unspent_total + unconfirmed_total + immature_total,
amount_awaiting_confirmation: unconfirmed_total, amount_awaiting_confirmation: unconfirmed_total,
amount_confirmed_but_locked: unspent_but_locked_total, amount_immature: immature_total,
amount_currently_spendable: unspent_total - unspent_but_locked_total,
amount_locked: locked_total, amount_locked: locked_total,
data_confirmed: refreshed, amount_currently_spendable: unspent_total,
data_confirmed_from: String::from(from),
}) })
}); });
ret_val ret_val

View file

@ -76,7 +76,11 @@ where
fn get_output(&self, key_id: &Identifier) -> Option<&OutputData>; fn get_output(&self, key_id: &Identifier) -> Option<&OutputData>;
/// Next child ID when we want to create a new output /// 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 /// Select spendable coins from the wallet
fn select_coins( fn select_coins(
@ -399,21 +403,40 @@ pub struct CbData {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletInfo { pub struct WalletInfo {
/// height from which info was taken /// height from which info was taken
pub current_height: u64, pub last_confirmed_height: u64,
/// total amount in the wallet /// total amount in the wallet
pub total: u64, pub total: u64,
/// amount awaiting confirmation /// amount awaiting confirmation
pub amount_awaiting_confirmation: u64, pub amount_awaiting_confirmation: u64,
/// confirmed but locked /// coinbases waiting for lock height
pub amount_confirmed_but_locked: u64, pub amount_immature: u64,
/// amount currently spendable /// amount currently spendable
pub amount_currently_spendable: u64, pub amount_currently_spendable: u64,
/// amount locked by previous transactions /// amount locked via previous transactions
pub amount_locked: u64, pub amount_locked: u64,
/// whether the data was confirmed against a live node }
pub data_confirmed: bool,
/// node confirming the data /// Separate data for a wallet, containing fields
pub data_confirmed_from: String, /// 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. /// Dummy wrapper for the hex-encoded serialized transaction.

View file

@ -61,7 +61,8 @@ where
None => {} 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(()) Ok(())
} }