Browse Source

Add an extra state for invoices where our tx has been broadcast

successfully, but it is not in our history yet.

(follow-up 159646fe54)
master
ThomasV 3 years ago
parent
commit
f04e2e2e6f
  1. 7
      electrum/gui/qml/qeinvoice.py
  2. 8
      electrum/gui/qml/qewallet.py
  3. 7
      electrum/gui/qt/send_tab.py
  4. 4
      electrum/gui/qt/util.py
  5. 14
      electrum/invoices.py
  6. 10
      electrum/wallet.py

7
electrum/gui/qml/qeinvoice.py

@ -10,7 +10,7 @@ from electrum import lnutil
from electrum.i18n import _ from electrum.i18n import _
from electrum.invoices import Invoice from electrum.invoices import Invoice
from electrum.invoices import (PR_UNPAID, PR_EXPIRED, PR_UNKNOWN, PR_PAID, PR_INFLIGHT, 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.lnaddr import LnInvoiceException
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.transaction import PartialTxOutput from electrum.transaction import PartialTxOutput
@ -95,7 +95,7 @@ class QEInvoice(QObject, QtEventListener):
@event_listener @event_listener
def on_event_invoice_status(self, wallet, key, status): 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.update_userinfo()
self.determine_can_pay() self.determine_can_pay()
self.statusChanged.emit() self.statusChanged.emit()
@ -330,7 +330,8 @@ class QEInvoice(QObject, QtEventListener):
self.userinfo = { self.userinfo = {
PR_EXPIRED: _('This invoice has expired'), PR_EXPIRED: _('This invoice has expired'),
PR_PAID: _('This invoice was already paid'), 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_UNCONFIRMED: _('Payment in progress...') + ' (' + _('waiting for confirmation') + ')',
PR_UNKNOWN: _('Invoice has unknown status'), PR_UNKNOWN: _('Invoice has unknown status'),
}[self.status] }[self.status]

8
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 import bitcoin
from electrum.i18n import _ 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.logging import get_logger
from electrum.network import TxBroadcastError, BestEffortRequestFailed from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.transaction import PartialTxOutput, PartialTransaction from electrum.transaction import PartialTxOutput, PartialTransaction
@ -549,21 +549,23 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
assert tx.is_complete() assert tx.is_complete()
def broadcast_thread(): def broadcast_thread():
self.wallet.set_broadcasting(tx, True) self.wallet.set_broadcasting(tx, PR_BROADCASTING)
try: try:
self._logger.info('running broadcast in thread') self._logger.info('running broadcast in thread')
self.wallet.network.run_from_another_thread(self.wallet.network.broadcast_transaction(tx)) self.wallet.network.run_from_another_thread(self.wallet.network.broadcast_transaction(tx))
except TxBroadcastError as e: except TxBroadcastError as e:
self._logger.error(repr(e)) self._logger.error(repr(e))
self.broadcastFailed.emit(tx.txid(), '', e.get_message_for_gui()) self.broadcastFailed.emit(tx.txid(), '', e.get_message_for_gui())
self.wallet.set_broadcasting(tx, None)
except BestEffortRequestFailed as e: except BestEffortRequestFailed as e:
self._logger.error(repr(e)) self._logger.error(repr(e))
self.broadcastFailed.emit(tx.txid(), '', repr(e)) self.broadcastFailed.emit(tx.txid(), '', repr(e))
self.wallet.set_broadcasting(tx, None)
else: else:
self._logger.info('broadcast success') self._logger.info('broadcast success')
self.broadcastSucceeded.emit(tx.txid()) self.broadcastSucceeded.emit(tx.txid())
self.historyModel.requestRefresh.emit() # via qt thread 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() threading.Thread(target=broadcast_thread, daemon=True).start()

7
electrum/gui/qt/send_tab.py

@ -18,7 +18,7 @@ from electrum.i18n import _
from electrum.util import (get_asyncio_loop, FailedToParsePaymentIdentifier, from electrum.util import (get_asyncio_loop, FailedToParsePaymentIdentifier,
InvalidBitcoinURI, maybe_extract_lightning_payment_identifier, NotEnoughFunds, InvalidBitcoinURI, maybe_extract_lightning_payment_identifier, NotEnoughFunds,
NoDynamicFeeEstimates, InvoiceError, parse_max_spend) 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.transaction import Transaction, PartialTxInput, PartialTransaction, PartialTxOutput
from electrum.network import TxBroadcastError, BestEffortRequestFailed from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.logging import Logger from electrum.logging import Logger
@ -761,19 +761,20 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
# Capture current TL window; override might be removed on return # Capture current TL window; override might be removed on return
parent = self.window.top_level_window(lambda win: isinstance(win, MessageBoxMixin)) 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): def broadcast_done(result):
self.wallet.set_broadcasting(tx, False)
# GUI thread # GUI thread
if result: if result:
success, msg = result success, msg = result
if success: if success:
parent.show_message(_('Payment sent.') + '\n' + msg) parent.show_message(_('Payment sent.') + '\n' + msg)
self.invoice_list.update() self.invoice_list.update()
self.wallet.set_broadcasting(tx, PR_BROADCAST)
else: else:
msg = msg or '' msg = msg or ''
parent.show_error(msg) parent.show_error(msg)
self.wallet.set_broadcasting(tx, None)
WaitingDialog(self, _('Broadcasting transaction...'), WaitingDialog(self, _('Broadcasting transaction...'),
broadcast_thread, broadcast_done, self.window.on_error) broadcast_thread, broadcast_done, self.window.on_error)

4
electrum/gui/qt/util.py

@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout,
from electrum.i18n import _, languages from electrum.i18n import _, languages
from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path
from electrum.util import EventListener, event_listener 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.logging import Logger
from electrum.qrreader import MissingQrDetectionLib from electrum.qrreader import MissingQrDetectionLib
@ -60,6 +60,8 @@ pr_icons = {
PR_FAILED:"warning.png", PR_FAILED:"warning.png",
PR_ROUTING:"unconfirmed.png", PR_ROUTING:"unconfirmed.png",
PR_UNCONFIRMED:"unconfirmed.png", PR_UNCONFIRMED:"unconfirmed.png",
PR_BROADCASTING:"unconfirmed.png",
PR_BROADCAST:"unconfirmed.png",
} }

