From eb52090fee61811df00a1b60d5a7fad642f5b579 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 16 Apr 2025 09:07:27 +0200 Subject: [PATCH 1/3] plugins: psbt_nostr: start processing PSBTs after wallet is_up_to_date also don't break the receive loop when an invalid tx is received. --- electrum/plugins/psbt_nostr/psbt_nostr.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/electrum/plugins/psbt_nostr/psbt_nostr.py b/electrum/plugins/psbt_nostr/psbt_nostr.py index 19a843130..6f71ce85a 100644 --- a/electrum/plugins/psbt_nostr/psbt_nostr.py +++ b/electrum/plugins/psbt_nostr/psbt_nostr.py @@ -38,7 +38,7 @@ from electrum.i18n import _ from electrum.logging import Logger from electrum.plugin import BasePlugin from electrum.transaction import PartialTransaction, tx_from_any -from electrum.util import log_exceptions, OldTaskGroup, ca_path, trigger_callback +from electrum.util import log_exceptions, OldTaskGroup, ca_path, trigger_callback, event_listener from electrum.wallet import Multisig_Wallet if TYPE_CHECKING: @@ -84,7 +84,10 @@ class CosignerWallet(Logger): self.network = wallet.network self.config = self.wallet.config + self.pending = asyncio.Event() + self.wallet_uptodate = asyncio.Event() + self.known_events = wallet.db.get_dict('cosigner_events') for k, v in list(self.known_events.items()): @@ -114,10 +117,17 @@ class CosignerWallet(Logger): if self.network and self.nostr_pubkey: asyncio.run_coroutine_threadsafe(self.main_loop(), self.network.asyncio_loop) + @event_listener + def on_event_wallet_updated(self, wallet): + if self.wallet == wallet and wallet.is_up_to_date() and not self.wallet_uptodate.is_set(): + self.logger.debug('starting handling of PSBTs') + self.wallet_uptodate.set() + @log_exceptions async def main_loop(self): self.logger.info("starting taskgroup.") try: + await self.wallet_uptodate.wait() # start processing PSBTs only after wallet is_up_to_date async with self.taskgroup as group: await group.spawn(self.check_direct_messages()) except Exception as e: @@ -189,7 +199,7 @@ class CosignerWallet(Logger): except Exception as e: self.logger.info(_("Unable to deserialize the transaction:") + "\n" + str(e)) self.known_events[event.id] = now() - return + continue self.logger.info(f"received PSBT from {event.pubkey}") trigger_callback('psbt_nostr_received', self.wallet, event.pubkey, event.id, tx) await self.pending.wait() From 182accb9fbbdfa818a932ef278b61cf9feb9a445 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 16 Apr 2025 09:47:41 +0200 Subject: [PATCH 2/3] plugins: psbt_nostr: move can_send_psbt logic from GUI to backend, fix qml wallet switch bug --- electrum/plugins/psbt_nostr/psbt_nostr.py | 8 ++++++++ electrum/plugins/psbt_nostr/qml.py | 17 ++++++++++++----- electrum/plugins/psbt_nostr/qml/main.qml | 2 +- electrum/plugins/psbt_nostr/qt.py | 10 +--------- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/electrum/plugins/psbt_nostr/psbt_nostr.py b/electrum/plugins/psbt_nostr/psbt_nostr.py index 6f71ce85a..9c86d8618 100644 --- a/electrum/plugins/psbt_nostr/psbt_nostr.py +++ b/electrum/plugins/psbt_nostr/psbt_nostr.py @@ -218,6 +218,14 @@ class CosignerWallet(Logger): # note that tx could also be unrelated from wallet?... (not ismine inputs) return True + def can_send_psbt(self, tx: Union[Transaction, PartialTransaction]) -> bool: + if tx.is_complete() or self.wallet.can_sign(tx): + return False + for xpub, pubkey in self.cosigner_list: + if self.cosigner_can_sign(tx, xpub): + return True + return False + def mark_pending_event_rcvd(self, event_id): self.logger.debug('marking event rcvd') self.known_events[event_id] = now() diff --git a/electrum/plugins/psbt_nostr/qml.py b/electrum/plugins/psbt_nostr/qml.py index 1818eb59c..6ff9af905 100644 --- a/electrum/plugins/psbt_nostr/qml.py +++ b/electrum/plugins/psbt_nostr/qml.py @@ -58,23 +58,30 @@ class QReceiveSignalObject(QObject): def loader(self): return 'main.qml' + @pyqtSlot(QEWallet, str, result=bool) + def canSendPsbt(self, wallet: 'QEWallet', tx: str) -> bool: + cosigner_wallet = self._plugin.cosigner_wallets.get(wallet.wallet) + if not cosigner_wallet: + return False + return cosigner_wallet.can_send_psbt(tx_from_any(tx, deserialize=True)) + @pyqtSlot(QEWallet, str) def sendPsbt(self, wallet: 'QEWallet', tx: str): - cosigner_wallet = self._plugin.cosigner_wallets[wallet.wallet] + cosigner_wallet = self._plugin.cosigner_wallets.get(wallet.wallet) if not cosigner_wallet: return cosigner_wallet.send_psbt(tx_from_any(tx, deserialize=True)) @pyqtSlot(QEWallet, str) def acceptPsbt(self, wallet: 'QEWallet', event_id: str): - cosigner_wallet = self._plugin.cosigner_wallets[wallet.wallet] + cosigner_wallet = self._plugin.cosigner_wallets.get(wallet.wallet) if not cosigner_wallet: return cosigner_wallet.accept_psbt(event_id) @pyqtSlot(QEWallet, str) def rejectPsbt(self, wallet: 'QEWallet', event_id: str): - cosigner_wallet = self._plugin.cosigner_wallets[wallet.wallet] + cosigner_wallet = self._plugin.cosigner_wallets.get(wallet.wallet) if not cosigner_wallet: return cosigner_wallet.reject_psbt(event_id) @@ -98,8 +105,8 @@ class Plugin(PsbtNostrPlugin): @hook def load_wallet(self, wallet: 'Abstract_Wallet'): # remove existing, only foreground wallet active - if len(self.cosigner_wallets): - self.remove_cosigner_wallet(self.cosigner_wallets[0]) + for wallet in self.cosigner_wallets.copy().keys(): + self.remove_cosigner_wallet(wallet) if not isinstance(wallet, Multisig_Wallet): return self.add_cosigner_wallet(wallet, QmlCosignerWallet(wallet, self)) diff --git a/electrum/plugins/psbt_nostr/qml/main.qml b/electrum/plugins/psbt_nostr/qml/main.qml index b54ceee83..7df6d6aed 100644 --- a/electrum/plugins/psbt_nostr/qml/main.qml +++ b/electrum/plugins/psbt_nostr/qml/main.qml @@ -36,7 +36,7 @@ Item { property variant dialog text: qsTr('Nostr') icon.source: Qt.resolvedUrl('../../../gui/icons/network.png') - visible: Daemon.currentWallet.isMultisig && Daemon.currentWallet.walletType != '2fa' + visible: AppController.plugin('psbt_nostr').canSendPsbt(Daemon.currentWallet, dialog.text) onClicked: { console.log('about to psbt nostr send') psbt_nostr_send_button.enabled = false diff --git a/electrum/plugins/psbt_nostr/qt.py b/electrum/plugins/psbt_nostr/qt.py index 73818e600..49eeef81d 100644 --- a/electrum/plugins/psbt_nostr/qt.py +++ b/electrum/plugins/psbt_nostr/qt.py @@ -77,15 +77,7 @@ class Plugin(PsbtNostrPlugin): def transaction_dialog_update(self, d: 'TxDialog'): if cw := self.cosigner_wallets.get(d.wallet): assert isinstance(cw, QtCosignerWallet) - if d.tx.is_complete() or d.wallet.can_sign(d.tx): - d.cosigner_send_button.setVisible(False) - return - for xpub, pubkey in cw.cosigner_list: - if cw.cosigner_can_sign(d.tx, xpub): - d.cosigner_send_button.setVisible(True) - break - else: - d.cosigner_send_button.setVisible(False) + d.cosigner_send_button.setVisible(cw.can_send_psbt(d.tx)) class QtCosignerWallet(EventListener, CosignerWallet): From 4e9ec5d2ea397b235fe5b8e5b6dc348deb7a9d0e Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 16 Apr 2025 10:05:54 +0200 Subject: [PATCH 3/3] plugins: psbt_nostr: exclude 2FA wallets --- electrum/plugins/psbt_nostr/qml.py | 2 ++ electrum/plugins/psbt_nostr/qt.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/electrum/plugins/psbt_nostr/qml.py b/electrum/plugins/psbt_nostr/qml.py index 6ff9af905..8dc9d65b4 100644 --- a/electrum/plugins/psbt_nostr/qml.py +++ b/electrum/plugins/psbt_nostr/qml.py @@ -109,6 +109,8 @@ class Plugin(PsbtNostrPlugin): self.remove_cosigner_wallet(wallet) if not isinstance(wallet, Multisig_Wallet): return + if wallet.wallet_type == '2fa': + return self.add_cosigner_wallet(wallet, QmlCosignerWallet(wallet, self)) diff --git a/electrum/plugins/psbt_nostr/qt.py b/electrum/plugins/psbt_nostr/qt.py index 49eeef81d..160bb2541 100644 --- a/electrum/plugins/psbt_nostr/qt.py +++ b/electrum/plugins/psbt_nostr/qt.py @@ -55,6 +55,8 @@ class Plugin(PsbtNostrPlugin): def load_wallet(self, wallet: 'Abstract_Wallet', window: 'ElectrumWindow'): if not isinstance(wallet, Multisig_Wallet): return + if wallet.wallet_type == '2fa': + return self.add_cosigner_wallet(wallet, QtCosignerWallet(wallet, window)) @hook