Skip to content

Commit

Permalink
fix: reduce console wallet tui memory usage (#3389)
Browse files Browse the repository at this point in the history
Description
---
Transactional information is copied to an internal buffer for the console wallet to use in its screen refresh tick cycle and updates to this information are event-driven. This PR reduces the memory overhead required to keep transactions in their various life cycle stages in the buffer memory.

Motivation and Context
---
Unnecessary memory was allocated to the internal buffer as the entire completed transaction body was kept while only the signature(s) and maturity is required. This is more significant for large wallets.

How Has This Been Tested?
---
System-level testing
  • Loading branch information
hansieodendaal authored Oct 4, 2021
1 parent dae656a commit ca1e9fd
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ use std::collections::HashMap;

use crate::ui::{
components::{balance::Balance, Component},
state::AppState,
state::{AppState, CompletedTransactionInfo},
widgets::{draw_dialog, MultiColumnList, WindowedListState},
MAX_WIDTH,
};
use chrono::{DateTime, Local};
use tari_crypto::tari_utilities::hex::Hex;
use tari_wallet::transaction_service::storage::models::{
CompletedTransaction,
TransactionDirection,
TransactionStatus,
};
use tari_wallet::transaction_service::storage::models::{TransactionDirection, TransactionStatus};
use tokio::runtime::Handle;
use tui::{
backend::Backend,
Expand All @@ -28,7 +23,7 @@ pub struct TransactionsTab {
selected_tx_list: SelectedTransactionList,
pending_list_state: WindowedListState,
completed_list_state: WindowedListState,
detailed_transaction: Option<CompletedTransaction>,
detailed_transaction: Option<CompletedTransactionInfo>,
error_message: Option<String>,
confirmation_dialog: bool,
}
Expand Down Expand Up @@ -197,16 +192,11 @@ impl TransactionsTab {
app_state.get_alias(&t.source_public_key),
Style::default().fg(text_color),
)));
let maturity = if let Some(output) = t.transaction.body.outputs().first() {
output.features.maturity
} else {
0
};
let color = match (t.cancelled, chain_height) {
// cancelled
(true, _) => Color::DarkGray,
// not mature yet
(_, Some(height)) if maturity > height => Color::Yellow,
(_, Some(height)) if t.maturity > height => Color::Yellow,
// default
_ => Color::Green,
};
Expand Down Expand Up @@ -326,15 +316,13 @@ impl TransactionsTab {
};
let direction = Span::styled(format!("{}", tx.direction), Style::default().fg(Color::White));
let amount = Span::styled(format!("{}", tx.amount), Style::default().fg(Color::White));
let fee_details = if tx.is_coinbase() {
let fee_details = if tx.is_coinbase {
Span::raw("")
} else {
Span::styled(
format!(
" (weight: {}g, #inputs: {}, #outputs: {})",
tx.transaction.calculate_weight(),
tx.transaction.body.inputs().len(),
tx.transaction.body.outputs().len()
tx.weight, tx.inputs_count, tx.outputs_count
),
Style::default().fg(Color::Gray),
)
Expand All @@ -357,12 +345,7 @@ impl TransactionsTab {
format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")),
Style::default().fg(Color::White),
);
let excess_hex = tx
.transaction
.first_kernel_excess_sig()
.map(|s| s.get_signature().to_hex())
.unwrap_or_default();
let excess = Span::styled(excess_hex.as_str(), Style::default().fg(Color::White));
let excess = Span::styled(tx.excess_signature.as_str(), Style::default().fg(Color::White));
let confirmation_count = app_state.get_confirmations(&tx.tx_id);
let confirmations_msg = if tx.status == TransactionStatus::MinedConfirmed && !tx.cancelled {
format!("{} required confirmations met", required_confirmations)
Expand All @@ -382,15 +365,8 @@ impl TransactionsTab {
.unwrap_or_else(|| "N/A".to_string()),
Style::default().fg(Color::White),
);
let maturity = tx
.transaction
.body
.outputs()
.first()
.map(|o| o.features.maturity)
.unwrap_or_else(|| 0);
let maturity = if maturity > 0 {
format!("Spendable at Block #{}", maturity)
let maturity = if tx.maturity > 0 {
format!("Spendable at Block #{}", tx.maturity)
} else {
"N/A".to_string()
};
Expand Down
95 changes: 83 additions & 12 deletions applications/tari_console_wallet/src/ui/state/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use std::{
};

use bitflags::bitflags;
use chrono::{DateTime, Local};
use chrono::{DateTime, Local, NaiveDateTime};
use log::*;
use qrcode::{render::unicode, QrCode};
use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::hex::Hex};
Expand Down Expand Up @@ -75,7 +75,10 @@ use crate::{
utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
wallet_modes::PeerConfig,
};
use tari_wallet::output_manager_service::handle::OutputManagerHandle;
use tari_wallet::{
output_manager_service::handle::OutputManagerHandle,
transaction_service::storage::models::TransactionDirection,
};

const LOG_TARGET: &str = "wallet::console_wallet::app_state";

Expand Down Expand Up @@ -320,27 +323,27 @@ impl AppState {
&self.cached_data.contacts[start..end]
}

pub fn get_pending_txs(&self) -> &Vec<CompletedTransaction> {
pub fn get_pending_txs(&self) -> &Vec<CompletedTransactionInfo> {
&self.cached_data.pending_txs
}

pub fn get_pending_txs_slice(&self, start: usize, end: usize) -> &[CompletedTransaction] {
pub fn get_pending_txs_slice(&self, start: usize, end: usize) -> &[CompletedTransactionInfo] {
if self.cached_data.pending_txs.is_empty() || start > end || end > self.cached_data.pending_txs.len() {
return &[];
}

&self.cached_data.pending_txs[start..end]
}

pub fn get_pending_tx(&self, index: usize) -> Option<&CompletedTransaction> {
pub fn get_pending_tx(&self, index: usize) -> Option<&CompletedTransactionInfo> {
if index < self.cached_data.pending_txs.len() {
Some(&self.cached_data.pending_txs[index])
} else {
None
}
}

pub fn get_completed_txs(&self) -> Vec<&CompletedTransaction> {
pub fn get_completed_txs(&self) -> Vec<&CompletedTransactionInfo> {
if self
.completed_tx_filter
.contains(TransactionFilter::ABANDONED_COINBASES)
Expand All @@ -359,7 +362,7 @@ impl AppState {
(&self.cached_data.confirmations).get(tx_id)
}

pub fn get_completed_tx(&self, index: usize) -> Option<&CompletedTransaction> {
pub fn get_completed_tx(&self, index: usize) -> Option<&CompletedTransactionInfo> {
let filtered_completed_txs = self.get_completed_txs();
if index < filtered_completed_txs.len() {
Some(filtered_completed_txs[index])
Expand Down Expand Up @@ -519,7 +522,10 @@ impl AppStateInner {
pending_transactions.sort_by(|a: &CompletedTransaction, b: &CompletedTransaction| {
b.timestamp.partial_cmp(&a.timestamp).unwrap()
});
self.data.pending_txs = pending_transactions;
self.data.pending_txs = pending_transactions
.iter()
.map(|tx| CompletedTransactionInfo::from(tx.clone()))
.collect();

let mut completed_transactions: Vec<CompletedTransaction> = Vec::new();
completed_transactions.extend(
Expand Down Expand Up @@ -548,7 +554,10 @@ impl AppStateInner {
.expect("Should be able to compare timestamps")
});

self.data.completed_txs = completed_transactions;
self.data.completed_txs = completed_transactions
.iter()
.map(|tx| CompletedTransactionInfo::from(tx.clone()))
.collect();
self.updated = true;
Ok(())
}
Expand Down Expand Up @@ -590,7 +599,7 @@ impl AppStateInner {
});
},
Some(tx) => {
let tx = CompletedTransaction::from(tx);
let tx = CompletedTransactionInfo::from(CompletedTransaction::from(tx));
if let Some(index) = self.data.pending_txs.iter().position(|i| i.tx_id == tx_id) {
if tx.status == TransactionStatus::Pending && !tx.cancelled {
self.data.pending_txs[index] = tx;
Expand Down Expand Up @@ -852,10 +861,72 @@ impl AppStateInner {
}
}

#[derive(Clone)]
pub struct CompletedTransactionInfo {
pub tx_id: TxId,
pub source_public_key: CommsPublicKey,
pub destination_public_key: CommsPublicKey,
pub amount: MicroTari,
pub fee: MicroTari,
pub excess_signature: String,
pub maturity: u64,
pub status: TransactionStatus,
pub message: String,
pub timestamp: NaiveDateTime,
pub cancelled: bool,
pub direction: TransactionDirection,
pub valid: bool,
pub mined_height: Option<u64>,
pub is_coinbase: bool,
pub weight: u64,
pub inputs_count: usize,
pub outputs_count: usize,
}

impl From<CompletedTransaction> for CompletedTransactionInfo {
fn from(completed_transaction: CompletedTransaction) -> Self {
let excess_signature = if completed_transaction.transaction.body.kernels().is_empty() {
"".to_string()
} else {
completed_transaction.transaction.body.kernels()[0]
.excess_sig
.get_signature()
.to_hex()
};

Self {
tx_id: completed_transaction.tx_id,
source_public_key: completed_transaction.source_public_key.clone(),
destination_public_key: completed_transaction.destination_public_key.clone(),
amount: completed_transaction.amount,
fee: completed_transaction.fee,
excess_signature,
maturity: completed_transaction
.transaction
.body
.outputs()
.first()
.map(|o| o.features.maturity)
.unwrap_or_else(|| 0),
status: completed_transaction.status.clone(),
message: completed_transaction.message.clone(),
timestamp: completed_transaction.timestamp,
cancelled: completed_transaction.cancelled,
direction: completed_transaction.direction.clone(),
valid: completed_transaction.valid,
mined_height: completed_transaction.mined_height,
is_coinbase: completed_transaction.is_coinbase(),
weight: completed_transaction.transaction.calculate_weight(),
inputs_count: completed_transaction.transaction.body.inputs().len(),
outputs_count: completed_transaction.transaction.body.outputs().len(),
}
}
}

#[derive(Clone)]
struct AppStateData {
pending_txs: Vec<CompletedTransaction>,
completed_txs: Vec<CompletedTransaction>,
pending_txs: Vec<CompletedTransactionInfo>,
completed_txs: Vec<CompletedTransactionInfo>,
confirmations: HashMap<TxId, u64>,
my_identity: MyIdentity,
contacts: Vec<UiContact>,
Expand Down

0 comments on commit ca1e9fd

Please sign in to comment.