From f04e2e2e6ffaade73d543c7e6e7b848db50eed98 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 4 Apr 2023 19:31:19 +0200 Subject: [PATCH] Add an extra state for invoices where our tx has been broadcast successfully, but it is not in our history yet. (follow-up 159646fe54720dc76aa77413e94f64d868d6666d) --- electrum/gui/qml/qeinvoice.py | 7 ++++--- electrum/gui/qml/qewallet.py | 8 +++++--- electrum/gui/qt/send_tab.py | 7 ++++--- electrum/gui/qt/util.py | 4 +++- electrum/invoices.py | 14 +++++++++++--- electrum/wallet.py | 10 +++++++--- 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index 38cdc7e90..a17ed9d05 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -10,7 +10,7 @@ from electrum import lnutil from electrum.i18n import _ from electrum.invoices import Invoice from electrum.invoices import (PR_UNPAID, PR_EXPIRED, PR_UNKNOWN, PR_PAID, PR_INFLIGHT, - PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, LN_EXPIRY_NEVER) + PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST, LN_EXPIRY_NEVER) from electrum.lnaddr import LnInvoiceException from electrum.logging import get_logger from electrum.transaction import PartialTxOutput @@ -95,7 +95,7 @@ class QEInvoice(QObject, QtEventListener): @event_listener def on_event_invoice_status(self, wallet, key, status): - if wallet == self._wallet.wallet and key == self.key: + if self._wallet and wallet == self._wallet.wallet and key == self.key: self.update_userinfo() self.determine_can_pay() self.statusChanged.emit() @@ -330,7 +330,8 @@ 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_BROADCASTING: _('Payment in progress...') + ' (' + _('broadcasting') + ')', + PR_BROADCAST: _('Payment in progress...') + ' (' + _('broadcast successfully') + ')', 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 2fa19e674..72e45b437 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -9,7 +9,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, QM from electrum import bitcoin from electrum.i18n import _ -from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID +from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID, PR_BROADCASTING, PR_BROADCAST from electrum.logging import get_logger from electrum.network import TxBroadcastError, BestEffortRequestFailed from electrum.transaction import PartialTxOutput, PartialTransaction @@ -549,21 +549,23 @@ class QEWallet(AuthMixin, QObject, QtEventListener): assert tx.is_complete() def broadcast_thread(): - self.wallet.set_broadcasting(tx, True) + self.wallet.set_broadcasting(tx, PR_BROADCASTING) try: self._logger.info('running broadcast in thread') self.wallet.network.run_from_another_thread(self.wallet.network.broadcast_transaction(tx)) except TxBroadcastError as e: self._logger.error(repr(e)) self.broadcastFailed.emit(tx.txid(), '', e.get_message_for_gui()) + self.wallet.set_broadcasting(tx, None) except BestEffortRequestFailed as e: self._logger.error(repr(e)) self.broadcastFailed.emit(tx.txid(), '', repr(e)) + self.wallet.set_broadcasting(tx, None) else: self._logger.info('broadcast success') self.broadcastSucceeded.emit(tx.txid()) self.historyModel.requestRefresh.emit() # via qt thread - self.wallet.set_broadcasting(tx, False) + self.wallet.set_broadcasting(tx, PR_BROADCAST) 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 7e3c23966..3412a2f1c 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -18,7 +18,7 @@ from electrum.i18n import _ from electrum.util import (get_asyncio_loop, FailedToParsePaymentIdentifier, InvalidBitcoinURI, maybe_extract_lightning_payment_identifier, NotEnoughFunds, NoDynamicFeeEstimates, InvoiceError, parse_max_spend) -from electrum.invoices import PR_PAID, Invoice +from electrum.invoices import PR_PAID, Invoice, PR_BROADCASTING, PR_BROADCAST from electrum.transaction import Transaction, PartialTxInput, PartialTransaction, PartialTxOutput from electrum.network import TxBroadcastError, BestEffortRequestFailed from electrum.logging import Logger @@ -761,19 +761,20 @@ 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) + self.wallet.set_broadcasting(tx, PR_BROADCASTING) def broadcast_done(result): - self.wallet.set_broadcasting(tx, False) # GUI thread if result: success, msg = result if success: parent.show_message(_('Payment sent.') + '\n' + msg) self.invoice_list.update() + self.wallet.set_broadcasting(tx, PR_BROADCAST) else: msg = msg or '' parent.show_error(msg) + self.wallet.set_broadcasting(tx, None) WaitingDialog(self, _('Broadcasting transaction...'), broadcast_thread, broadcast_done, self.window.on_error) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 5dd43e195..2e400e158 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, from electrum.i18n import _, languages from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path from electrum.util import EventListener, event_listener -from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED +from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST from electrum.logging import Logger from electrum.qrreader import MissingQrDetectionLib @@ -60,6 +60,8 @@ pr_icons = { PR_FAILED:"warning.png", PR_ROUTING:"unconfirmed.png", PR_UNCONFIRMED:"unconfirmed.png", + PR_BROADCASTING:"unconfirmed.png", + PR_BROADCAST:"unconfirmed.png", } diff --git a/electrum/invoices.py b/electrum/invoices.py index 47ea6aa0a..c90ad75a6 100644 --- a/electrum/invoices.py +++ b/electrum/invoices.py @@ -29,7 +29,8 @@ PR_INFLIGHT = 4 # only for LN. payment attempt in progress PR_FAILED = 5 # only for LN. we attempted to pay it, but all attempts failed PR_ROUTING = 6 # only for LN. *unused* atm. PR_UNCONFIRMED = 7 # only onchain. invoice is satisfied but tx is not mined yet. - +PR_BROADCASTING = 8 # onchain, tx is being broadcast +PR_BROADCAST = 9 # onchain, tx was broadcast, is not yet in our history pr_color = { PR_UNPAID: (.7, .7, .7, 1), @@ -38,7 +39,9 @@ pr_color = { PR_EXPIRED: (.9, .2, .2, 1), PR_INFLIGHT: (.9, .6, .3, 1), PR_FAILED: (.9, .2, .2, 1), - PR_ROUTING: (.9, .6, .3, 1), + PR_ROUTING: (.9, .6, .3, 1), + PR_BROADCASTING: (.9, .6, .3, 1), + PR_BROADCAST: (.9, .6, .3, 1), PR_UNCONFIRMED: (.9, .6, .3, 1), } @@ -48,6 +51,8 @@ pr_tooltips = { PR_UNKNOWN:_('Unknown'), PR_EXPIRED:_('Expired'), PR_INFLIGHT:_('In progress'), + PR_BROADCASTING:_('Broadcasting'), + PR_BROADCAST:_('Broadcast successfully'), PR_FAILED:_('Failed'), PR_ROUTING: _('Computing route...'), PR_UNCONFIRMED: _('Unconfirmed'), @@ -243,11 +248,14 @@ class BaseInvoice(StoredObject): class Invoice(BaseInvoice): lightning_invoice = attr.ib(type=str, kw_only=True) # type: Optional[str] __lnaddr = None - _is_broadcasting = False + _broadcasting_status = None # can be None or PR_BROADCASTING or PR_BROADCAST def is_lightning(self): return self.lightning_invoice is not None + def get_broadcasting_status(self): + return self._broadcasting_status + def get_address(self) -> Optional[str]: address = None if self.outputs: diff --git a/electrum/wallet.py b/electrum/wallet.py index 6723898e1..d53e2dddc 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -1157,6 +1157,9 @@ class Abstract_Wallet(ABC, Logger, EventListener): self._invoices_from_txid_map[txid].add(invoice_key) for txout in invoice.get_outputs(): self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key) + # update invoice status + status = self.get_invoice_status(invoice) + util.trigger_callback('invoice_status', self, invoice_key, status) def _is_onchain_invoice_paid(self, invoice: BaseInvoice) -> Tuple[bool, Optional[int], Sequence[str]]: """Returns whether on-chain invoice/request is satisfied, num confs required txs have, @@ -2405,8 +2408,6 @@ 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) @@ -2414,6 +2415,9 @@ class Abstract_Wallet(ABC, Logger, EventListener): return self.check_expired_status(invoice, status) paid, conf = self.is_onchain_invoice_paid(invoice) if not paid: + if isinstance(invoice, Invoice): + if status:=invoice.get_broadcasting_status(): + return status status = PR_UNPAID elif conf == 0: status = PR_UNCONFIRMED @@ -2532,7 +2536,7 @@ class Abstract_Wallet(ABC, Logger, EventListener): invoice = self._invoices.get(key) if not invoice: continue - invoice._is_broadcasting = b + invoice._broadcasting_status = b status = self.get_invoice_status(invoice) util.trigger_callback('invoice_status', self, key, status)