Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet Performance enhancements - cache commit + transaction fetching logic #2375

Merged
merged 7 commits into from Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions config/src/comments.rs
Expand Up @@ -411,6 +411,15 @@ fn comments() -> HashMap<String, String> {
"data_file_dir".to_string(),
"
#where to find wallet files (seed, data, etc)
"
.to_string(),
);
retval.insert(
"no_commit_cache".to_string(),
"
#If true, don't store calculated commits in the database
#better privacy, but at a performance cost of having to
#re-calculate commits every time they're used
"
.to_string(),
);
Expand Down
4 changes: 2 additions & 2 deletions wallet/src/libwallet/api.rs
Expand Up @@ -431,7 +431,7 @@ where

let res = Ok((
validated,
updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id))?,
updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?,
));

w.close()?;
Expand Down Expand Up @@ -867,7 +867,7 @@ where
None => w.parent_key_id(),
};
// Don't do this multiple times
let tx = updater::retrieve_txs(&mut *w, None, Some(slate.id), Some(&parent_key_id))?;
let tx = updater::retrieve_txs(&mut *w, None, Some(slate.id), Some(&parent_key_id), false)?;
for t in &tx {
if t.tx_type == TxLogEntryType::TxReceived {
return Err(ErrorKind::TransactionAlreadyReceived(slate.id.to_string()).into());
Expand Down
3 changes: 3 additions & 0 deletions wallet/src/libwallet/internal/restore.rs
Expand Up @@ -142,6 +142,7 @@ where
C: NodeClient,
K: Keychain,
{
let commit = wallet.cached_commit(output.value, &output.key_id)?;
let mut batch = wallet.batch()?;

let parent_key_id = output.key_id.parent_path();
Expand All @@ -167,6 +168,7 @@ where
key_id: output.key_id,
n_child: output.n_child,
mmr_index: Some(output.mmr_index),
commit: commit,
value: output.value,
status: OutputStatus::Unspent,
height: output.height,
Expand Down Expand Up @@ -198,6 +200,7 @@ where
output.tx_log_entry.clone(),
None,
Some(&parent_key_id),
false,
)?;
if entries.len() > 0 {
let mut entry = entries[0].clone();
Expand Down
13 changes: 11 additions & 2 deletions wallet/src/libwallet/internal/selection.rs
Expand Up @@ -21,6 +21,8 @@ use crate::libwallet::error::{Error, ErrorKind};
use crate::libwallet::internal::keys;
use crate::libwallet::types::*;

use std::collections::HashMap;

/// Initialize a transaction on the sender side, returns a corresponding
/// libwallet transaction slate with the appropriate inputs selected,
/// and saves the private wallet identifiers of our selected outputs
Expand Down Expand Up @@ -85,9 +87,12 @@ where
context.add_input(&input.key_id, &input.mmr_index);
}

// Store change output(s)
for (_, id, mmr_index) in &change_amounts_derivations {
let mut commits: HashMap<Identifier, Option<String>> = HashMap::new();

// Store change output(s) and cached commits
for (change_amount, id, mmr_index) in &change_amounts_derivations {
context.add_output(&id, &mmr_index);
commits.insert(id.clone(), wallet.cached_commit(*change_amount, &id)?);
}

let lock_inputs = context.get_inputs().clone();
Expand Down Expand Up @@ -119,10 +124,12 @@ where
for (change_amount, id, _) in &change_amounts_derivations {
t.num_outputs += 1;
t.amount_credited += change_amount;
let commit = commits.get(&id).unwrap().clone();
batch.save(OutputData {
root_key_id: parent_key_id.clone(),
key_id: id.clone(),
n_child: id.to_path().last_path_index(),
commit: commit,
mmr_index: None,
value: change_amount.clone(),
status: OutputStatus::Unconfirmed,
Expand Down Expand Up @@ -189,6 +196,7 @@ where
// Create closure that adds the output to recipient's wallet
// (up to the caller to decide when to do)
let wallet_add_fn = move |wallet: &mut T| {
let commit = wallet.cached_commit(amount, &key_id_inner)?;
let mut batch = wallet.batch()?;
let log_id = batch.next_tx_log_id(&parent_key_id)?;
let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxReceived, log_id);
Expand All @@ -200,6 +208,7 @@ where
key_id: key_id_inner.clone(),
mmr_index: None,
n_child: key_id_inner.to_path().last_path_index(),
commit: commit,
value: amount,
status: OutputStatus::Unconfirmed,
height: height,
Expand Down
6 changes: 3 additions & 3 deletions wallet/src/libwallet/internal/tx.rs
Expand Up @@ -161,7 +161,7 @@ where
} else if let Some(tx_slate_id) = tx_slate_id {
tx_id_string = tx_slate_id.to_string();
}
let tx_vec = updater::retrieve_txs(wallet, tx_id, tx_slate_id, Some(&parent_key_id))?;
let tx_vec = updater::retrieve_txs(wallet, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
if tx_vec.len() != 1 {
return Err(ErrorKind::TransactionDoesntExist(tx_id_string))?;
}
Expand Down Expand Up @@ -191,7 +191,7 @@ where
C: NodeClient,
K: Keychain,
{
let tx_vec = updater::retrieve_txs(wallet, Some(tx_id), None, Some(parent_key_id))?;
let tx_vec = updater::retrieve_txs(wallet, Some(tx_id), None, Some(parent_key_id), false)?;
if tx_vec.len() != 1 {
return Err(ErrorKind::TransactionDoesntExist(tx_id.to_string()))?;
}
Expand All @@ -207,7 +207,7 @@ where
K: Keychain,
{
// finalize command
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None)?;
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?;
let mut tx = None;
// don't want to assume this is the right tx, in case of self-sending
for t in tx_vec {
Expand Down
74 changes: 45 additions & 29 deletions wallet/src/libwallet/internal/updater.rs
Expand Up @@ -74,7 +74,10 @@ where
let res = outputs
.into_iter()
.map(|out| {
let commit = keychain.commit(out.value, &out.key_id).unwrap();
let commit = match out.commit.clone() {
Some(c) => pedersen::Commitment::from_vec(util::from_hex(c).unwrap()),
None => keychain.commit(out.value, &out.key_id).unwrap(),
};
(out, commit)
})
.collect();
Expand All @@ -88,30 +91,39 @@ pub fn retrieve_txs<T: ?Sized, C, K>(
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
parent_key_id: Option<&Identifier>,
outstanding_only: bool,
) -> Result<Vec<TxLogEntry>, Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: Keychain,
{
// just read the wallet here, no need for a write lock
let mut txs = if let Some(id) = tx_id {
wallet.tx_log_iter().filter(|t| t.id == id).collect()
} else if tx_slate_id.is_some() {
wallet
.tx_log_iter()
.filter(|t| t.tx_slate_id == tx_slate_id)
.collect()
} else {
wallet.tx_log_iter().collect::<Vec<_>>()
};
if let Some(k) = parent_key_id {
txs = txs
.iter()
.filter(|t| t.parent_key_id == *k)
.map(|t| t.clone())
.collect();
}
let mut txs: Vec<TxLogEntry> = wallet
.tx_log_iter()
.filter(|tx_entry| {
let f_pk = match parent_key_id {
Some(k) => tx_entry.parent_key_id == *k,
None => true,
};
let f_tx_id = match tx_id {
Some(i) => tx_entry.id == i,
None => true,
};
let f_txs = match tx_slate_id {
Some(t) => tx_entry.tx_slate_id == Some(t),
None => true,
};
let f_outstanding = match outstanding_only {
true => {
!tx_entry.confirmed
&& (tx_entry.tx_type == TxLogEntryType::TxReceived
|| tx_entry.tx_type == TxLogEntryType::TxSent)
}
false => true,
};
f_pk && f_tx_id && f_txs && f_outstanding
})
.collect();
txs.sort_by_key(|tx| tx.creation_ts);
Ok(txs)
}
Expand Down Expand Up @@ -153,20 +165,18 @@ where
.filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent)
.collect();

let tx_entries = retrieve_txs(wallet, None, None, Some(&parent_key_id), true)?;

// Only select outputs that are actually involved in an outstanding transaction
let unspents: Vec<OutputData> = match update_all {
false => unspents
.into_iter()
.filter(|x| match x.tx_log_entry.as_ref() {
Some(t) => {
let entries = retrieve_txs(wallet, Some(*t), None, Some(&parent_key_id));
match entries {
Err(_) => true,
Ok(e) => {
e.len() > 0
&& !e[0].confirmed && (e[0].tx_type == TxLogEntryType::TxReceived
|| e[0].tx_type == TxLogEntryType::TxSent)
}
if let Some(_) = tx_entries.iter().find(|&te| te.id == *t) {
true
} else {
false
}
}
None => true,
Expand All @@ -176,7 +186,10 @@ where
};

for out in unspents {
let commit = keychain.commit(out.value, &out.key_id)?;
let commit = match out.commit.clone() {
Some(c) => pedersen::Commitment::from_vec(util::from_hex(c).unwrap()),
None => keychain.commit(out.value, &out.key_id).unwrap(),
};
wallet_outputs.insert(commit, (out.key_id.clone(), out.mmr_index));
}
Ok(wallet_outputs)
Expand Down Expand Up @@ -466,13 +479,16 @@ where

{
// Now acquire the wallet lock and write the new output.
let amount = reward(block_fees.fees);
let commit = wallet.cached_commit(amount, &key_id)?;
let mut batch = wallet.batch()?;
batch.save(OutputData {
root_key_id: parent_key_id,
key_id: key_id.clone(),
n_child: key_id.to_path().last_path_index(),
mmr_index: None,
value: reward(block_fees.fees),
commit: commit,
value: amount,
status: OutputStatus::Unconfirmed,
height: height,
lock_height: lock_height,
Expand Down
5 changes: 5 additions & 0 deletions wallet/src/libwallet/types.rs
Expand Up @@ -67,6 +67,9 @@ where
/// Return the client being used to communicate with the node
fn w2n_client(&mut self) -> &mut C;

/// return the commit if allowed, if enabled, None otherwise
fn cached_commit(&mut self, amount: u64, id: &Identifier) -> Result<Option<String>, Error>;

/// Set parent key id by stored account name
fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>;

Expand Down Expand Up @@ -241,6 +244,8 @@ pub struct OutputData {
pub key_id: Identifier,
/// How many derivations down from the root key
pub n_child: u32,
/// The actual commit, optionally stored
pub commit: Option<String>,
/// PMMR Index, used on restore in case of duplicate wallets using the same
/// key_id (2 wallets using same seed, for instance
pub mmr_index: Option<u64>,
Expand Down
11 changes: 11 additions & 0 deletions wallet/src/lmdb_wallet.rs
Expand Up @@ -199,6 +199,17 @@ where
&mut self.w2n_client
}

/// return the commit, if enabled, None otherwise
fn cached_commit(&mut self, amount: u64, id: &Identifier) -> Result<Option<String>, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this maybe mis-named or am I misunderstanding what is going on here?
Its not actually cached in here?

if self.config.no_commit_cache == Some(true) {
Ok(None)
} else {
Ok(Some(util::to_hex(
self.keychain().commit(amount, &id)?.0.to_vec(),
)))
}
}

/// Set parent path by account name
fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> {
let label = label.to_owned();
Expand Down
4 changes: 4 additions & 0 deletions wallet/src/types.rs
Expand Up @@ -52,6 +52,9 @@ pub struct WalletConfig {
pub owner_api_include_foreign: Option<bool>,
// The directory in which wallet files are stored
pub data_file_dir: String,
/// If Some(true), don't cache commits alongside output data
/// speed improvement, but your commits are in the database
pub no_commit_cache: Option<bool>,
/// TLS certificate file
pub tls_certificate_file: Option<String>,
/// TLS certificate private key file
Expand All @@ -74,6 +77,7 @@ impl Default for WalletConfig {
check_node_api_http_addr: "http://127.0.0.1:3413".to_string(),
owner_api_include_foreign: Some(false),
data_file_dir: ".".to_string(),
no_commit_cache: Some(false),
tls_certificate_file: None,
tls_certificate_key: None,
dark_background_color_scheme: Some(true),
Expand Down