mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
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:
parent
a30ee88236
commit
9e0b3b6862
13 changed files with 177 additions and 106 deletions
|
@ -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));
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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(_)) => {
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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") {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue