wallet: fix tx repost, delete txs with 0 amount, trim message to parse

This commit is contained in:
ardocrat
2026-05-25 16:44:51 +03:00
parent f7287bd9ad
commit 176df6f93e
7 changed files with 151 additions and 122 deletions
+21 -33
View File
@@ -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<InvoiceRequestContent>,
/// Send request creation [`Modal`] content.
send_content: Option<SendRequestContent>,
/// Slatepack message input [`Modal`] content.
message_content: Option<MessageInputContent>,
}
/// 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()
+2 -1
View File
@@ -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;
}
@@ -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<WalletTransactionContent>,
/// Message input [`Modal`] content.
pub message_content: Option<MessageInputContent>,
/// Transaction identifier to use at confirmation [`Modal`] to cancel.
confirm_cancel_tx_id: Option<u32>,
@@ -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<WalletTx>) -> 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)
+3 -1
View File
@@ -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 {
+7 -19
View File
@@ -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
}
+9 -40
View File
@@ -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.
+97 -28
View File
@@ -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<TxLogEntry> = {
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<SlatepackAddress>,
_: Option<SlatepackAddress>,
) -> Result<String, Error> {
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<u64, Error> {
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<u32>, slate_id: Option<&Uuid>) -> Option<Slate> {
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<Slate> {
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()