14
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_FAILED = 5 # only for LN. we attempted to pay it, but all attempts failed
PR_ROUTING = 6 # only for LN. *unused* atm. PR_ROUTING = 6 # only for LN. *unused* atm.
PR_UNCONFIRMED = 7 # only onchain. invoice is satisfied but tx is not mined yet. 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_color = {
PR_UNPAID: (.7, .7, .7, 1), PR_UNPAID: (.7, .7, .7, 1),
@ -38,7 +39,9 @@ pr_color = {
PR_EXPIRED: (.9, .2, .2, 1), PR_EXPIRED: (.9, .2, .2, 1),
PR_INFLIGHT: (.9, .6, .3, 1), PR_INFLIGHT: (.9, .6, .3, 1),
PR_FAILED: (.9, .2, .2, 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), PR_UNCONFIRMED: (.9, .6, .3, 1),
} }
@ -48,6 +51,8 @@ pr_tooltips = {
PR_UNKNOWN:_('Unknown'), PR_UNKNOWN:_('Unknown'),
PR_EXPIRED:_('Expired'), PR_EXPIRED:_('Expired'),
PR_INFLIGHT:_('In progress'), PR_INFLIGHT:_('In progress'),
PR_BROADCASTING:_('Broadcasting'),
PR_BROADCAST:_('Broadcast successfully'),
PR_FAILED:_('Failed'), PR_FAILED:_('Failed'),
PR_ROUTING: _('Computing route...'), PR_ROUTING: _('Computing route...'),
PR_UNCONFIRMED: _('Unconfirmed'), PR_UNCONFIRMED: _('Unconfirmed'),
@ -243,11 +248,14 @@ class BaseInvoice(StoredObject):
class Invoice(BaseInvoice): class Invoice(BaseInvoice):
lightning_invoice = attr.ib(type=str, kw_only=True) # type: Optional[str] lightning_invoice = attr.ib(type=str, kw_only=True) # type: Optional[str]
__lnaddr = None __lnaddr = None
_is_broadcasting = False _broadcasting_status = None # can be None or PR_BROADCASTING or PR_BROADCAST
def is_lightning(self): def is_lightning(self):
return self.lightning_invoice is not None return self.lightning_invoice is not None
def get_broadcasting_status(self):
return self._broadcasting_status
def get_address(self) -> Optional[str]: def get_address(self) -> Optional[str]:
address = None address = None
if self.outputs: if self.outputs:

10
electrum/wallet.py

@ -1157,6 +1157,9 @@ class Abstract_Wallet(ABC, Logger, EventListener):
self._invoices_from_txid_map[txid].add(invoice_key) self._invoices_from_txid_map[txid].add(invoice_key)
for txout in invoice.get_outputs(): for txout in invoice.get_outputs():
self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key) 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]]: 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, """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): def get_invoice_status(self, invoice: BaseInvoice):
"""Returns status of (incoming) request or (outgoing) invoice.""" """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 # lightning invoices can be paid onchain
if invoice.is_lightning() and self.lnworker: if invoice.is_lightning() and self.lnworker:
status = self.lnworker.get_invoice_status(invoice) status = self.lnworker.get_invoice_status(invoice)
@ -2414,6 +2415,9 @@ class Abstract_Wallet(ABC, Logger, EventListener):
return self.check_expired_status(invoice, status) return self.check_expired_status(invoice, status)
paid, conf = self.is_onchain_invoice_paid(invoice) paid, conf = self.is_onchain_invoice_paid(invoice)
if not paid: if not paid:
if isinstance(invoice, Invoice):
if status:=invoice.get_broadcasting_status():
return status
status = PR_UNPAID status = PR_UNPAID
elif conf == 0: elif conf == 0:
status = PR_UNCONFIRMED status = PR_UNCONFIRMED
@ -2532,7 +2536,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
invoice = self._invoices.get(key) invoice = self._invoices.get(key)
if not invoice: if not invoice:
continue continue
invoice._is_broadcasting = b invoice._broadcasting_status = b
status = self.get_invoice_status(invoice) status = self.get_invoice_status(invoice)
util.trigger_callback('invoice_status', self, key, status) util.trigger_callback('invoice_status', self, key, status)

Loading…
Cancel
Save