From 65bb57944671ac0d4a8917862a1a258498627648 Mon Sep 17 00:00:00 2001 From: Andrejs Gubarevs <1062334+agubarev@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:35:07 +0300 Subject: [PATCH] added database functions and UI, still need to finish wiring everything together --- applications/tari_base_node/src/main.rs | 2 +- .../tari_console_wallet/src/ui/app.rs | 44 ++++++- .../src/ui/components/burn_tab.rs | 118 ++++++++++++++++-- .../src/ui/components/contacts_tab.rs | 2 +- .../tari_console_wallet/src/ui/mod.rs | 7 +- .../src/ui/state/app_state.rs | 57 ++++++++- .../tari_console_wallet/src/ui/state/tasks.rs | 21 ++-- .../src/ui/{ui_contact.rs => types.rs} | 9 +- .../tari_console_wallet/src/ui/ui_error.rs | 40 ------ base_layer/common_types/src/encryption.rs | 1 + base_layer/wallet/src/schema.rs | 8 ++ base_layer/wallet/src/storage/database.rs | 34 +++++ .../wallet/src/storage/sqlite_db/wallet.rs | 104 ++++++++++++++- 13 files changed, 372 insertions(+), 75 deletions(-) rename applications/tari_console_wallet/src/ui/{ui_contact.rs => types.rs} (91%) delete mode 100644 applications/tari_console_wallet/src/ui/ui_error.rs diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index ef282c96a5..3390b51420 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -130,7 +130,7 @@ fn main_inner() -> Result<(), ExitError> { consts::APP_VERSION ); - let mut config = ApplicationConfig::load_from(&cfg)?; + let config = ApplicationConfig::load_from(&cfg)?; debug!(target: LOG_TARGET, "Using base node configuration: {:?}", config); // Load or create the Node identity diff --git a/applications/tari_console_wallet/src/ui/app.rs b/applications/tari_console_wallet/src/ui/app.rs index 34292d10e0..50fd3155b7 100644 --- a/applications/tari_console_wallet/src/ui/app.rs +++ b/applications/tari_console_wallet/src/ui/app.rs @@ -20,8 +20,18 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_comms::peer_manager::Peer; -use tari_wallet::{util::wallet_identity::WalletIdentity, WalletConfig, WalletSqlite}; +use tari_comms::{connectivity::ConnectivityError, peer_manager::Peer}; +use tari_contacts::contacts_service::error::ContactsServiceError; +use tari_utilities::hex::HexError; +use tari_wallet::{ + error::{WalletError, WalletStorageError}, + output_manager_service::error::OutputManagerError, + transaction_service::error::TransactionServiceError, + util::wallet_identity::WalletIdentity, + WalletConfig, + WalletSqlite, +}; +use thiserror::Error; use tokio::runtime::Handle; use tui::{ backend::Backend, @@ -188,3 +198,33 @@ impl App { self.menu.draw(f, title_chunks[3], &self.app_state); } } + +#[derive(Error, Debug)] +pub enum UiError { + #[error(transparent)] + TransactionService(#[from] TransactionServiceError), + #[error(transparent)] + OutputManager(#[from] OutputManagerError), + #[error(transparent)] + ContactsService(#[from] ContactsServiceError), + #[error(transparent)] + Connectivity(#[from] ConnectivityError), + #[error(transparent)] + HexError(#[from] HexError), + #[error(transparent)] + WalletError(#[from] WalletError), + #[error(transparent)] + WalletStorageError(#[from] WalletStorageError), + #[error("Could not convert string into Public Key")] + PublicKeyParseError, + #[error("Could not convert string into Net Address")] + AddressParseError, + #[error("Peer did not include an address")] + NoAddress, + #[error("Specified burn proof file already exists")] + BurntProofFileExists, + #[error("Burnt proof with id {0} is not found")] + BurntProofNotFound(i32), + #[error("Channel send error: `{0}`")] + SendError(String), +} diff --git a/applications/tari_console_wallet/src/ui/components/burn_tab.rs b/applications/tari_console_wallet/src/ui/components/burn_tab.rs index 191a4d1996..c53dd8cd0d 100644 --- a/applications/tari_console_wallet/src/ui/components/burn_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/burn_tab.rs @@ -1,8 +1,6 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use std::path::Path; - use log::*; use tari_core::transactions::tari_amount::MicroTari; use tari_wallet::output_manager_service::UtxoSelectionCriteria; @@ -12,15 +10,20 @@ use tui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Span, Spans}, - widgets::{Block, Borders, Paragraph, TableState, Wrap}, + widgets::{Block, Borders, ListItem, Paragraph, TableState, Wrap}, Frame, }; use unicode_width::UnicodeWidthStr; -use crate::ui::{ - components::{balance::Balance, Component, KeyHandled}, - state::{AppState, UiTransactionBurnStatus}, - widgets::draw_dialog, +use crate::{ + ui::{ + components::{balance::Balance, contacts_tab::ContactsTab, Component, KeyHandled}, + state::{AppState, UiTransactionBurnStatus}, + types::UiBurntProof, + widgets::{draw_dialog, MultiColumnList, WindowedListState}, + MAX_WIDTH, + }, + utils::formatting::display_compressed_string, }; const LOG_TARGET: &str = "wallet::console_wallet::burn_tab "; @@ -39,6 +42,8 @@ pub struct BurnTab { burn_result_watch: Option>, confirmation_dialog: Option, table_state: TableState, + proofs_list_state: WindowedListState, + show_proofs: bool, } impl BurnTab { @@ -54,9 +59,11 @@ impl BurnTab { error_message: None, success_message: None, offline_message: None, + proofs_list_state: WindowedListState::new(), burn_result_watch: None, confirmation_dialog: None, table_state: TableState::default(), + show_proofs: false, } } @@ -101,7 +108,9 @@ impl BurnTab { Span::styled("F", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Fee-Per-Gram", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field."), + Span::raw(" field,"), + Span::styled("B", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to view burnt proofs."), ]), Spans::from(vec![ Span::raw("Press "), @@ -199,6 +208,68 @@ impl BurnTab { } } + #[allow(dead_code)] + fn draw_proofs(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + where B: Backend { + let block = Block::default().borders(Borders::ALL).title(Span::styled( + "Burnt Proofs", + Style::default().fg(Color::White).add_modifier(Modifier::BOLD), + )); + f.render_widget(block, area); + + let list_areas = Layout::default() + .constraints([Constraint::Length(1), Constraint::Min(42)].as_ref()) + .margin(1) + .split(area); + + let instructions = Paragraph::new(Spans::from(vec![ + Span::raw(" Use "), + Span::styled("Up↑/Down↓ Keys", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to choose a contact, "), + Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to select."), + ])) + .wrap(Wrap { trim: true }); + f.render_widget(instructions, list_areas[0]); + + self.proofs_list_state.set_num_items(app_state.get_burnt_proofs().len()); + + let mut list_state = self + .proofs_list_state + .get_list_state((list_areas[1].height as usize).saturating_sub(3)); + + let window = self.proofs_list_state.get_start_end(); + let windowed_view = app_state.get_burnt_proofs_slice(window.0, window.1); + + let column_list = BurnTab::create_column_view(windowed_view); + column_list.render(f, list_areas[1], &mut list_state); + } + + // Helper function to create the column list to be rendered + pub fn create_column_view(windowed_view: &[UiBurntProof]) -> MultiColumnList> { + let mut column0_items = Vec::new(); + let mut column1_items = Vec::new(); + // let mut column2_items = Vec::new(); + + for item in windowed_view.iter() { + column0_items.push(ListItem::new(Span::raw(item.id.to_string()))); + column1_items.push(ListItem::new(Span::raw(item.proof.clone()))); + // column2_items.push(ListItem::new(Span::raw(item.last_seen.clone()))); + } + + let column_list = MultiColumnList::new() + .highlight_style(Style::default().add_modifier(Modifier::BOLD).fg(Color::Magenta)) + .heading_style(Style::default().fg(Color::Magenta)) + .max_width(MAX_WIDTH) + .add_column(Some("ID"), Some(23), column0_items) + .add_column(None, Some(1), Vec::new()) + .add_column(Some("Reciprocal Public Key"), Some(66), column1_items); + // .add_column(None, Some(1), Vec::new()) + // .add_column(Some("Last Seen"), Some(11), column2_items); + + column_list + } + #[allow(clippy::too_many_lines)] fn on_key_confirmation_dialog(&mut self, c: char, app_state: &mut AppState) -> KeyHandled { if self.confirmation_dialog.is_some() { @@ -286,7 +357,7 @@ impl BurnTab { match self.burn_input_mode { BurnInputMode::None => (), BurnInputMode::BurntProofPath => match c { - '\n' => self.burn_input_mode = BurnInputMode::Amount, + '\n' => self.burn_input_mode = BurnInputMode::ClaimPublicKey, c => { self.burnt_proof_filepath_field.push(c); return KeyHandled::Handled; @@ -329,6 +400,22 @@ impl BurnTab { KeyHandled::NotHandled } + + fn on_key_show_proofs(&mut self, c: char, app_state: &mut AppState) -> KeyHandled { + if self.show_proofs && c == '\n' { + if let Some(c) = self + .proofs_list_state + .selected() + .and_then(|i| app_state.get_burnt_proof_by_index(i)) + .cloned() + { + self.show_proofs = false; + } + return KeyHandled::Handled; + } + + KeyHandled::NotHandled + } } impl Component for BurnTab { @@ -350,6 +437,10 @@ impl Component for BurnTab { self.balance.draw(f, areas[0], app_state); self.draw_burn_form(f, areas[1], app_state); + if self.show_proofs { + self.draw_proofs(f, areas[2], app_state); + }; + let rx_option = self.burn_result_watch.take(); if let Some(rx) = rx_option { trace!(target: LOG_TARGET, "{:?}", (*rx.borrow()).clone()); @@ -373,7 +464,7 @@ impl Component for BurnTab { ); return; }, - UiTransactionBurnStatus::TransactionComplete => { + UiTransactionBurnStatus::TransactionComplete((proof_id, serialized_proof)) => { self.success_message = Some("Transaction completed successfully!\nPlease press Enter to continue".to_string()); return; @@ -456,6 +547,10 @@ impl Component for BurnTab { return; } + if self.on_key_show_proofs(c, app_state) == KeyHandled::Handled { + return; + } + match c { 'p' => self.burn_input_mode = BurnInputMode::BurntProofPath, 'c' => self.burn_input_mode = BurnInputMode::ClaimPublicKey, @@ -464,6 +559,9 @@ impl Component for BurnTab { }, 'f' => self.burn_input_mode = BurnInputMode::Fee, 'm' => self.burn_input_mode = BurnInputMode::Message, + 'b' => { + self.show_proofs = !self.show_proofs; + }, 's' => { // if self.burnt_proof_filepath_field.is_empty() { // self.error_message = Some("Burn proof filepath is empty\nPress Enter to continue.".to_string()); diff --git a/applications/tari_console_wallet/src/ui/components/contacts_tab.rs b/applications/tari_console_wallet/src/ui/components/contacts_tab.rs index e165cf8b8a..ca071934cb 100644 --- a/applications/tari_console_wallet/src/ui/components/contacts_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/contacts_tab.rs @@ -16,8 +16,8 @@ use crate::{ ui::{ components::{Component, KeyHandled}, state::AppState, + types::UiContact, widgets::{centered_rect_absolute, draw_dialog, MultiColumnList, WindowedListState}, - UiContact, MAX_WIDTH, }, utils::formatting::display_compressed_string, diff --git a/applications/tari_console_wallet/src/ui/mod.rs b/applications/tari_console_wallet/src/ui/mod.rs index 838e1ca1f5..cfc9182f74 100644 --- a/applications/tari_console_wallet/src/ui/mod.rs +++ b/applications/tari_console_wallet/src/ui/mod.rs @@ -29,9 +29,9 @@ use crate::utils::crossterm_events::CrosstermEvents; mod app; mod components; pub mod state; -mod ui_contact; -mod ui_error; +pub mod types; mod widgets; + use std::io::{stdout, Stdout}; pub use app::*; @@ -41,10 +41,9 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use log::*; +use tari_contacts::contacts_service::storage::database::Contact; use tokio::runtime::Handle; use tui::{backend::CrosstermBackend, Terminal}; -pub use ui_contact::*; -pub use ui_error::*; use crate::utils::events::{Event, EventStream}; diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index 47d26b46fb..5d809eb6b7 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -72,13 +72,13 @@ use super::tasks::send_one_sided_to_stealth_address_transaction; use crate::{ notifier::Notifier, ui::{ + app::UiError, state::{ debouncer::BalanceEnquiryDebouncer, tasks::{send_burn_transaction_task, send_one_sided_transaction_task, send_transaction_task}, wallet_event_monitor::WalletEventMonitor, }, - UiContact, - UiError, + types::{UiBurntProof, UiContact}, }, utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, wallet_modes::PeerConfig, @@ -165,6 +165,14 @@ impl AppState { Ok(()) } + pub async fn refresh_burnt_proofs_state(&mut self) -> Result<(), UiError> { + let mut inner = self.inner.write().await; + inner.refresh_burnt_proofs_state().await?; + drop(inner); + self.update_cache().await; + Ok(()) + } + pub async fn refresh_connected_peers_state(&mut self) -> Result<(), UiError> { self.check_connectivity().await; let mut inner = self.inner.write().await; @@ -379,7 +387,7 @@ impl AppState { let path = PathBuf::from(path); if path.exists() { - return Err(UiError::BurnProofFileExists); + return Err(UiError::BurntProofFileExists); } Some(path) @@ -435,6 +443,14 @@ impl AppState { &self.cached_data.my_identity } + pub fn get_burnt_proofs(&self) -> &[UiBurntProof] { + self.cached_data.burnt_proofs.as_slice() + } + + pub fn get_burnt_proof_by_index(&self, idx: usize) -> Option<&UiBurntProof> { + self.cached_data.burnt_proofs.get(idx) + } + pub fn get_contacts(&self) -> &[UiContact] { self.cached_data.contacts.as_slice() } @@ -455,6 +471,14 @@ impl AppState { &self.cached_data.contacts[start..end] } + pub fn get_burnt_proofs_slice(&self, start: usize, end: usize) -> &[UiBurntProof] { + if self.cached_data.burnt_proofs.is_empty() || start > end || end > self.cached_data.burnt_proofs.len() { + return &[]; + } + + &self.cached_data.burnt_proofs[start..end] + } + pub fn get_pending_txs(&self) -> &Vec { &self.cached_data.pending_txs } @@ -829,6 +853,29 @@ impl AppStateInner { Ok(()) } + pub async fn refresh_burnt_proofs_state(&mut self) -> Result<(), UiError> { + let db_burnt_proofs = self.wallet.db.get_burnt_proofs()?; + let mut ui_proofs: Vec = vec![]; + + for proof in db_burnt_proofs { + ui_proofs.push(UiBurntProof { + id: proof.0, + proof: proof.1, + }); + } + + // TODO: sort by timestamp when added + // ui_proofs.sort_by(|a, b| { + // a.alias + // .partial_cmp(&b.alias) + // .expect("Should be able to compare contact aliases") + // }); + + self.data.burnt_proofs = ui_proofs; + self.updated = true; + Ok(()) + } + pub async fn refresh_connected_peers_state(&mut self) -> Result<(), UiError> { let connections = self.wallet.comms.connectivity().get_active_connections().await?; let peer_manager = self.wallet.comms.peer_manager(); @@ -1113,6 +1160,7 @@ struct AppStateData { confirmations: HashMap, my_identity: MyIdentity, contacts: Vec, + burnt_proofs: Vec, connected_peers: Vec, balance: Balance, base_node_state: BaseNodeState, @@ -1187,6 +1235,7 @@ impl AppStateData { confirmations: HashMap::new(), my_identity: identity, contacts: Vec::new(), + burnt_proofs: vec![], connected_peers: Vec::new(), balance: Balance::zero(), base_node_state: BaseNodeState::default(), @@ -1226,7 +1275,7 @@ pub enum UiTransactionBurnStatus { Initiated, Queued, SentDirect, - TransactionComplete, + TransactionComplete((i32, String)), DiscoveryInProgress, SentViaSaf, Error(String), diff --git a/applications/tari_console_wallet/src/ui/state/tasks.rs b/applications/tari_console_wallet/src/ui/state/tasks.rs index 516f6fb715..af04d015d8 100644 --- a/applications/tari_console_wallet/src/ui/state/tasks.rs +++ b/applications/tari_console_wallet/src/ui/state/tasks.rs @@ -22,6 +22,7 @@ use std::path::PathBuf; +use rand::random; use tari_common_types::{tari_address::TariAddress, types::PublicKey}; use tari_core::transactions::{tari_amount::MicroTari, transaction_components::OutputFeatures}; use tari_utilities::ByteArray; @@ -32,8 +33,8 @@ use tari_wallet::{ use tokio::sync::{broadcast, watch}; use crate::ui::{ + app::UiError, state::{BurntProofBase64, CommitmentSignatureBase64, UiTransactionBurnStatus, UiTransactionSendStatus}, - UiError, }; const LOG_TARGET: &str = "wallet::console_wallet::tasks "; @@ -259,17 +260,21 @@ pub async fn send_burn_transaction_task( range_proof: proof.range_proof.0, }; + let serialized_proof = + serde_json::to_string_pretty(&BurntProofBase64::from(wrapped_proof)) + .expect("failed to serialize burn proof"); + let filepath = burn_proof_filepath .unwrap_or(PathBuf::from(format!("{}.json", burn_tx_id.as_u64().to_string()))); - std::fs::write( - filepath, - serde_json::to_string_pretty(&BurntProofBase64::from(wrapped_proof)) - .expect("failed to serialize burn proof"), - ) - .expect("failed to save burn proof"); + std::fs::write(filepath, serialized_proof.as_bytes()) + .expect("failed to save burn proof"); + + let _ = result_tx.send(UiTransactionBurnStatus::TransactionComplete(( + random::(), + serialized_proof, + ))); - let _ = result_tx.send(UiTransactionBurnStatus::TransactionComplete); return; } } diff --git a/applications/tari_console_wallet/src/ui/ui_contact.rs b/applications/tari_console_wallet/src/ui/types.rs similarity index 91% rename from applications/tari_console_wallet/src/ui/ui_contact.rs rename to applications/tari_console_wallet/src/ui/types.rs index 1426aa975a..e44ac2eb6c 100644 --- a/applications/tari_console_wallet/src/ui/ui_contact.rs +++ b/applications/tari_console_wallet/src/ui/types.rs @@ -1,9 +1,12 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - use chrono::{DateTime, Local}; use tari_contacts::contacts_service::storage::database::Contact; +#[derive(Debug, Clone)] +pub struct UiBurntProof { + pub id: i32, + pub proof: String, +} + #[derive(Debug, Clone)] pub struct UiContact { pub alias: String, diff --git a/applications/tari_console_wallet/src/ui/ui_error.rs b/applications/tari_console_wallet/src/ui/ui_error.rs deleted file mode 100644 index cde90b243c..0000000000 --- a/applications/tari_console_wallet/src/ui/ui_error.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -use tari_comms::connectivity::ConnectivityError; -use tari_contacts::contacts_service::error::ContactsServiceError; -use tari_utilities::hex::HexError; -use tari_wallet::{ - error::{WalletError, WalletStorageError}, - output_manager_service::error::OutputManagerError, - transaction_service::error::TransactionServiceError, -}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum UiError { - #[error(transparent)] - TransactionService(#[from] TransactionServiceError), - #[error(transparent)] - OutputManager(#[from] OutputManagerError), - #[error(transparent)] - ContactsService(#[from] ContactsServiceError), - #[error(transparent)] - Connectivity(#[from] ConnectivityError), - #[error(transparent)] - HexError(#[from] HexError), - #[error(transparent)] - WalletError(#[from] WalletError), - #[error(transparent)] - WalletStorageError(#[from] WalletStorageError), - #[error("Could not convert string into Public Key")] - PublicKeyParseError, - #[error("Could not convert string into Net Address")] - AddressParseError, - #[error("Peer did not include an address")] - NoAddress, - #[error("Specified burn proof file already exists")] - BurnProofFileExists, - #[error("Channel send error: `{0}`")] - SendError(String), -} diff --git a/base_layer/common_types/src/encryption.rs b/base_layer/common_types/src/encryption.rs index 8983b26187..7de8e7bd90 100644 --- a/base_layer/common_types/src/encryption.rs +++ b/base_layer/common_types/src/encryption.rs @@ -41,6 +41,7 @@ pub trait Encryptable { const COMPLETED_TRANSACTION: &'static [u8] = b"COMPLETED_TRANSACTION"; const KNOWN_ONESIDED_PAYMENT_SCRIPT: &'static [u8] = b"KNOWN_ONESIDED_PAYMENT_SCRIPT"; const CLIENT_KEY_VALUE: &'static [u8] = b"CLIENT_KEY_VALUE"; + const BURNT_PROOF: &'static [u8] = b"BURNT_PROOF"; fn domain(&self, field_name: &'static str) -> Vec; fn encrypt(self, cipher: &C) -> Result diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index 20b42551ce..99b677963b 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -130,6 +130,13 @@ diesel::table! { } } +diesel::table! { + burnt_proofs (id) { + id -> Integer, + value -> Text, + } +} + diesel::allow_tables_to_appear_in_same_query!( client_key_values, completed_transactions, @@ -139,4 +146,5 @@ diesel::allow_tables_to_appear_in_same_query!( outputs, scanned_blocks, wallet_settings, + burnt_proofs, ); diff --git a/base_layer/wallet/src/storage/database.rs b/base_layer/wallet/src/storage/database.rs index 4ab418ba76..00b0e496e7 100644 --- a/base_layer/wallet/src/storage/database.rs +++ b/base_layer/wallet/src/storage/database.rs @@ -79,6 +79,8 @@ pub enum DbKey { WalletBirthday, LastAccessedNetwork, LastAccessedVersion, + BurntProofId(i32), + ListBurntProofs, } impl DbKey { @@ -98,6 +100,8 @@ impl DbKey { DbKey::CommsIdentitySignature => "CommsIdentitySignature".to_string(), DbKey::LastAccessedNetwork => "LastAccessedNetwork".to_string(), DbKey::LastAccessedVersion => "LastAccessedVersion".to_string(), + DbKey::BurntProofId(id) => format!("BurntProof.{}", id), + DbKey::ListBurntProofs => "ListBurntProofs".to_string(), } } } @@ -118,6 +122,8 @@ pub enum DbValue { WalletBirthday(String), LastAccessedNetwork(String), LastAccessedVersion(String), + BurntProofs(Vec<(i32, String)>), + BurntProof((i32, String)), } #[derive(Clone)] @@ -130,6 +136,7 @@ pub enum DbKeyValuePair { CommsFeatures(PeerFeatures), CommsIdentitySignature(Box), NetworkAndVersion((String, String)), + BurntProof((i32, String)), } pub enum WriteOperation { @@ -351,6 +358,31 @@ where T: WalletBackend + 'static self.db.clear_scanned_blocks_before_height(height, exclude_recovered)?; Ok(()) } + + // ---------------------------------------------------------------------------- + // burnt proofs + + pub fn insert_burn_proof(&self, id: i32, proof: String) -> Result<(), WalletStorageError> { + self.db + .write(WriteOperation::Insert(DbKeyValuePair::BurntProof((id, proof))))?; + Ok(()) + } + + pub fn remove_burnt_proof(&self, id: i32) -> Result<(), WalletStorageError> { + self.db.write(WriteOperation::Remove(DbKey::BurntProofId(id)))?; + Ok(()) + } + + pub fn get_burnt_proofs(&self) -> Result, WalletStorageError> { + let proofs = match self.db.fetch(&DbKey::ListBurntProofs) { + Ok(None) => Ok(vec![]), + Ok(Some(DbValue::BurntProofs(proofs))) => Ok(proofs), + Ok(Some(other)) => unexpected_result(DbKey::ListBurntProofs, other), + Err(e) => log_error(DbKey::ListBurntProofs, e), + }?; + + Ok(proofs) + } } impl Display for DbValue { @@ -371,6 +403,8 @@ impl Display for DbValue { DbValue::CommsIdentitySignature(_) => f.write_str("CommsIdentitySignature"), DbValue::LastAccessedNetwork(network) => f.write_str(&format!("LastAccessedNetwork: {}", network)), DbValue::LastAccessedVersion(version) => f.write_str(&format!("LastAccessedVersion: {}", version)), + DbValue::BurntProofs(proofs) => f.write_str(&format!("BurntProofs: {:?}", proofs)), + DbValue::BurntProof((id, _)) => f.write_str(&format!("BurntProof: {}", id)), } } } diff --git a/base_layer/wallet/src/storage/sqlite_db/wallet.rs b/base_layer/wallet/src/storage/sqlite_db/wallet.rs index 649a842f6b..1df3bf5ea7 100644 --- a/base_layer/wallet/src/storage/sqlite_db/wallet.rs +++ b/base_layer/wallet/src/storage/sqlite_db/wallet.rs @@ -34,6 +34,7 @@ use argon2::password_hash::{ use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305}; use diesel::{prelude::*, result::Error, SqliteConnection}; use digest::{generic_array::GenericArray, FixedOutput}; +use itertools::Itertools; use log::*; use tari_common_sqlite::sqlite_connection_pool::PooledDbConnection; use tari_common_types::{ @@ -51,6 +52,7 @@ use tari_utilities::{ hex::{from_hex, Hex}, hidden_type, safe_array::SafeArray, + ByteArray, Hidden, SafePassword, }; @@ -59,7 +61,7 @@ use zeroize::Zeroize; use crate::{ error::WalletStorageError, - schema::{client_key_values, wallet_settings}, + schema::{burnt_proofs, client_key_values, wallet_settings}, storage::{ database::{DbKey, DbKeyValuePair, DbValue, WalletBackend, WriteOperation}, sqlite_db::scanned_blocks::ScannedBlockSql, @@ -422,6 +424,12 @@ impl WalletSqliteDatabase { WalletSettingSql::new(DbKey::LastAccessedNetwork, network).set(&mut conn)?; WalletSettingSql::new(DbKey::LastAccessedVersion, version).set(&mut conn)?; }, + DbKeyValuePair::BurntProof((id, proof)) => { + kvp_text = "BurntProof"; + + let cipher = acquire_read_lock!(self.cipher); + BurntProofSql::new(id, proof, &cipher)?.insert(&mut conn)?; + }, } if start.elapsed().as_millis() > 0 { @@ -463,7 +471,9 @@ impl WalletSqliteDatabase { DbKey::WalletBirthday | DbKey::CommsIdentitySignature | DbKey::LastAccessedNetwork | - DbKey::LastAccessedVersion => { + DbKey::LastAccessedVersion | + DbKey::BurntProofId(_) | + DbKey::ListBurntProofs => { return Err(WalletStorageError::OperationNotSupported); }, }; @@ -517,6 +527,12 @@ impl WalletBackend for WalletSqliteDatabase { .and_then(|bytes| IdentitySignature::from_bytes(&bytes).ok()) .map(Box::new) .map(DbValue::CommsIdentitySignature), + DbKey::BurntProofId(id) => { + BurntProofSql::get(*id, &mut conn)?.map(|BurntProofSql { id, value }| DbValue::BurntProof((id, value))) + }, + DbKey::ListBurntProofs => BurntProofSql::index(&mut conn) + .map(|proofs| DbValue::BurntProofs(proofs.into_iter().map(|x| (x.id, x.value)).collect_vec())) + .ok(), }; if start.elapsed().as_millis() > 0 { trace!( @@ -888,6 +904,90 @@ impl Encryptable for ClientKeyValueSql { } } +#[derive(Clone, Debug, Queryable, Insertable, PartialEq)] +#[diesel(table_name = burnt_proofs)] +struct BurntProofSql { + id: i32, + value: String, +} + +impl BurntProofSql { + pub fn new(id: i32, value: String, cipher: &XChaCha20Poly1305) -> Result { + let client_kv = Self { id, value }; + client_kv.encrypt(cipher).map_err(WalletStorageError::AeadError) + } + + #[allow(dead_code)] + pub fn index(conn: &mut SqliteConnection) -> Result, WalletStorageError> { + Ok(burnt_proofs::table.load::(conn)?) + } + + pub fn insert(&self, conn: &mut SqliteConnection) -> Result<(), WalletStorageError> { + diesel::replace_into(burnt_proofs::table).values(self).execute(conn)?; + + Ok(()) + } + + pub fn get(id: i32, conn: &mut SqliteConnection) -> Result, WalletStorageError> { + burnt_proofs::table + .filter(burnt_proofs::id.eq(id)) + .first::(conn) + .map(Some) + .or_else(|err| match err { + diesel::result::Error::NotFound => Ok(None), + err => Err(err.into()), + }) + } + + pub fn delete(id: i32, conn: &mut SqliteConnection) -> Result { + let num_deleted = diesel::delete(burnt_proofs::table.filter(burnt_proofs::id.eq(id))).execute(conn)?; + + Ok(num_deleted > 0) + } +} + +impl Encryptable for BurntProofSql { + fn domain(&self, field_name: &'static str) -> Vec { + [ + Self::BURNT_PROOF, + self.id.to_be_bytes().as_bytes(), + field_name.as_bytes(), + ] + .concat() + .to_vec() + } + + #[allow(unused_assignments)] + fn encrypt(mut self, cipher: &XChaCha20Poly1305) -> Result { + self.value = encrypt_bytes_integral_nonce( + cipher, + self.domain("value"), + Hidden::hide(self.value.as_bytes().to_vec()), + )? + .to_hex(); + + Ok(self) + } + + #[allow(unused_assignments)] + fn decrypt(mut self, cipher: &XChaCha20Poly1305) -> Result { + let mut decrypted_value = decrypt_bytes_integral_nonce( + cipher, + self.domain("value"), + &from_hex(self.value.as_str()).map_err(|e| e.to_string())?, + )?; + + self.value = from_utf8(decrypted_value.as_slice()) + .map_err(|e| e.to_string())? + .to_string(); + + // we zeroize the decrypted value + decrypted_value.zeroize(); + + Ok(self) + } +} + #[cfg(test)] mod test { use tari_common_sqlite::sqlite_connection_pool::PooledDbConnection;