diff --git a/electrum/gui/qml/qeaddresslistmodel.py b/electrum/gui/qml/qeaddresslistmodel.py index fa7a546a6299..4ef9595e9aa9 100644 --- a/electrum/gui/qml/qeaddresslistmodel.py +++ b/electrum/gui/qml/qeaddresslistmodel.py @@ -123,9 +123,9 @@ def __init__(self, wallet: 'Abstract_Wallet', parent=None): self._filterModel = None self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) - QEConfig.instance.freezeReusedAddressUtxosChanged.connect(lambda: self.setDirty()) + QEConfig.instance.freezeReusedAddressUtxosChanged.connect(self.setDirty) self._dirty = True self.initModel() diff --git a/electrum/gui/qml/qechanneldetails.py b/electrum/gui/qml/qechanneldetails.py index be9c2d525604..34ca52998373 100644 --- a/electrum/gui/qml/qechanneldetails.py +++ b/electrum/gui/qml/qechanneldetails.py @@ -50,7 +50,7 @@ def __init__(self, parent=None): self._is_closing = False self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) @event_listener def on_event_channel(self, wallet: 'Abstract_Wallet', channel: 'AbstractChannel'): diff --git a/electrum/gui/qml/qechannellistmodel.py b/electrum/gui/qml/qechannellistmodel.py index 0079cdcd5b4f..f028bf9169ab 100644 --- a/electrum/gui/qml/qechannellistmodel.py +++ b/electrum/gui/qml/qechannellistmodel.py @@ -46,7 +46,7 @@ def __init__(self, wallet: 'Abstract_Wallet', parent=None): # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) @qt_event_listener def on_event_channel(self, wallet, channel): diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index 28a6640bd9d6..50a037a109c5 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -152,6 +152,19 @@ class QEDaemon(AuthMixin, QObject): walletDeleteError = pyqtSignal([str, str], arguments=['code', 'message']) walletRenameError = pyqtSignal([str], arguments=['message']) + __qewallet_instances = [] + + # this factory method should be used to instantiate QEWallet + # so we have only one QEWallet for each electrum.wallet + @classmethod + def getQEWalletInstanceFor(cls, wallet): + for i in cls.__qewallet_instances: + if i.wallet == wallet: + return i + i = QEWallet(wallet, QEDaemon.instance) + cls.__qewallet_instances.append(i) + return i + def __init__(self, daemon: 'Daemon', plugins: 'Plugins', parent=None): super().__init__(parent) if QEDaemon.instance: @@ -205,7 +218,7 @@ def loadWallet(self, path=None, password=None): wallet_already_open = self.daemon.get_wallet(self._path) if wallet_already_open is not None: - password = QEWallet.getInstanceFor(wallet_already_open).password + password = QEDaemon.getQEWalletInstanceFor(wallet_already_open).password def load_wallet_task(): success = False @@ -267,7 +280,7 @@ def _on_backend_wallet_loaded(self, password=None): self._logger.debug('_on_backend_wallet_loaded') wallet = self.daemon.get_wallet(self._path) assert wallet is not None - self._current_wallet = QEWallet.getInstanceFor(wallet) + self._current_wallet = QEDaemon.getQEWalletInstanceFor(wallet) self.availableWallets.updateWallet(self._path) wallet.unlock(password or None) # not conditional on wallet.requires_unlock in qml, as # the auth wrapper doesn't pass the entered password, but instead we rely on the password in memory @@ -301,9 +314,7 @@ def checkThenDeleteWallet(self, wallet, confirm_requests=False, confirm_balance= def delete_wallet(self, wallet): path = standardize_path(wallet.wallet.storage.get_path()) self._logger.debug('deleting wallet with path %s' % path) - self._current_wallet = None - # TODO walletLoaded signal is confusing - self.walletLoaded.emit(None, None) + self.unloadWallet(wallet) if not self.daemon.delete_wallet(path): self.walletDeleteError.emit('error', _('Problem deleting wallet')) @@ -349,13 +360,24 @@ def renameWallet(self, new_name: str): new_path = standardize_path(os.path.join(wallet_dir, new_name)) if old_path == new_path: return - self._current_wallet = None - self.daemon.stop_wallet(old_path) + self.unloadWallet(wallet) try: self.daemon.rename_wallet_file(old_path, new_path) except Exception as e: self.walletRenameError.emit(_('Error renaming wallet:\n') + str(e)) - self.walletLoaded.emit(None, None) + + # @pyqtSlot() + # TODO: make slot once the GUI/backend properly handles not ending up in a no wallet loaded scenario + def unloadWallet(self, wallet: QEWallet): + if wallet: + wallet_path = standardize_path(wallet.wallet.storage.get_path()) + self.daemon.stop_wallet(wallet_path) + QEDaemon.__qewallet_instances.remove(wallet) + if wallet == self._current_wallet: + self._current_wallet = None + # TODO walletLoaded signal is confusing + self.walletLoaded.emit(None, None) + wallet.deleteLater() @pyqtProperty(bool, notify=loadingChanged) def loading(self): diff --git a/electrum/gui/qml/qefx.py b/electrum/gui/qml/qefx.py index 8225a3a62fed..1ed71d5941c7 100644 --- a/electrum/gui/qml/qefx.py +++ b/electrum/gui/qml/qefx.py @@ -24,7 +24,7 @@ def __init__(self, fxthread: FxThread, config: SimpleConfig, parent=None): self.fx = fxthread self.config = config self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) def on_destroy(self): self.unregister_callbacks() diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index 7069663f3441..3cc262b478eb 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -79,7 +79,7 @@ def __init__(self, parent=None): self._updating_max = False self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) def on_destroy(self): self.unregister_callbacks() diff --git a/electrum/gui/qml/qeinvoicelistmodel.py b/electrum/gui/qml/qeinvoicelistmodel.py index 551afb19fa84..0e59c56104b1 100644 --- a/electrum/gui/qml/qeinvoicelistmodel.py +++ b/electrum/gui/qml/qeinvoicelistmodel.py @@ -182,7 +182,7 @@ class QEInvoiceListModel(QEAbstractInvoiceListModel, QtEventListener): def __init__(self, wallet, parent=None): super().__init__(wallet, parent) self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) _logger = get_logger(__name__) @@ -217,7 +217,7 @@ class QERequestListModel(QEAbstractInvoiceListModel, QtEventListener): def __init__(self, wallet, parent=None): super().__init__(wallet, parent) self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) _logger = get_logger(__name__) diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index 5130bf4f9cc2..bb4334634504 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -59,7 +59,7 @@ def __init__(self, network: 'Network', parent=None): self._height = network.get_local_height() # init here, update event can take a while self._server_height = network.get_server_height() # init here, update event can take a while self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) QEConfig.instance.useGossipChanged.connect(self.on_gossip_setting_changed) diff --git a/electrum/gui/qml/qeqrscanner.py b/electrum/gui/qml/qeqrscanner.py index be93bbf1f288..d582b35f6f3d 100644 --- a/electrum/gui/qml/qeqrscanner.py +++ b/electrum/gui/qml/qeqrscanner.py @@ -1,6 +1,6 @@ import os -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Qt +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Qt, QMetaObject from PyQt6.QtGui import QGuiApplication from electrum.gui.qml.qetypes import QEBytes @@ -25,13 +25,11 @@ class QEQRScanner(QObject): foundText = pyqtSignal(str) foundBinary = pyqtSignal(QEBytes) - finished = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._hint = _("Scan a QR code.") - self.finished.connect(self._unbind, Qt.ConnectionType.QueuedConnection) self.destroyed.connect(lambda: self.on_destroy()) @@ -78,6 +76,11 @@ def on_qr_activity_result(self, requestCode, resultCode, intent): send_exception_to_crash_reporter(e) finally: self.finished.emit() + self.unbind() + + def unbind(self): + # submit unbind request queued + QMetaObject.invokeMethod(self, '_unbind', Qt.ConnectionType.QueuedConnection) @pyqtSlot() def _unbind(self): @@ -88,4 +91,3 @@ def _scan_qr_non_android(self): data = QGuiApplication.clipboard().text() self.foundText.emit(data) self.finished.emit() - return diff --git a/electrum/gui/qml/qeserverlistmodel.py b/electrum/gui/qml/qeserverlistmodel.py index bac6578c0b6e..266fda6b40df 100644 --- a/electrum/gui/qml/qeserverlistmodel.py +++ b/electrum/gui/qml/qeserverlistmodel.py @@ -27,7 +27,7 @@ def __init__(self, network, parent=None): self.network = network self.initModel() self.register_callbacks() - self.destroyed.connect(lambda: self.unregister_callbacks()) + self.destroyed.connect(self.unregister_callbacks) @qt_event_listener def on_event_network_updated(self): diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index 8bbf9483df0c..8677619ce478 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -4,7 +4,7 @@ from typing import Union, Optional, TYPE_CHECKING, Sequence from PyQt6.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtEnum, QAbstractListModel, Qt, - QModelIndex, QVariant) + QModelIndex, QVariant, QMetaObject) from PyQt6.QtGui import QColor from electrum.i18n import _ @@ -154,7 +154,6 @@ class State(IntEnum): error = pyqtSignal([str], arguments=['message']) undefinedNPub = pyqtSignal() offersUpdated = pyqtSignal() - requestTxUpdate = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) @@ -192,7 +191,6 @@ def __init__(self, parent=None): self._fwd_swap_updatetx_timer = QTimer(self) self._fwd_swap_updatetx_timer.setSingleShot(True) self._fwd_swap_updatetx_timer.timeout.connect(self.fwd_swap_updatetx) - self.requestTxUpdate.connect(self.tx_update_pushback_timer) self.offersUpdated.connect(self.on_offers_updated) self.transport_task: Optional[asyncio.Task] = None @@ -606,10 +604,14 @@ def swap_slider_moved(self): else: # update tx only if slider isn't moved for a while self.valid = False - # trigger tx_update_pushback_timer through signal, as this might be called from other thread - self.requestTxUpdate.emit() + self.requestTxUpdate() - def tx_update_pushback_timer(self): + def requestTxUpdate(self): + # trigger _tx_update_pushback_timer from qt thread, as this might be called from other thread + QMetaObject.invokeMethod(self, '_tx_update_pushback_timer', Qt.ConnectionType.QueuedConnection) + + @pyqtSlot() + def _tx_update_pushback_timer(self): self._fwd_swap_updatetx_timer.start(250) def check_valid(self, send_amount, receive_amount): diff --git a/electrum/gui/qml/qetransactionlistmodel.py b/electrum/gui/qml/qetransactionlistmodel.py index 293e60ee3670..aaf3d9b8e648 100644 --- a/electrum/gui/qml/qetransactionlistmodel.py +++ b/electrum/gui/qml/qetransactionlistmodel.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from typing import TYPE_CHECKING, Dict, Any -from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot +from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QMetaObject from PyQt6.QtCore import Qt, QAbstractListModel, QModelIndex from electrum.logging import get_logger @@ -27,8 +27,6 @@ class QETransactionListModel(QAbstractListModel, QtEventListener): _ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) - requestRefresh = pyqtSignal() - def __init__(self, wallet: 'Abstract_Wallet', parent=None, *, onchain_domain=None, include_lightning=True): super().__init__(parent) self.wallet = wallet @@ -38,8 +36,7 @@ def __init__(self, wallet: 'Abstract_Wallet', parent=None, *, onchain_domain=Non self.tx_history = [] self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) - self.requestRefresh.connect(lambda: self.initModel()) + self.destroyed.connect(self.on_destroy) self._dirty = True self.initModel() @@ -87,6 +84,10 @@ def on_event_labels_received(self, wallet, labels): if wallet == self.wallet: self.initModel(True) # TODO: be less dramatic + def requestRefresh(self): + # ensure execute on qt thread + QMetaObject.invokeMethod(self, 'initModel', Qt.ConnectionType.QueuedConnection) + def rowCount(self, index): return len(self.tx_history) diff --git a/electrum/gui/qml/qetxdetails.py b/electrum/gui/qml/qetxdetails.py index a5e4dabe03fd..ac4ceab76be0 100644 --- a/electrum/gui/qml/qetxdetails.py +++ b/electrum/gui/qml/qetxdetails.py @@ -30,8 +30,6 @@ class QETxDetails(QObject, QtEventListener): def __init__(self, parent=None): super().__init__(parent) - self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) self._wallet = None # type: Optional[QEWallet] self._txid = '' @@ -73,6 +71,9 @@ def __init__(self, parent=None): self._header_hash = '' self._short_id = "" + self.register_callbacks() + self.destroyed.connect(self.on_destroy) + def on_destroy(self): self.unregister_callbacks() @@ -407,6 +408,23 @@ def update_mined_status(self, tx_mined_info: TxMinedInfo): self._header_hash = tx_mined_info.header_hash self._short_id = tx_mined_info.short_id() or "" + def on_sign_success(self): + self._logger.debug('on_sign_success') + self.update() + + def on_sign_failed(self, failure): + self._logger.debug('on_sign_failed') + + def on_broadcast_success(self, *args): + self._logger.debug('on_broadcast_success') + self._can_broadcast = False + self.detailsChanged.emit() + + def on_broadcast_failed(self, *args): + self._logger.debug('on_broadcast_failed') + self._can_broadcast = True + self.detailsChanged.emit() + @pyqtSlot() def signAndBroadcast(self): self._sign(broadcast=True) @@ -416,69 +434,25 @@ def sign(self): self._sign(broadcast=False) def _sign(self, broadcast): - # TODO: connecting/disconnecting signal handlers here is hmm - try: - if broadcast: - self._wallet.broadcastSucceeded.disconnect(self.onBroadcastSucceeded) - self._wallet.broadcastFailed.disconnect(self.onBroadcastFailed) - except Exception: - pass - - if broadcast: - self._wallet.broadcastSucceeded.connect(self.onBroadcastSucceeded) - self._wallet.broadcastFailed.connect(self.onBroadcastFailed) - self._wallet.sign_and_broadcast(self._tx, on_success=self.on_signed_tx) - else: - self._wallet.sign(self._tx, on_success=self.on_signed_tx) - - # side-effect: signing updates self._tx - # we rely on this for broadcast + def on_sign_success(): + self._logger.debug('on_sign_success, broadcasting') + self.on_sign_success() # indicate sign success + if self._tx.is_complete(): + self._wallet.broadcast(self._tx, on_success=self.on_broadcast_success, on_failure=self.on_broadcast_failed) + else: + self._logger.warning('tx not complete, not broadcasting') - def on_signed_tx(self, tx: Transaction): - self._logger.debug('on_signed_tx') - self.update() + sign_success_cb = on_sign_success if broadcast else self.on_sign_success + self._wallet.sign(self._tx, on_success=sign_success_cb, on_failure=self.on_sign_failed) @pyqtSlot() def broadcast(self): assert self._tx.is_complete() - try: - self._wallet.broadcastFailed.disconnect(self.onBroadcastFailed) - except Exception: - pass - self._wallet.broadcastFailed.connect(self.onBroadcastFailed) - - self._can_broadcast = False - self.detailsChanged.emit() - - self._wallet.broadcast(self._tx) - - @pyqtSlot(str) - def onBroadcastSucceeded(self, txid): - if txid != self._txid: - return - - self._logger.debug('onBroadcastSucceeded') - try: - self._wallet.broadcastSucceeded.disconnect(self.onBroadcastSucceeded) - except Exception: - pass - self._can_broadcast = False self.detailsChanged.emit() - @pyqtSlot(str, str, str) - def onBroadcastFailed(self, txid, code, reason): - if txid != self._txid: - return - - try: - self._wallet.broadcastFailed.disconnect(self.onBroadcastFailed) - except Exception: - pass - - self._can_broadcast = True - self.detailsChanged.emit() + self._wallet.broadcast(self._tx, on_success=self.on_broadcast_success, on_failure=self.on_broadcast_failed) @pyqtSlot() @pyqtSlot(bool) diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py index d0457c80ecbc..aea626890048 100644 --- a/electrum/gui/qml/qetxfinalizer.py +++ b/electrum/gui/qml/qetxfinalizer.py @@ -572,7 +572,7 @@ def sign(self): self._wallet.sign(self._tx, on_success=partial(self.on_signed_tx, True), on_failure=self.on_sign_failed) - def on_signed_tx(self, save: bool, tx: Transaction): + def on_signed_tx(self, save: bool): self._logger.debug('on_signed_tx') saved = False if save and self._tx.txid(): @@ -580,7 +580,7 @@ def on_signed_tx(self, save: bool, tx: Transaction): saved = True else: self._logger.error('Could not save tx') - self.finished.emit(True, saved, tx.is_complete()) + self.finished.emit(True, saved, self._tx.is_complete()) def on_sign_failed(self, msg: str = None): self._logger.debug('on_sign_failed') @@ -612,7 +612,7 @@ def __init__(self, parent=None): self._txid = '' self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + self.destroyed.connect(self.on_destroy) def on_destroy(self): self.unregister_callbacks() @@ -1210,7 +1210,7 @@ def update(self): self._valid = True self.validChanged.emit() - self.on_signed_tx(False, tx) + self.on_signed_tx(False) @pyqtSlot() def send(self): diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index eeabf5261b43..172e96abd28b 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -3,7 +3,7 @@ import queue import threading import time -from typing import TYPE_CHECKING, Callable, Optional, Any, Tuple +from typing import TYPE_CHECKING, Callable, Optional, Tuple from functools import partial from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer @@ -37,19 +37,6 @@ class QEWallet(AuthMixin, QObject, QtEventListener): - __instances = [] - - # this factory method should be used to instantiate QEWallet - # so we have only one QEWallet for each electrum.wallet - @classmethod - def getInstanceFor(cls, wallet): - for i in cls.__instances: - if i.wallet == wallet: - return i - i = QEWallet(wallet) - cls.__instances.append(i) - return i - _logger = get_logger(__name__) # emitted when wallet wants to display a user notification @@ -125,16 +112,13 @@ def __init__(self, wallet: 'Abstract_Wallet', parent=None): self.sync_progress_timer.setInterval(2000) self.sync_progress_timer.timeout.connect(self.update_sync_progress) - # post-construction init in GUI thread - # QMetaObject.invokeMethod(self, 'qt_init', Qt.QueuedConnection) - # To avoid leaking references to "self" that prevent the # window from being GC-ed when closed, callbacks should be # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... - self.register_callbacks() - self.destroyed.connect(lambda: self.on_destroy()) + + self.destroyed.connect(self.on_destroy) self.synchronizing = not wallet.is_up_to_date() synchronizingChanged = pyqtSignal() @@ -256,9 +240,6 @@ def on_event_payment_failed(self, wallet, key, reason): self.paymentFailed.emit(key, reason) def on_destroy(self): - if self not in QEWallet.__instances: - return - QEWallet.__instances.remove(self) self.unregister_callbacks() def add_tx_notification(self, tx: Transaction): @@ -301,35 +282,35 @@ def update_sync_progress(self): @pyqtProperty(QETransactionListModel, notify=historyModelChanged) def historyModel(self): if self._historyModel is None: - self._historyModel = QETransactionListModel(self.wallet) + self._historyModel = QETransactionListModel(self.wallet, parent=self) return self._historyModel addressCoinModelChanged = pyqtSignal() @pyqtProperty(QEAddressCoinListModel, notify=addressCoinModelChanged) def addressCoinModel(self): if self._addressCoinModel is None: - self._addressCoinModel = QEAddressCoinListModel(self.wallet) + self._addressCoinModel = QEAddressCoinListModel(self.wallet, parent=self) return self._addressCoinModel requestModelChanged = pyqtSignal() @pyqtProperty(QERequestListModel, notify=requestModelChanged) def requestModel(self): if self._requestModel is None: - self._requestModel = QERequestListModel(self.wallet) + self._requestModel = QERequestListModel(self.wallet, parent=self) return self._requestModel invoiceModelChanged = pyqtSignal() @pyqtProperty(QEInvoiceListModel, notify=invoiceModelChanged) def invoiceModel(self): if self._invoiceModel is None: - self._invoiceModel = QEInvoiceListModel(self.wallet) + self._invoiceModel = QEInvoiceListModel(self.wallet, parent=self) return self._invoiceModel channelModelChanged = pyqtSignal() @pyqtProperty(QEChannelListModel, notify=channelModelChanged) def channelModel(self): if self._channelModel is None: - self._channelModel = QEChannelListModel(self.wallet) + self._channelModel = QEChannelListModel(self.wallet, parent=self) return self._channelModel nameChanged = pyqtSignal() @@ -527,21 +508,21 @@ def enableLightning(self): @auth_protect(message=_('Sign and send on-chain transaction?')) def sign_and_broadcast(self, tx, *, - on_success: Callable[[Transaction], None] = None, - on_failure: Callable[[Optional[Any]], None] = None) -> None: + on_success: Callable[[], None] = None, + on_failure: Callable[[str], None] = None) -> None: self.do_sign(tx, True, on_success, on_failure) @auth_protect(message=_('Sign on-chain transaction?')) def sign(self, tx, *, - on_success: Callable[[Transaction], None] = None, - on_failure: Callable[[Optional[Any]], None] = None) -> None: + on_success: Callable[[], None] = None, + on_failure: Callable[[str], None] = None) -> None: self.do_sign(tx, False, on_success, on_failure) - def do_sign(self, tx, broadcast, on_success: Callable[[Transaction], None] = None, on_failure: Callable[[Optional[Any]], None] = None): + def do_sign(self, tx, broadcast, on_success: Callable[[], None] = None, on_failure: Callable[[str], None] = None): # tc_sign_wrapper is only used by 2fa. don't pass on_failure handler, it is handled via otpFailed signal sign_hook = run_hook('tc_sign_wrapper', self.wallet, tx, - partial(self.on_sign_complete, broadcast, on_success), - partial(self.on_sign_failed, None)) + partial(self.on_tc_sign_complete, broadcast, on_success), + partial(self.on_tc_sign_failed, None)) try: # ignore_warnings=True, because UI checks and asks user confirmation itself tx = self.wallet.sign_transaction(tx, self.password, ignore_warnings=True) @@ -554,7 +535,7 @@ def do_sign(self, tx, broadcast, on_success: Callable[[Transaction], None] = Non if tx is None: self._logger.info('did not sign') if on_failure: - on_failure() + on_failure(_('Could not sign')) return if sign_hook: @@ -576,18 +557,18 @@ def do_sign(self, tx, broadcast, on_success: Callable[[Transaction], None] = Non self.historyModel.initModel(True) if on_success: - on_success(tx) + on_success() - # this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok - def on_sign_complete(self, broadcast, cb: Callable[[Transaction], None] = None, tx: Transaction = None): + # trustedcoin tc_sign_wrapper + def on_tc_sign_complete(self, broadcast, cb: Callable[[], None] = None, tx: Transaction = None): self.otpSuccess.emit() if cb: - cb(tx) + cb() if broadcast: self.broadcast(tx) - # this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok - def on_sign_failed(self, cb: Callable[[], None] = None, error: str = None): + # trustedcoin tc_sign_wrapper + def on_tc_sign_failed(self, cb: Callable[[], None] = None, error: str = None): self.otpFailed.emit('error', error) if cb: cb() @@ -602,7 +583,13 @@ def submit_otp_task(): self._otp_on_submit(otp) threading.Thread(target=submit_otp_task, daemon=True).start() - def broadcast(self, tx): + def broadcast( + self, + tx, + *, + on_success: Callable[[], None] = None, + on_failure: Callable[[str], None] = None + ): assert tx.is_complete() async def broadcast_coro(): @@ -613,15 +600,24 @@ async def broadcast_coro(): except TxBroadcastError as e: self._logger.error(repr(e)) self.broadcastFailed.emit(tx.txid(), '', e.get_message_for_gui()) + if on_failure: + on_failure(e.get_message_for_gui()) except BestEffortRequestFailed as e: self._logger.error(repr(e)) self.broadcastFailed.emit(tx.txid(), '', repr(e)) - except Exception: + if on_failure: + on_failure(repr(e)) + except Exception as e: self._logger.exception("failed to broadcast tx") + self.broadcastFailed.emit(tx.txid(), '', repr(e)) + if on_failure: + on_failure(repr(e)) else: - self._logger.info('broadcast success') self.broadcastSucceeded.emit(tx.txid()) - self.historyModel.requestRefresh.emit() # via qt thread + if self._historyModel: + self._historyModel.requestRefresh() + if on_success: + on_success() finally: self.wallet.set_broadcasting(tx, broadcasting_status=None) diff --git a/electrum/plugins/labels/qml.py b/electrum/plugins/labels/qml.py index 9e43370f4072..84bacba25130 100644 --- a/electrum/plugins/labels/qml.py +++ b/electrum/plugins/labels/qml.py @@ -29,7 +29,7 @@ from electrum.i18n import _ from electrum.plugin import hook -from electrum.gui.qml.qewallet import QEWallet +from electrum.gui.qml.qedaemon import QEDaemon from electrum.gui.common_qt.plugins import PluginQObject from .labels import LabelsPlugin @@ -137,7 +137,7 @@ def pull_thread(_wallet): threading.Thread(target=pull_thread, args=[wallet]).start() def on_pulled(self, wallet): - _wallet = QEWallet.getInstanceFor(wallet) + _wallet = QEDaemon.getQEWalletInstanceFor(wallet) self.logger.debug('wallet ' + ('found' if _wallet else 'not found')) @hook diff --git a/electrum/plugins/trustedcoin/qml.py b/electrum/plugins/trustedcoin/qml.py index f2e66dab1abc..ca61728fec3b 100644 --- a/electrum/plugins/trustedcoin/qml.py +++ b/electrum/plugins/trustedcoin/qml.py @@ -4,7 +4,6 @@ from electrum.plugin import hook from electrum.util import UserFacingException -from electrum.gui.qml.qewallet import QEWallet from electrum.gui.qml.qedaemon import QEDaemon from .common_qt import TrustedcoinPluginQObject @@ -107,7 +106,7 @@ def prompt_user_for_otp(self, wallet, tx, on_success, on_failure): self.on_failure = on_failure if on_failure else lambda x: self.logger.error(x) self.wallet = wallet self.tx = tx - qewallet = QEWallet.getInstanceFor(wallet) + qewallet = QEDaemon.getQEWalletInstanceFor(wallet) qewallet.request_otp(self.on_otp) def on_otp(self, otp): @@ -133,6 +132,6 @@ def on_otp(self, otp): def billing_info_retrieved(self, wallet): self.logger.info('billing_info_retrieved') - qewallet = QEWallet.getInstanceFor(wallet) + qewallet = QEDaemon.getQEWalletInstanceFor(wallet) qewallet.billingInfoChanged.emit() self.so.updateBillingInfo(wallet)