bug: psbt_nostr: set CosignerWallet.pending on aio loop
What changed, and why it matters
This is a bug-fix patch for a crash in Electrum's optional PSBT-over-Nostr plugin. The plugin helps co-signers exchange partially-signed Bitcoin transactions. The crash happened because a background thread tried to update an asyncio synchronization object on the wrong event loop, triggering a RuntimeError. The fix routes that update to the correct asyncio loop. It is a reliability fix, not a security vulnerability, and there is no evidence it can be exploited by an attacker.
Treat as a normal stability bug fix. No urgent security action is required. Users of the psbt_nostr plugin should update to avoid crashes during co-signing workflows. Reviewers may want to audit other plugin callbacks for similar cross-thread asyncio access.
Security signals we found
Thread-safety violation in asyncio event-loop usage
Crash-only symptom (RuntimeError), no privilege escalation or data corruption evident
Fix uses existing utility run_sync_function_on_asyncio_thread for cross-thread scheduling
Evidence from the diff
In electrum/plugins/psbt_nostr/psbt_nostr.py, mark_pending_event_rcvd() was calling self.pending.set() directly from a non-asyncio thread (likely a Nostr client callback thread). asyncio.Event.set() is not thread-safe and raises RuntimeError when invoked from a different thread than the event loop’s. The patch imports run_sync_function_on_asyncio_thread and uses it to schedule self.pending.set() on the asyncio loop with block=False. This resolves the thread-safety violation and prevents the plugin from crashing on incoming Nostr events.
Changed components
electrum/plugins/psbt_nostr/psbt_nostr.pyCosignerWallet.mark_pending_event_rcvdElectrum PSBT-over-Nostr multisig co-signing pluginInspect captured patch +3 / −2
diff --git a/electrum/plugins/psbt_nostr/psbt_nostr.py b/electrum/plugins/psbt_nostr/psbt_nostr.py
index fed2a8a..6d45349 100644
--- a/electrum/plugins/psbt_nostr/psbt_nostr.py
+++ b/electrum/plugins/psbt_nostr/psbt_nostr.py
@@ -40,7 +40,8 @@ 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, event_listener, json_decode, make_aiohttp_proxy_connector
+ log_exceptions, OldTaskGroup, ca_path, trigger_callback, event_listener, json_decode,
+ make_aiohttp_proxy_connector, run_sync_function_on_asyncio_thread,
)
from electrum.wallet import Multisig_Wallet
@@ -250,7 +251,7 @@ class CosignerWallet(Logger):
def mark_pending_event_rcvd(self, event_id):
self.logger.debug('marking event rcvd')
self.known_events[event_id] = now()
- self.pending.set()
+ run_sync_function_on_asyncio_thread(self.pending.set, block=False)
def prepare_messages(self, tx: Union[Transaction, PartialTransaction], label: str = None) -> List[Tuple[str, dict]]:
messages = []
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.