From 159646fe54720dc76aa77413e94f64d868d6666d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 4 Apr 2023 16:55:06 +0200 Subject: [PATCH] Set status of onchain invoices to PR_INFLIGHT while tx is being broadcast --- electrum/gui/qml/qeinvoice.py | 6 ++--- electrum/gui/qml/qewallet.py | 2 ++ electrum/gui/qt/send_tab.py | 3 +++ electrum/invoices.py | 1 + electrum/wallet.py | 44 ++++++++++++++++++++++++++--------- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index af6a7d524..38cdc7e90 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -96,10 +96,9 @@ class QEInvoice(QObject, QtEventListener): @event_listener def on_event_invoice_status(self, wallet, key, status): if wallet == self._wallet.wallet and key == self.key: + self.update_userinfo() + self.determine_can_pay() self.statusChanged.emit() - if status in [PR_INFLIGHT, PR_ROUTING]: - self.determine_can_pay() - self.userinfo = _('In progress...') walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) @@ -331,6 +330,7 @@ class QEInvoice(QObject, QtEventListener): self.userinfo = { PR_EXPIRED: _('This invoice has expired'), PR_PAID: _('This invoice was already paid'), + PR_INFLIGHT: _('Payment in progress...') + ' (' + _('broadcasting') + ')', PR_UNCONFIRMED: _('Payment in progress...') + ' (' + _('waiting for confirmation') + ')', PR_UNKNOWN: _('Invoice has unknown status'), }[self.status] diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index bc2701bc5..2fa19e674 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -549,6 +549,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener): assert tx.is_complete() def broadcast_thread(): + self.wallet.set_broadcasting(tx, True) try: self._logger.info('running broadcast in thread') self.wallet.network.run_from_another_thread(self.wallet.network.broadcast_transaction(tx)) @@ -562,6 +563,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener): self._logger.info('broadcast success') self.broadcastSucceeded.emit(tx.txid()) self.historyModel.requestRefresh.emit() # via qt thread + self.wallet.set_broadcasting(tx, False) threading.Thread(target=broadcast_thread, daemon=True).start() diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index 825c9aa69..7e3c23966 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -761,7 +761,10 @@ class SendTab(QWidget, MessageBoxMixin, Logger): # Capture current TL window; override might be removed on return parent = self.window.top_level_window(lambda win: isinstance(win, MessageBoxMixin)) + self.wallet.set_broadcasting(tx, True) + def broadcast_done(result): + self.wallet.set_broadcasting(tx, False) # GUI thread if result: success, msg = result diff --git a/electrum/invoices.py b/electrum/invoices.py index 1bd10292b..47ea6aa0a 100644 --- a/electrum/invoices.py +++ b/electrum/invoices.py @@ -243,6 +243,7 @@ class BaseInvoice(StoredObject): class Invoice(BaseInvoice): lightning_invoice = attr.ib(type=str, kw_only=True) # type: Optional[str] __lnaddr = None + _is_broadcasting = False def is_lightning(self): return self.lightning_invoice is not None diff --git a/electrum/wallet.py b/electrum/wallet.py index d246c4caa..6723898e1 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -75,7 +75,7 @@ from .plugin import run_hook from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE) from .invoices import BaseInvoice, Invoice, Request -from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED +from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_INFLIGHT from .contacts import Contacts from .interface import NetworkException from .mnemonic import Mnemonic @@ -2405,6 +2405,8 @@ class Abstract_Wallet(ABC, Logger, EventListener): def get_invoice_status(self, invoice: BaseInvoice): """Returns status of (incoming) request or (outgoing) invoice.""" + if isinstance(invoice, Invoice) and invoice._is_broadcasting: + return PR_INFLIGHT # lightning invoices can be paid onchain if invoice.is_lightning() and self.lnworker: status = self.lnworker.get_invoice_status(invoice) @@ -2496,6 +2498,18 @@ class Abstract_Wallet(ABC, Logger, EventListener): d['bip70'] = x.bip70 return d + def get_invoices_and_requests_touched_by_tx(self, tx): + request_keys = set() + invoice_keys = set() + with self.lock, self.transaction_lock: + for txo in tx.outputs(): + addr = txo.address + if request:=self.get_request_by_addr(addr): + request_keys.add(request.get_id()) + for invoice_key in self._invoices_from_scriptpubkey_map.get(txo.scriptpubkey, set()): + invoice_keys.add(invoice_key) + return request_keys, invoice_keys + def _update_invoices_and_reqs_touched_by_tx(self, tx_hash: str) -> None: # FIXME in some cases if tx2 replaces unconfirmed tx1 in the mempool, we are not called. # For a given receive request, if tx1 touches it but tx2 does not, then @@ -2503,16 +2517,24 @@ class Abstract_Wallet(ABC, Logger, EventListener): tx = self.db.get_transaction(tx_hash) if tx is None: return - relevant_invoice_keys = set() - with self.lock, self.transaction_lock: - for txo in tx.outputs(): - addr = txo.address - if request:=self.get_request_by_addr(addr): - status = self.get_invoice_status(request) - util.trigger_callback('request_status', self, request.get_id(), status) - for invoice_key in self._invoices_from_scriptpubkey_map.get(txo.scriptpubkey, set()): - relevant_invoice_keys.add(invoice_key) - self._update_onchain_invoice_paid_detection(relevant_invoice_keys) + request_keys, invoice_keys = self.get_invoices_and_requests_touched_by_tx(tx) + for key in request_keys: + request = self.get_request(key) + if not request: + continue + status = self.get_invoice_status(request) + util.trigger_callback('request_status', self, request.get_id(), status) + self._update_onchain_invoice_paid_detection(invoice_keys) + + def set_broadcasting(self, tx: Transaction, b: bool): + request_keys, invoice_keys = self.get_invoices_and_requests_touched_by_tx(tx) + for key in invoice_keys: + invoice = self._invoices.get(key) + if not invoice: + continue + invoice._is_broadcasting = b + status = self.get_invoice_status(invoice) + util.trigger_callback('invoice_status', self, key, status) def get_bolt11_invoice(self, req: Request) -> str: if not self.lnworker: