From 2f56defffaecf6013f3b5a680ea8e8fb09819f96 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 25 Jun 2025 11:08:57 +0300 Subject: [PATCH] wallet: broadcasting delay, repeat tx action --- src/gui/views/wallets/wallet/txs/content.rs | 52 +++++- src/gui/views/wallets/wallet/txs/tx.rs | 17 +- src/wallet/config.rs | 6 + src/wallet/store.rs | 59 +++++-- src/wallet/types.rs | 112 +++++++++++-- src/wallet/wallet.rs | 167 +++++++++----------- 6 files changed, 291 insertions(+), 122 deletions(-) diff --git a/src/gui/views/wallets/wallet/txs/content.rs b/src/gui/views/wallets/wallet/txs/content.rs index 077f13d4..60ef3aea 100644 --- a/src/gui/views/wallets/wallet/txs/content.rs +++ b/src/gui/views/wallets/wallet/txs/content.rs @@ -21,7 +21,7 @@ use grin_wallet_libwallet::TxLogEntryType; use std::ops::Range; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::gui::icons::{ARCHIVE_BOX, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE}; +use crate::gui::icons::{ARCHIVE_BOX, ARROWS_CLOCKWISE, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::types::{LinePosition, ModalPosition}; use crate::gui::views::wallets::types::WalletTab; @@ -89,6 +89,7 @@ impl WalletTransactions { /// Draw transactions content. fn txs_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) { let data = wallet.get_data().unwrap(); + let config = wallet.get_config(); if data.txs.is_none() { ui.centered_and_justified(|ui| { View::big_loading_spinner(ui); @@ -125,13 +126,13 @@ impl WalletTransactions { .min_refresh_distance(70.0) .scroll_area_ui(ui, |ui| { ScrollArea::vertical() - .id_salt(Id::from("wallet_tx_list_scroll").with(wallet.get_config().id)) + .id_salt(Id::from("wallet_tx_list_scroll").with(config.id)) .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .auto_shrink([false; 2]) .show_rows(ui, Self::TX_ITEM_HEIGHT, txs.len(), |ui, row_range| { ui.add_space(1.0); View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| { - self.tx_list_ui(ui, row_range, wallet, txs); + self.tx_list_ui(ui, row_range, &wallet, txs); }); }) }); @@ -151,6 +152,7 @@ impl WalletTransactions { row_range: Range, wallet: &Wallet, txs: &Vec) { + let data = wallet.get_data().unwrap(); for index in row_range { let mut rect = ui.available_rect_before_wrap(); rect.min += egui::emath::vec2(6.0, 0.0); @@ -163,7 +165,6 @@ impl WalletTransactions { p.rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Middle); let tx = txs.get(index).unwrap(); - let data = wallet.get_data().unwrap(); Self::tx_item_ui(ui, tx, rect, &data, |ui| { // Draw button to show transaction info. if tx.data.tx_slate_id.is_some() { @@ -173,8 +174,11 @@ impl WalletTransactions { self.show_tx_info_modal(tx.data.id); }); } + + let rebroadcast = tx.broadcasting_timed_out(wallet); + // Draw button to cancel transaction. - if tx.can_cancel() { + if tx.can_cancel() || rebroadcast { let (icon, color) = (PROHIBIT, Some(Colors::red())); View::item_button(ui, CornerRadius::default(), icon, color, || { self.confirm_cancel_tx_id = Some(tx.data.id); @@ -185,9 +189,10 @@ impl WalletTransactions { .show(); }); } - //TODO: Draw button to repeat transaction task on error. - if tx.action_error.is_some() { - + + // Draw button to repeat transaction action or resend with tor. + if tx.action_error.is_some() || rebroadcast || tx.can_resend_tor() { + Self::tx_repeat_button_ui(ui, CornerRadius::default(), tx, wallet, rebroadcast); } }); } @@ -466,6 +471,37 @@ impl WalletTransactions { }); } + /// Draw button to repeat transaction action on error or repost. + pub fn tx_repeat_button_ui(ui: &mut egui::Ui, + rounding: CornerRadius, + tx: &WalletTransaction, + wallet: &Wallet, + repost: bool) { + let (icon, color) = (ARROWS_CLOCKWISE, Some(Colors::green())); + View::item_button(ui, rounding, icon, color, || { + if repost { + wallet.task(WalletTask::Post(None, tx.data.id)); + } else { + match tx.action.as_ref().unwrap() { + WalletTransactionAction::Cancelling => { + wallet.task(WalletTask::Cancel(tx.clone())); + } + WalletTransactionAction::Finalizing => { + wallet.task(WalletTask::Finalize(None, tx.data.id)); + } + WalletTransactionAction::Posting => { + wallet.task(WalletTask::Post(None, tx.data.id)); + } + WalletTransactionAction::SendingTor => { + if let Some(a) = &tx.receiver { + wallet.task(WalletTask::SendTor(tx.data.id, a.clone())); + } + } + } + } + }); + } + /// Show transaction information [`Modal`]. fn show_tx_info_modal(&mut self, id: u32) { let modal = WalletTransactionContent::new(id); diff --git a/src/gui/views/wallets/wallet/txs/tx.rs b/src/gui/views/wallets/wallet/txs/tx.rs index 08a960f8..f80fc602 100644 --- a/src/gui/views/wallets/wallet/txs/tx.rs +++ b/src/gui/views/wallets/wallet/txs/tx.rs @@ -257,8 +257,11 @@ impl WalletTransactionContent { self.scan_qr_content = Some(CameraContent::default()); }); } + + let rebroadcast = tx.broadcasting_timed_out(&wallet); + // Draw button to cancel transaction. - if tx.can_cancel() { + if tx.can_cancel() || rebroadcast { let r = if tx.can_finalize() { CornerRadius::default() } else { @@ -269,6 +272,16 @@ impl WalletTransactionContent { Modal::close(); }); } + + // Draw button to repeat transaction action. + if tx.action_error.is_some() || rebroadcast || tx.can_resend_tor() { + let r = if tx.can_finalize() || tx.can_cancel() { + CornerRadius::default() + } else { + View::item_rounding(0, 2, true) + }; + WalletTransactions::tx_repeat_button_ui(ui, r, tx, wallet, rebroadcast); + } }); // Show identifier. @@ -282,7 +295,7 @@ impl WalletTransactionContent { info_item_ui(ui, kernel.0.to_hex(), label, true, cb); } // Show receiver address. - if let Some(rec) = tx.receiver() { + if let Some(rec) = &tx.receiver { let label = format!("{} {}", CIRCLE_HALF, t!("network_mining.address")); info_item_ui(ui, rec.to_string(), label, true, cb); } diff --git a/src/wallet/config.rs b/src/wallet/config.rs index ae25c7a0..d165ad4e 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -46,6 +46,8 @@ pub struct WalletConfig { pub enable_tor_listener: Option, /// Wallet API port. pub api_port: Option, + /// Delay in blocks before another transaction broadcasting attempt. + pub tx_broadcast_timeout: Option, } /// Base wallets directory name. @@ -68,6 +70,9 @@ impl WalletConfig { /// Default account name value. pub const DEFAULT_ACCOUNT_LABEL: &'static str = "default"; + /// Default value of timeout for broadcasting transaction in blocks. + pub const BROADCASTING_TIMEOUT_DEFAULT: u64 = 10; + /// Create new wallet config. pub fn create(name: String, conn_method: &ConnectionMethod) -> WalletConfig { // Setup configuration path. @@ -88,6 +93,7 @@ impl WalletConfig { use_dandelion: Some(true), enable_tor_listener: Some(false), api_port: Some(rand::rng().random_range(10000..30000)), + tx_broadcast_timeout: Some(Self::BROADCASTING_TIMEOUT_DEFAULT), }; Settings::write_to_file(&config, config_path); config diff --git a/src/wallet/store.rs b/src/wallet/store.rs index d76f39ca..feccb5be 100644 --- a/src/wallet/store.rs +++ b/src/wallet/store.rs @@ -16,32 +16,37 @@ use std::sync::{Arc, RwLock}; use rkv::backend::{Lmdb, LmdbDatabase, LmdbEnvironment}; use rkv::{IntegerStore, Manager, Rkv, StoreOptions, Value}; -/// Transaction confirmation height storage. +/// Transaction height storage. pub struct TxHeightStore { - env_arc: Arc>>, - store: IntegerStore + env: Arc>>, + /// Confirmed heights. + confirmed: IntegerStore, + /// Broadcasting heights. + broadcasting: IntegerStore } impl TxHeightStore { - /// Create new transaction height storage at provided directory. + /// Create new transaction height storage from provided directory. pub fn new(dir: String) -> Self { let mut manager = Manager::::singleton().write().unwrap(); let env_arc = manager.get_or_create(std::path::Path::new(&dir), Rkv::new::).unwrap(); let env_arc_store = env_arc.clone(); let env = env_arc_store.read().unwrap(); - let store = env.open_integer("tx_height", StoreOptions::create()).unwrap(); + let confirmed = env.open_integer("tx_height", StoreOptions::create()).unwrap(); + let broadcasting = env.open_integer("broadcast_tx_height", StoreOptions::create()).unwrap(); Self { - env_arc, - store + env: env_arc, + confirmed, + broadcasting } } /// Read transaction height from database. pub fn read_tx_height(&self, id: u32) -> Option { - let env = self.env_arc.read().unwrap(); + let env = self.env.read().unwrap(); let reader = env.read().unwrap(); - if let Ok(value) = self.store.get(&reader, id) { + if let Ok(value) = self.confirmed.get(&reader, id) { if let Some(height) = value { return match height { Value::U64(v) => Some(v), @@ -55,9 +60,41 @@ impl TxHeightStore { /// Write transaction height to database. pub fn write_tx_height(&self, id: u32, height: u64) { - let env = self.env_arc.read().unwrap(); + let env = self.env.read().unwrap(); let mut writer = env.write().unwrap(); - self.store.put(&mut writer, id, &Value::U64(height)).unwrap(); + self.confirmed.put(&mut writer, id, &Value::U64(height)).unwrap(); + writer.commit().unwrap(); + } + + /// Read broadcasting height from database. + pub fn read_broadcasting_height(&self, id: u32) -> Option { + let env = self.env.read().unwrap(); + let reader = env.read().unwrap(); + if let Ok(value) = self.broadcasting.get(&reader, id) { + if let Some(height) = value { + return match height { + Value::U64(v) => Some(v), + _ => None + }; + } + return None; + } + None + } + + /// Write broadcasting height to database. + pub fn write_broadcasting_height(&self, id: u32, height: u64) { + let env = self.env.read().unwrap(); + let mut writer = env.write().unwrap(); + self.broadcasting.put(&mut writer, id, &Value::U64(height)).unwrap(); + writer.commit().unwrap(); + } + + /// Delete broadcasting height from database. + pub fn delete_broadcasting_height(&self, id: u32) { + let env = self.env.read().unwrap(); + let mut writer = env.write().unwrap(); + self.broadcasting.delete(&mut writer, id).unwrap_or_default(); writer.commit().unwrap(); } } diff --git a/src/wallet/types.rs b/src/wallet/types.rs index a6a8e0f1..c4fa34a8 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; - use grin_keychain::ExtKeychain; use grin_util::Mutex; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; use grin_wallet_libwallet::{Error, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, WalletInfo, WalletInst}; use grin_wallet_util::OnionV3Address; use serde_derive::{Deserialize, Serialize}; +use std::sync::Arc; + +use crate::wallet::Wallet; /// Mnemonic phrase word. #[derive(Clone)] @@ -212,10 +213,15 @@ pub struct WalletTransaction { pub data: TxLogEntry, /// State of transaction Slate. pub state: SlateState, - /// Calculated transaction amount between debited and credited amount. + + /// Transaction amount without fees. pub amount: u64, + /// Possible receiver of transaction. + pub receiver: Option, /// Block height where tx was included. pub height: Option, + /// Block height where tx started broadcasting. + pub broadcasting_height: Option, /// Action on transaction. pub action: Option, @@ -224,6 +230,75 @@ pub struct WalletTransaction { } impl WalletTransaction { + /// Create new wallet transaction. + pub fn new(tx: TxLogEntry, + wallet: &Wallet, + height: Option, + broadcasting_height: Option, + action: Option, + action_error: Option) -> Self { + let amount = if tx.amount_debited > tx.amount_credited { + tx.amount_debited - tx.amount_credited + } else { + tx.amount_credited - tx.amount_debited + }; + let receiver: Option = { + if let Some(proof) = &tx.payment_proof { + let onion_addr = OnionV3Address::from_bytes(proof.receiver_address.to_bytes()); + if let Ok(addr) = SlatepackAddress::try_from(onion_addr) { + Some(addr); + } + } + None + }; + let mut t = Self { + data: tx, + state: SlateState::Unknown, + amount, + receiver, + height, + broadcasting_height, + action, + 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. + self.state = match tx.tx_type { + TxLogEntryType::TxReceived => SlateState::Invoice1, + _ => SlateState::Standard1 + }; + } + } + } + /// Check if transactions can be finalized after receiving response. pub fn can_finalize(&self) -> bool { !self.cancelling() && !self.data.confirmed && @@ -266,12 +341,18 @@ impl WalletTransaction { /// Check if transaction can be cancelled. pub fn can_cancel(&self) -> bool { - !self.cancelling() && !self.data.confirmed && + !self.cancelling() && !self.data.confirmed && !self.broadcasting() && (!self.sending_tor() || self.action_error.is_some()) && self.data.tx_type != TxLogEntryType::TxReceivedCancelled && self.data.tx_type != TxLogEntryType::TxSentCancelled } + /// Check if transaction can be sent over Tor again. + pub fn can_resend_tor(&self) -> bool { + !self.sending_tor() && (self.state == SlateState::Standard1 || + self.state == SlateState::Invoice1) && self.receiver.is_some() + } + /// Check if transaction is finalizing. pub fn finalizing(&self) -> bool { if let Some(a) = self.action.as_ref() { @@ -280,15 +361,22 @@ impl WalletTransaction { false } - /// Get receiver address if payment proof was created. - pub fn receiver(&self) -> Option { - if let Some(proof) = &self.data.payment_proof { - let onion_addr = OnionV3Address::from_bytes(proof.receiver_address.to_bytes()); - if let Ok(addr) = SlatepackAddress::try_from(onion_addr) { - return Some(addr); + /// Check if transaction is broadcasting after finalization. + pub fn broadcasting(&self) -> bool { + !self.data.confirmed && self.finalized() + } + + /// Check if broadcasting of transaction was timed out. + pub fn broadcasting_timed_out(&self, wallet: &Wallet) -> bool { + if let Some(data) = wallet.get_data() { + if self.broadcasting() { + let last_height = data.info.last_confirmed_height; + let broadcasting_height = self.broadcasting_height.unwrap_or(0); + let delay = wallet.broadcasting_delay(); + return last_height - broadcasting_height > delay; } } - None + false } } @@ -301,7 +389,7 @@ pub enum WalletTask { /// * amount /// * receiver Send(u64, Option), - /// Resend request over Tor. + /// Send request over Tor. /// * local tx id /// * receiver SendTor(u32, SlatepackAddress), diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index f7622a3d..f43b7812 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -420,6 +420,19 @@ impl Wallet { w_config.save(); } + /// Get transaction broadcasting delay in blocks. + pub fn broadcasting_delay(&self) -> u64 { + let r_config = self.config.read(); + r_config.tx_broadcast_timeout.unwrap_or(WalletConfig::BROADCASTING_TIMEOUT_DEFAULT) + } + + /// Update transaction broadcasting delay in blocks. + pub fn update_broadcasting_delay(&self, delay: u64) { + let mut w_config = self.config.write(); + w_config.tx_broadcast_timeout = Some(delay); + w_config.save(); + } + /// Update external connection identifier. pub fn update_connection(&self, conn: &ConnectionMethod) { let mut w_config = self.config.write(); @@ -991,30 +1004,29 @@ impl Wallet { res } - /// Get possible transaction confirmation height from db or node. - fn tx_height(&self, tx: &TxLogEntry, store: &TxHeightStore) -> Result, Error> { + /// Get possible transaction confirmation height, . + fn tx_height(&self, tx: &WalletTransaction) -> Result, Error> { let mut tx_height = None; - if tx.kernel_lookup_min_height.is_some() && tx.kernel_excess.is_some() && tx.confirmed { - if let Some(height) = store.read_tx_height(tx.id) { - tx_height = Some(height); - } else { - let r_inst = self.instance.as_ref().read(); - let instance = r_inst.clone().unwrap(); - let mut w_lock = instance.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - if let Ok(res) = w.w2n_client().get_kernel( - tx.kernel_excess.as_ref().unwrap(), - tx.kernel_lookup_min_height, - None - ) { - if let Some((_, h, _)) = res { - tx_height = Some(h); - store.write_tx_height(tx.id, h); - } else { - tx_height = Some(0); - } - } + if tx.data.confirmed && tx.data.kernel_excess.is_some() { + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut w_lock = instance.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + if let Ok(res) = w.w2n_client().get_kernel( + tx.data.kernel_excess.as_ref().unwrap(), + tx.data.kernel_lookup_min_height, + None + ) { + tx_height = Some(match res { + None => 0, + Some((_, h, _)) => h + }); } + } else if tx.broadcasting() { + tx_height = match self.get_data() { + None => None, + Some(data) => Some(data.info.last_confirmed_height) + }; } Ok(tx_height) } @@ -1117,10 +1129,8 @@ impl Wallet { /// Delay in seconds to sync [`WalletData`] (60 seconds as average block time). const SYNC_DELAY: Duration = Duration::from_millis(60 * 1000); - /// Delay in seconds for sync thread to wait before start of new attempt. const ATTEMPT_DELAY: Duration = Duration::from_millis(3 * 1000); - /// Number of attempts to sync [`WalletData`] before setting an error. const SYNC_ATTEMPTS: u8 = 10; @@ -1328,10 +1338,9 @@ async fn handle_task(w: &Wallet, t: WalletTask) { w.invoice_creating.store(false, Ordering::Relaxed); }, WalletTask::Finalize(s, id) => { - let slate = if let Some(s) = s { - s - } else { - &w.get_tx(*id).unwrap() + let slate = match s { + None => &w.get_tx(*id).unwrap(), + Some(s) => s }; match w.finalize(slate) { Ok(s) => { @@ -1350,10 +1359,9 @@ async fn handle_task(w: &Wallet, t: WalletTask) { } } WalletTask::Post(s, id) => { - let slate = if let Some(s) = s { - s - } else { - &w.get_tx(*id).unwrap() + let slate = match s { + None => &w.get_tx(*id).unwrap(), + Some(s) => s }; match w.post(slate) { Ok(_) => { @@ -1501,81 +1509,62 @@ fn update_txs(wallet: &Wallet, instance: WalletInstance, info: WalletInfo) } }).collect::>(); - // Initialize tx confirmation height storage. let tx_height_store = TxHeightStore::new(wallet.get_config().get_extra_db_path()); - let data = wallet.get_data().unwrap(); let data_txs = data.txs.unwrap_or(vec![]); let mut new_txs: Vec = vec![]; for tx in &account_txs { - // Setup transaction amount. - let amount = if tx.amount_debited > tx.amount_credited { - tx.amount_debited - tx.amount_credited - } else { - tx.amount_credited - tx.amount_debited - }; - - // Setup confirmation height, action and state. let mut height: Option = None; + let mut broadcasting_height: Option = None; let mut action: Option = None; let mut action_error: Option = None; - let mut state: Option = None; for t in &data_txs { if t.data.id == tx.id { - height = t.height; action = t.action.clone(); action_error = t.action_error.clone(); - state = Some(t.state.clone()); + height = t.height; + broadcasting_height = t.broadcasting_height; break; } } - if tx.kernel_lookup_min_height.is_some() && - tx.kernel_excess.is_some() && tx.confirmed { - if height.is_none() { - height = wallet.tx_height(tx, &tx_height_store).unwrap_or(None); - } - } - - // Setup transaction state for unconfirmed tx or for initial update. + let mut new = WalletTransaction::new(tx.clone(), + wallet, + height, + broadcasting_height, + action, + action_error); + // Update Slate state for unconfirmed. let unconfirmed = !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent || tx.tx_type == TxLogEntryType::TxReceived); - if unconfirmed || state.is_none() { - 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) { - state = Some(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) { - state = Some(slate.state); - } else { - // Transaction just was created. - state = Some(match tx.tx_type { - TxLogEntryType::TxReceived => SlateState::Invoice1, - _ => SlateState::Standard1 - }); - } - } + if unconfirmed { + new.update_slate_state(wallet); } - // Add transaction to the list. - new_txs.push(WalletTransaction { - data: tx.clone(), - state: state.unwrap(), - amount, - height, - action, - action_error, - }); + + // Setup initial tx heights. + if height.is_none() && tx.confirmed { + height = if let Some(height) = tx_height_store.read_tx_height(tx.id) { + Some(height) + } else { + tx_height_store.delete_broadcasting_height(tx.id); + let h = wallet.tx_height(&new)?; + if let Some(h) = h { + tx_height_store.write_tx_height(tx.id, h); + } + h + }; + new.height = height; + } else if broadcasting_height.is_none() && new.broadcasting() { + broadcasting_height = if let Some(h) = tx_height_store.read_broadcasting_height(tx.id) { + Some(h) + } else { + let h = data.info.last_confirmed_height; + tx_height_store.write_broadcasting_height(tx.id, h); + Some(h) + }; + new.broadcasting_height = Some(broadcasting_height.unwrap_or(0)); + } + + new_txs.push(new); } // Update wallet txs. let mut w_data = wallet.data.write();