From 2cd35c593ba66ba4f3de1df06e7487d0844732bb Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 29 Jun 2021 13:29:45 +0200 Subject: [PATCH] [console-wallet] Add maturity to transaction detail --- .../src/ui/components/transactions_tab.rs | 171 ++++++++++-------- .../src/output_manager_service/service.rs | 5 +- .../wallet/src/transaction_service/service.rs | 2 +- .../tests/output_manager_service/service.rs | 7 +- 4 files changed, 99 insertions(+), 86 deletions(-) diff --git a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs index 7789e38679..11d8db5f05 100644 --- a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs @@ -71,11 +71,17 @@ impl TransactionsTab { .title(Span::styled("(P)ending Transactions", style)); f.render_widget(block, list_areas[0]); + self.draw_pending_transactions(f, list_areas[0], app_state); + self.draw_completed_transactions(f, list_areas[1], app_state); + } + + fn draw_pending_transactions(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + where B: Backend { // Pending Transactions self.pending_list_state.set_num_items(app_state.get_pending_txs().len()); let mut pending_list_state = self .pending_list_state - .get_list_state((list_areas[0].height as usize).saturating_sub(3)); + .get_list_state((area.height as usize).saturating_sub(3)); let window = self.pending_list_state.get_start_end(); let windowed_view = app_state.get_pending_txs_slice(window.0, window.1); @@ -132,8 +138,11 @@ impl TransactionsTab { .add_column(Some("Amount"), Some(18), column1_items) .add_column(Some("Local Date/Time"), Some(20), column2_items) .add_column(Some("Message"), None, column3_items); - column_list.render(f, list_areas[0], &mut pending_list_state); + column_list.render(f, area, &mut pending_list_state); + } + fn draw_completed_transactions(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + where B: Backend { // Completed Transactions let style = if self.selected_tx_list == SelectedTransactionList::CompletedTxs { Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD) @@ -143,15 +152,26 @@ impl TransactionsTab { let block = Block::default() .borders(Borders::ALL) .title(Span::styled("Completed (T)ransactions", style)); - f.render_widget(block, list_areas[1]); + f.render_widget(block, area); let completed_txs = app_state.get_completed_txs(); self.completed_list_state.set_num_items(completed_txs.len()); let mut completed_list_state = self .completed_list_state - .get_list_state((list_areas[1].height as usize).saturating_sub(3)); - let window = self.completed_list_state.get_start_end(); - let windowed_view = &completed_txs[window.0..window.1]; + .get_list_state((area.height as usize).saturating_sub(3)); + let (start, end) = self.completed_list_state.get_start_end(); + let windowed_view = &completed_txs[start..end]; + + let text_colors: HashMap = [(true, Color::DarkGray), (false, Color::Reset)] + .iter() + .cloned() + .collect(); + + let base_node_state = app_state.get_base_node_state(); + let chain_height = base_node_state + .chain_metadata + .as_ref() + .map(|cm| cm.height_of_longest_chain()); let mut column0_items = Vec::new(); let mut column1_items = Vec::new(); @@ -159,7 +179,8 @@ impl TransactionsTab { let mut column3_items = Vec::new(); for t in windowed_view.iter() { - let text_color = text_colors.get(&t.cancelled).unwrap_or(&Color::Reset).to_owned(); + let cancelled = t.cancelled || !t.valid; + let text_color = text_colors.get(&cancelled).unwrap_or(&Color::Reset).to_owned(); if t.direction == TransactionDirection::Outbound { column0_items.push(ListItem::new(Span::styled( format!("{}", t.destination_public_key), @@ -176,11 +197,20 @@ impl TransactionsTab { format!("{}", t.source_public_key), Style::default().fg(text_color), ))); - let amount_style = if t.cancelled { - Style::default().fg(Color::Green).add_modifier(Modifier::DIM) + let maturity = if let Some(output) = t.transaction.body.outputs().first() { + output.features.maturity } else { - Style::default().fg(Color::Green) + 0 + }; + let color = match (t.cancelled, chain_height) { + // cancelled + (true, _) => Color::DarkGray, + // not mature yet + (_, Some(height)) if maturity > height => Color::Yellow, + // default + _ => Color::Green, }; + let amount_style = Style::default().fg(color); column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); @@ -209,7 +239,7 @@ impl TransactionsTab { .add_column(Some("Local Date/Time"), Some(20), column2_items) .add_column(Some("Status"), None, column3_items); - column_list.render(f, list_areas[1], &mut completed_list_state); + column_list.render(f, area, &mut completed_list_state); } fn draw_detailed_transaction(&self, f: &mut Frame, area: Rect, app_state: &AppState) @@ -226,27 +256,9 @@ impl TransactionsTab { .margin(1) .split(area); - // Labels: - let label_layout = Layout::default() - .constraints( - [ - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - ] - .as_ref(), - ) - .split(columns[0]); + // Labels + let constraints = [Constraint::Length(1); 13]; + let label_layout = Layout::default().constraints(constraints).split(columns[0]); let tx_id = Span::styled("TxID:", Style::default().fg(Color::Magenta)); let source_public_key = Span::styled("Source Public Key:", Style::default().fg(Color::Magenta)); @@ -260,53 +272,41 @@ impl TransactionsTab { let excess = Span::styled("Excess:", Style::default().fg(Color::Magenta)); let confirmations = Span::styled("Confirmations:", Style::default().fg(Color::Magenta)); let mined_height = Span::styled("Mined Height:", Style::default().fg(Color::Magenta)); - let paragraph = Paragraph::new(tx_id).wrap(Wrap { trim: true }); + let maturity = Span::styled("Maturity:", Style::default().fg(Color::Magenta)); + + let trim = Wrap { trim: true }; + let paragraph = Paragraph::new(tx_id).wrap(trim); f.render_widget(paragraph, label_layout[0]); - let paragraph = Paragraph::new(source_public_key).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(source_public_key).wrap(trim); f.render_widget(paragraph, label_layout[1]); - let paragraph = Paragraph::new(destination_public_key).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(destination_public_key).wrap(trim); f.render_widget(paragraph, label_layout[2]); - let paragraph = Paragraph::new(direction).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(direction).wrap(trim); f.render_widget(paragraph, label_layout[3]); - let paragraph = Paragraph::new(amount).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(amount).wrap(trim); f.render_widget(paragraph, label_layout[4]); - let paragraph = Paragraph::new(fee).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(fee).wrap(trim); f.render_widget(paragraph, label_layout[5]); - let paragraph = Paragraph::new(status).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(status).wrap(trim); f.render_widget(paragraph, label_layout[6]); - let paragraph = Paragraph::new(message).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(message).wrap(trim); f.render_widget(paragraph, label_layout[7]); - let paragraph = Paragraph::new(timestamp).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(timestamp).wrap(trim); f.render_widget(paragraph, label_layout[8]); - let paragraph = Paragraph::new(excess).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(excess).wrap(trim); f.render_widget(paragraph, label_layout[9]); - let paragraph = Paragraph::new(confirmations).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(confirmations).wrap(trim); f.render_widget(paragraph, label_layout[10]); - let paragraph = Paragraph::new(mined_height).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(mined_height).wrap(trim); f.render_widget(paragraph, label_layout[11]); - // Content: + let paragraph = Paragraph::new(maturity).wrap(trim); + f.render_widget(paragraph, label_layout[12]); + + // Content let required_confirmations = app_state.get_required_confirmations(); if let Some(tx) = self.detailed_transaction.as_ref() { - let content_layout = Layout::default() - .constraints( - [ - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - ] - .as_ref(), - ) - .split(columns[1]); + let constraints = [Constraint::Length(1); 13]; + let content_layout = Layout::default().constraints(constraints).split(columns[1]); let tx_id = Span::styled(format!("{}", tx.tx_id), Style::default().fg(Color::White)); let source_public_key = @@ -366,31 +366,46 @@ 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) + } else { + "N/A".to_string() + }; + let maturity = Span::styled(maturity, Style::default().fg(Color::White)); - let paragraph = Paragraph::new(tx_id).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(tx_id).wrap(trim); f.render_widget(paragraph, content_layout[0]); - let paragraph = Paragraph::new(source_public_key).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(source_public_key).wrap(trim); f.render_widget(paragraph, content_layout[1]); - let paragraph = Paragraph::new(destination_public_key).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(destination_public_key).wrap(trim); f.render_widget(paragraph, content_layout[2]); - let paragraph = Paragraph::new(direction).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(direction).wrap(trim); f.render_widget(paragraph, content_layout[3]); - let paragraph = Paragraph::new(amount).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(amount).wrap(trim); f.render_widget(paragraph, content_layout[4]); - let paragraph = Paragraph::new(fee).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(fee).wrap(trim); f.render_widget(paragraph, content_layout[5]); - let paragraph = Paragraph::new(status).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(status).wrap(trim); f.render_widget(paragraph, content_layout[6]); - let paragraph = Paragraph::new(message).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(message).wrap(trim); f.render_widget(paragraph, content_layout[7]); - let paragraph = Paragraph::new(timestamp).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(timestamp).wrap(trim); f.render_widget(paragraph, content_layout[8]); - let paragraph = Paragraph::new(excess).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(excess).wrap(trim); f.render_widget(paragraph, content_layout[9]); - let paragraph = Paragraph::new(confirmations).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(confirmations).wrap(trim); f.render_widget(paragraph, content_layout[10]); - let paragraph = Paragraph::new(mined_height).wrap(Wrap { trim: true }); + let paragraph = Paragraph::new(mined_height).wrap(trim); f.render_widget(paragraph, content_layout[11]); + let paragraph = Paragraph::new(maturity).wrap(trim); + f.render_widget(paragraph, content_layout[12]); } } } @@ -403,7 +418,7 @@ impl Component for TransactionsTab { Constraint::Length(3), Constraint::Length(1), Constraint::Min(10), - Constraint::Length(14), + Constraint::Length(15), ] .as_ref(), ) diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 1811be653a..a4ab79fba3 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -1274,8 +1274,11 @@ impl Balance { impl fmt::Display for Balance { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Available balance: {}", self.available_balance)?; + if let Some(locked) = self.time_locked_balance { + writeln!(f, "Time locked: {}", locked)?; + } writeln!(f, "Pending incoming balance: {}", self.pending_incoming_balance)?; - write!(f, "Pending outgoing balance: {}", self.pending_outgoing_balance)?; + writeln!(f, "Pending outgoing balance: {}", self.pending_outgoing_balance)?; Ok(()) } } diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index b59803e4ef..2cb5410b96 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -1804,7 +1804,7 @@ where MicroTari::from(0), tx.clone(), TransactionStatus::Coinbase, - format!("Coinbase Transaction for Block {}", block_height), + format!("Coinbase Transaction for Block #{}", block_height), Utc::now().naive_utc(), TransactionDirection::Inbound, Some(block_height), diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index bd673399e8..76c89535dd 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -297,12 +297,7 @@ fn generate_sender_transaction_message(amount: MicroTari) -> (TxId, TransactionS .with_change_secret(alice.change_spend_key) .with_input(utxo, input) .with_amount(0, amount) - .with_recipient_script( - 0, - script!(Nop), - PrivateKey::random(&mut OsRng), - OutputFeatures::default(), - ) + .with_recipient_script(0, script!(Nop), PrivateKey::random(&mut OsRng), Default::default()) .with_change_script( script!(Nop), inputs!(PublicKey::from_secret_key(&script_private_key)),