From 176df6f93e0a459c62b31c4a5ae99bd2968feefb Mon Sep 17 00:00:00 2001 From: ardocrat Date: Mon, 25 May 2026 16:44:51 +0300 Subject: [PATCH] wallet: fix tx repost, delete txs with 0 amount, trim message to parse --- src/gui/views/wallets/wallet/content.rs | 54 ++++----- src/gui/views/wallets/wallet/message.rs | 3 +- src/gui/views/wallets/wallet/txs/content.rs | 12 ++ src/gui/views/wallets/wallet/txs/tx.rs | 4 +- src/wallet/config.rs | 26 ++-- src/wallet/types.rs | 49 ++------ src/wallet/wallet.rs | 125 +++++++++++++++----- 7 files changed, 151 insertions(+), 122 deletions(-) diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 0adf969..147643e 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::scroll_area::ScrollBarVisibility; -use egui::{Id, Margin, RichText, ScrollArea}; -use grin_chain::SyncStatus; - use crate::AppConfig; use crate::gui::Colors; use crate::gui::icons::{ @@ -35,6 +31,10 @@ use crate::node::Node; use crate::wallet::types::{ConnectionMethod, WalletData, WalletTask}; use crate::wallet::{ExternalConnection, Wallet}; +use egui::scroll_area::ScrollBarVisibility; +use egui::{Id, Margin, RichText, ScrollArea}; +use grin_chain::SyncStatus; + /// Wallet content. pub struct WalletContent { /// Transactions content. @@ -52,8 +52,6 @@ pub struct WalletContent { invoice_content: Option, /// Send request creation [`Modal`] content. send_content: Option, - /// Slatepack message input [`Modal`] content. - message_content: Option, } /// Identifier for invoice creation [`Modal`]. @@ -63,11 +61,7 @@ const SEND_MODAL_ID: &'static str = "send_request_modal"; impl WalletContentContainer for WalletContent { fn modal_ids(&self) -> Vec<&'static str> { - vec![ - INVOICE_MODAL_ID, - SEND_MODAL_ID, - MessageInputContent::MODAL_ID, - ] + vec![INVOICE_MODAL_ID, SEND_MODAL_ID] } fn modal_ui(&mut self, ui: &mut egui::Ui, w: &Wallet, m: &Modal, cb: &dyn PlatformCallbacks) { @@ -84,17 +78,7 @@ impl WalletContentContainer for WalletContent { } self.send_content.as_mut().unwrap().ui(ui, w, m, cb); } - MessageInputContent::MODAL_ID => { - if self.message_content.is_none() { - self.message_content = Some(MessageInputContent::default()); - } - self.message_content.as_mut().unwrap().ui(ui, w, m, cb); - } - _ => { - self.invoice_content = None; - self.send_content = None; - self.message_content = None; - } + _ => {} } } @@ -309,7 +293,6 @@ impl Default for WalletContent { transport_content: WalletTransportContent::default(), invoice_content: None, send_content: None, - message_content: None, } } } @@ -392,7 +375,8 @@ impl WalletContent { self.txs_content = Some(WalletTransactionsContent::new(None)); self.settings_content = None; } - self.message_content = None; + self.txs_content.as_mut().unwrap().message_content = None; + self.invoice_content = Some(InvoiceRequestContent::default()); Modal::new(INVOICE_MODAL_ID) .position(ModalPosition::CenterTop) @@ -412,7 +396,8 @@ impl WalletContent { self.txs_content = Some(WalletTransactionsContent::new(None)); self.settings_content = None; } - self.message_content = Some(MessageInputContent::default()); + self.txs_content.as_mut().unwrap().message_content = + Some(MessageInputContent::default()); Modal::new(MessageInputContent::MODAL_ID) .position(ModalPosition::Center) .title(t!("wallets.messages")) @@ -432,7 +417,8 @@ impl WalletContent { self.txs_content = Some(WalletTransactionsContent::new(None)); self.settings_content = None; } - self.message_content = None; + self.txs_content.as_mut().unwrap().message_content = None; + self.send_content = Some(SendRequestContent::new(None)); Modal::new(SEND_MODAL_ID) .position(ModalPosition::CenterTop) @@ -495,25 +481,27 @@ impl WalletContent { } MessageInputContent::MODAL_ID => { match t { - WalletTask::VerifyProof(proof, res) => { + WalletTask::VerifyProof(p, res) => { if let Some(res) = res { // Update message content on validation error // or when tx not belongs to current wallet. if res.is_err() || (!res.as_ref().unwrap().1 && !res.as_ref().unwrap().2) { - if let Some(m) = self.message_content.as_mut() { - if let Ok(p) = serde_json::to_string_pretty(&proof) { - let mut c = PaymentProofContent::new(Some(p)); - c.validation_result = Some(res); - m.proof_content = Some(c); + if let Some(txs) = self.txs_content.as_mut() { + if let Some(m) = txs.message_content.as_mut() { + if let Ok(p) = serde_json::to_string_pretty(&p) { + let mut c = PaymentProofContent::new(Some(p)); + c.validation_result = Some(res); + m.proof_content = Some(c); + } } } } else if let Ok((id, _, _)) = res { let tx = data.tx_by_id(id); if let Some(tx) = tx { let mut tx_c = WalletTransactionsContent::new(Some(tx)); - if let Ok(p) = serde_json::to_string_pretty(&proof) { + if let Ok(p) = serde_json::to_string_pretty(&p) { let proof_c = PaymentProofContent::new(Some(p)); tx_c.tx_info_content .as_mut() diff --git a/src/gui/views/wallets/wallet/message.rs b/src/gui/views/wallets/wallet/message.rs index 8669584..61fc27e 100644 --- a/src/gui/views/wallets/wallet/message.rs +++ b/src/gui/views/wallets/wallet/message.rs @@ -109,6 +109,7 @@ impl MessageInputContent { ui.vertical_centered_justified(|ui| { View::button(ui, t!("close"), Colors::white_or_black(false), || { self.message_edit = "".to_string(); + self.parse_error = false; Modal::close(); }); }); @@ -240,7 +241,7 @@ impl MessageInputContent { /// Parse Slatepack message on input change. fn on_message_input(&mut self, text: String, wallet: &Wallet) { self.parse_error = false; - self.message_edit = text; + self.message_edit = text.trim().to_string(); if self.message_edit.is_empty() { return; } diff --git a/src/gui/views/wallets/wallet/txs/content.rs b/src/gui/views/wallets/wallet/txs/content.rs index 0f12033..b3970ff 100644 --- a/src/gui/views/wallets/wallet/txs/content.rs +++ b/src/gui/views/wallets/wallet/txs/content.rs @@ -32,6 +32,7 @@ use crate::gui::icons::{ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::types::{LinePosition, ModalPosition}; use crate::gui::views::wallets::wallet::WalletTransactionContent; +use crate::gui::views::wallets::wallet::message::MessageInputContent; use crate::gui::views::wallets::wallet::types::{GRIN, WalletContentContainer}; use crate::gui::views::{Content, Modal, PullToRefresh, View}; use crate::wallet::Wallet; @@ -41,6 +42,8 @@ use crate::wallet::types::{WalletData, WalletTask, WalletTx, WalletTxAction}; pub struct WalletTransactionsContent { /// Transaction information [`Modal`] content. pub tx_info_content: Option, + /// Message input [`Modal`] content. + pub message_content: Option, /// Transaction identifier to use at confirmation [`Modal`] to cancel. confirm_cancel_tx_id: Option, @@ -64,6 +67,7 @@ impl WalletContentContainer for WalletTransactionsContent { TX_INFO_MODAL, CANCEL_TX_CONFIRMATION_MODAL, DELETE_TX_CONFIRMATION_MODAL, + MessageInputContent::MODAL_ID, ] } @@ -86,6 +90,12 @@ impl WalletContentContainer for WalletTransactionsContent { DELETE_TX_CONFIRMATION_MODAL => { self.delete_confirmation_modal(ui, w); } + MessageInputContent::MODAL_ID => { + if self.message_content.is_none() { + self.message_content = Some(MessageInputContent::default()); + } + self.message_content.as_mut().unwrap().ui(ui, w, m, cb); + } _ => {} } } @@ -103,6 +113,7 @@ impl WalletTransactionsContent { pub fn new(tx: Option) -> Self { let mut content = Self { tx_info_content: None, + message_content: None, confirm_cancel_tx_id: None, confirm_delete_tx_id: None, manual_sync: None, @@ -647,6 +658,7 @@ impl WalletTransactionsContent { /// Show transaction information [`Modal`]. fn show_tx_info_modal(&mut self, id: u32) { let modal = WalletTransactionContent::new(id); + self.message_content = None; self.tx_info_content = Some(modal); Modal::new(TX_INFO_MODAL) .position(ModalPosition::Center) diff --git a/src/gui/views/wallets/wallet/txs/tx.rs b/src/gui/views/wallets/wallet/txs/tx.rs index e3edee8..8d864ae 100644 --- a/src/gui/views/wallets/wallet/txs/tx.rs +++ b/src/gui/views/wallets/wallet/txs/tx.rs @@ -168,7 +168,9 @@ impl WalletTransactionContent { cb: &dyn PlatformCallbacks, ) { if self.message.is_none() { - let slatepack_path = wallet.get_config().get_tx_slate_path(tx); + let slatepack_path = wallet + .get_config() + .get_slate_path(tx.data.tx_slate_id.unwrap(), &tx.state); self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string())); } if let Some(m) = &self.message { diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 534afdf..d4398ee 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -16,14 +16,14 @@ use std::fs; use std::path::PathBuf; use std::string::ToString; +use crate::wallet::ConnectionsConfig; +use crate::wallet::types::ConnectionMethod; +use crate::{AppConfig, Settings}; use grin_core::global::ChainTypes; -use grin_wallet_libwallet::Slate; +use grin_wallet_libwallet::SlateState; use rand::Rng; use serde_derive::{Deserialize, Serialize}; - -use crate::wallet::ConnectionsConfig; -use crate::wallet::types::{ConnectionMethod, WalletTx}; -use crate::{AppConfig, Settings}; +use uuid::Uuid; /// Wallet configuration. #[derive(Serialize, Deserialize, Clone)] @@ -227,26 +227,14 @@ impl WalletConfig { path.to_str().unwrap().to_string() } - /// Get Slatepack file path for transaction. - pub fn get_tx_slate_path(&self, tx: &WalletTx) -> PathBuf { - let mut path = PathBuf::from(self.get_base_data_path()); - path.push(SLATEPACKS_DIR_NAME); - if !path.exists() { - let _ = fs::create_dir_all(path.clone()); - } - let file = format!("{}.{}.slatepack", tx.data.tx_slate_id.unwrap(), tx.state); - path.push(file); - path - } - /// Get Slatepack file path for Slate. - pub fn get_slate_path(&self, slate: &Slate) -> PathBuf { + pub fn get_slate_path(&self, id: Uuid, state: &SlateState) -> PathBuf { let mut path = PathBuf::from(self.get_base_data_path()); path.push(SLATEPACKS_DIR_NAME); if !path.exists() { let _ = fs::create_dir_all(path.clone()); } - let file = format!("{}.{}.slatepack", slate.id, slate.state); + let file = format!("{}.{}.slatepack", id, state); path.push(file); path } diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 05d46b4..0e3b188 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -16,8 +16,8 @@ use grin_keychain::ExtKeychain; use grin_util::Mutex; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; use grin_wallet_libwallet::{ - Error, PaymentProof, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, - WalletInfo, WalletInst, + Error, PaymentProof, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, WalletInfo, + WalletInst, }; use grin_wallet_util::OnionV3Address; use serde_derive::{Deserialize, Serialize}; @@ -276,46 +276,15 @@ impl WalletTx { action_error, }; // Update Slate state for unconfirmed. - if !t.data.confirmed { - t.update_slate_state(wallet); - } - t - } - - /// Update transaction [`Slate`] state for provided wallet. - pub fn update_slate_state(&mut self, wallet: &Wallet) { - let tx = &self.data; - let mut slate = Slate::blank(1, false); - slate.id = tx.tx_slate_id.unwrap(); - slate.state = match tx.tx_type { - TxLogEntryType::TxReceived => SlateState::Invoice3, - _ => SlateState::Standard3, - }; - // Transaction was finalized. - if wallet.slatepack_exists(&slate) { - self.state = slate.state; - } else { - slate.id = tx.tx_slate_id.unwrap(); - slate.state = match tx.tx_type { - TxLogEntryType::TxReceived => SlateState::Standard2, - _ => SlateState::Invoice2, - }; - // Transaction signed to be finalized. - if wallet.slatepack_exists(&slate) { - self.state = slate.state; - } else { - // Transaction just was created. - slate.state = match tx.tx_type { - TxLogEntryType::TxReceived => SlateState::Invoice1, - _ => SlateState::Standard1, - }; - if wallet.slatepack_exists(&slate) { - self.state = slate.state; - } else { - self.state = SlateState::Unknown; - } + if !t.data.confirmed + && (t.data.tx_type == TxLogEntryType::TxSent + || t.data.tx_type == TxLogEntryType::TxReceived) + { + if let Some(slate_id) = t.data.tx_slate_id { + t.state = wallet.get_slate_state(slate_id, &t.data.tx_type) } } + t } /// Check if transactions can be finalized after receiving response. diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index cb46e96..2cb2cba 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -695,11 +695,46 @@ impl Wallet { // Apply limit. .take(limit as usize) .collect(); - // Reverse an order. - // txs.reverse(); Ok(txs) } + /// Delete txs with 0 amount. + fn clear_empty_txs(&self) -> Result<(), Error> { + let txs: Vec = { + let r_inst = self.instance.as_ref().read(); + let inst = r_inst.clone().unwrap(); + let mut wallet_lock = inst.lock(); + let lc = wallet_lock.lc_provider()?; + let w = lc.wallet_inst()?; + let parent_key_id = w.parent_key_id(); + // Retrieve txs from database. + w.tx_log_iter() + .filter(|tx_entry| tx_entry.parent_key_id == parent_key_id) + .filter(|tx_entry| { + if tx_entry.tx_type == TxLogEntryType::TxSent + || tx_entry.tx_type == TxLogEntryType::TxSentCancelled + { + BigInt::from(tx_entry.amount_debited) + - BigInt::from(tx_entry.amount_credited) + == BigInt::from(0) + } else if tx_entry.tx_type == TxLogEntryType::TxReceived + || tx_entry.tx_type == TxLogEntryType::TxReceivedCancelled + { + BigInt::from(tx_entry.amount_credited) + - BigInt::from(tx_entry.amount_debited) + == BigInt::from(0) + } else { + false + } + }) + .collect() + }; + for t in &txs { + self.delete_tx(t.id)?; + } + Ok(()) + } + /// Send a task to the wallet. pub fn task(&self, task: WalletTask) { let r_tasks = self.tasks_sender.read(); @@ -896,7 +931,7 @@ impl Wallet { &mut api, self.keychain_mask().as_ref(), None, - Some(text.clone()), + Some(text.trim().to_string()), ) { Ok(s) => Ok(s), Err(e) => Err(e), @@ -907,7 +942,7 @@ impl Wallet { fn create_slatepack_message( &self, slate: &Slate, - dest: Option, + _: Option, ) -> Result { let mut message = "".to_string(); let r_inst = self.instance.as_ref().read(); @@ -918,17 +953,17 @@ impl Wallet { self.keychain_mask().as_ref(), Some(&mut api), |api, m| { - let recipients = match dest { - Some(a) => vec![a], - None => vec![], - }; - message = api.create_slatepack_message(m, &slate, Some(0), recipients)?; + // let recipients = match dest { + // Some(a) => vec![a], + // None => vec![], + // }; + message = api.create_slatepack_message(m, &slate, Some(0), vec![])?; Ok(()) }, )?; // Write Slatepack message to file. - let slatepack_dir = self.get_config().get_slate_path(&slate); + let slatepack_dir = self.get_config().get_slate_path(slate.id, &slate.state); let mut output = File::create(slatepack_dir)?; output.write_all(message.as_bytes())?; output.sync_all()?; @@ -937,10 +972,44 @@ impl Wallet { /// Check if Slatepack file exists. pub fn slatepack_exists(&self, slate: &Slate) -> bool { - let slatepack_path = self.get_config().get_slate_path(slate); + let slatepack_path = self.get_config().get_slate_path(slate.id, &slate.state); fs::exists(slatepack_path).unwrap_or(false) } + /// Get possible state from tx type. + pub fn get_slate_state(&self, slate_id: Uuid, tx_type: &TxLogEntryType) -> SlateState { + let mut slate = Slate::blank(1, false); + slate.id = slate_id; + slate.state = match tx_type { + TxLogEntryType::TxReceived => SlateState::Invoice3, + _ => SlateState::Standard3, + }; + // Transaction was finalized. + if self.slatepack_exists(&slate) { + slate.state + } else { + slate.state = match tx_type { + TxLogEntryType::TxReceived => SlateState::Standard2, + _ => SlateState::Invoice2, + }; + // Transaction signed to be finalized. + if self.slatepack_exists(&slate) { + slate.state + } else { + // Transaction just was created. + slate.state = match tx_type { + TxLogEntryType::TxReceived => SlateState::Invoice1, + _ => SlateState::Standard1, + }; + if self.slatepack_exists(&slate) { + slate.state + } else { + SlateState::Unknown + } + } + } + } + /// Calculate transaction fee for provided amount. fn calculate_fee(&self, a: u64) -> Result { let r_inst = self.instance.as_ref().read(); @@ -1247,12 +1316,16 @@ impl Wallet { } /// Get stored transaction Slate. - fn get_tx_slate(&self, tx_id: Option, slate_id: Option<&Uuid>) -> Option { - let r_inst = self.instance.as_ref().read(); - let instance = r_inst.clone().unwrap(); - let api = Owner::new(instance, None); - if let Ok(s) = api.get_stored_tx(self.keychain_mask().as_ref(), tx_id, slate_id) { - return s; + fn get_tx_slate(&self, tx_id: u32) -> Option { + if let Some(tx) = self.retrieve_tx_by_id(Some(tx_id), None) { + if let Some(slate_id) = tx.tx_slate_id { + let slate_state = self.get_slate_state(slate_id, &tx.tx_type); + let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state); + let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string()); + if let Ok((slate, _)) = self.parse_slatepack(&msg) { + return Some(slate); + } + } } None } @@ -1261,7 +1334,7 @@ impl Wallet { fn delete_tx(&self, id: u32) -> Result<(), Error> { self.on_tx_action(id, Some(WalletTxAction::Deleting)); - let slate = self.get_tx_slate(Some(id), None); + let slate = self.get_tx_slate(id); let r_inst = self.instance.as_ref().read(); let instance = r_inst.clone().unwrap(); let keychain_mask = self.keychain_mask(); @@ -1275,7 +1348,7 @@ impl Wallet { // Delete transaction files. if let Some(s) = slate { - let slatepack_path = self.get_config().get_slate_path(&s); + let slatepack_path = self.get_config().get_slate_path(s.id, &s.state); fs::remove_file(&slatepack_path).unwrap_or_default(); let path = path::Path::new(&self.get_config().get_data_path()) .join("saved_txs") @@ -1800,6 +1873,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) { WalletTask::Send(a, r) => { w.send_creating.store(true, Ordering::Relaxed); if let Ok(s) = w.send(*a, r.clone()) { + error!("send amount: {}", s.amount); sync_wallet_data(&w, false); let tx = w.retrieve_tx_by_id(None, Some(s.id)); if let Some(tx) = tx { @@ -1820,7 +1894,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) { w.send_creating.store(false, Ordering::Relaxed); } WalletTask::SendTor(tx, r) => { - if let Some(s) = w.get_tx_slate(Some(tx.id), None) { + if let Some(s) = w.get_tx_slate(tx.id) { send_tor(tx.clone(), &s, r).await; } } @@ -1836,7 +1910,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) { w.invoice_creating.store(false, Ordering::Relaxed); } WalletTask::Finalize(id) => { - if let Some(s) = w.get_tx_slate(Some(*id), None) { + if let Some(s) = w.get_tx_slate(*id) { w.on_tx_error(*id, None); match w.finalize(&s, *id) { Ok(s) => match w.post(&s, Some(*id)) { @@ -1859,7 +1933,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) { } } WalletTask::Post(id) => { - if let Some(s) = w.get_tx_slate(Some(*id), None) { + if let Some(s) = w.get_tx_slate(*id) { w.on_tx_error(*id, None); // Cleanup broadcasting tx height. let tx_height_store = TxHeightStore::new(w.get_config().get_extra_db_path()); @@ -2031,6 +2105,7 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { /// Update wallet transactions. fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> { + let _ = wallet.clear_empty_txs(); let txs = wallet.retrieve_txs(txs_limit)?; // Exit if wallet was closed. @@ -2076,12 +2151,6 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> { action, action_error, ); - // Update Slate state for unconfirmed. - let unconfirmed = !tx.confirmed - && (tx.tx_type == TxLogEntryType::TxSent || tx.tx_type == TxLogEntryType::TxReceived); - if unconfirmed { - new.update_slate_state(wallet); - } // Payment proof setup. if proof.is_none() && tx.payment_proof.is_some()