commit 92244041081db96b92925c9e76b117035e241011
parent 73325831b7075f461e106f36099865a3fedadb72
Author: ThomasV <thomasv@electrum.org>
Date: Tue, 14 Apr 2020 16:12:47 +0200
Move callback manager out of Network class
Diffstat:
19 files changed, 146 insertions(+), 129 deletions(-)
diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py
@@ -28,7 +28,7 @@ import itertools
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple, NamedTuple, Sequence, List
-from . import bitcoin
+from . import bitcoin, util
from .bitcoin import COINBASE_MATURITY
from .util import profiler, bfh, TxMinedInfo
from .transaction import Transaction, TxOutput, TxInput, PartialTxInput, TxOutpoint, PartialTransaction
@@ -161,7 +161,7 @@ class AddressSynchronizer(Logger):
if self.network is not None:
self.synchronizer = Synchronizer(self)
self.verifier = SPV(self.network, self)
- self.network.register_callback(self.on_blockchain_updated, ['blockchain_updated'])
+ util.register_callback(self.on_blockchain_updated, ['blockchain_updated'])
def on_blockchain_updated(self, event, *args):
self._get_addr_balance_cache = {} # invalidate cache
@@ -174,7 +174,7 @@ class AddressSynchronizer(Logger):
if self.verifier:
asyncio.run_coroutine_threadsafe(self.verifier.stop(), self.network.asyncio_loop)
self.verifier = None
- self.network.unregister_callback(self.on_blockchain_updated)
+ util.unregister_callback(self.on_blockchain_updated)
self.db.put('stored_height', self.get_local_height())
def add_address(self, address):
@@ -546,7 +546,7 @@ class AddressSynchronizer(Logger):
self.unverified_tx.pop(tx_hash, None)
self.db.add_verified_tx(tx_hash, info)
tx_mined_status = self.get_tx_height(tx_hash)
- self.network.trigger_callback('verified', self, tx_hash, tx_mined_status)
+ util.trigger_callback('verified', self, tx_hash, tx_mined_status)
def get_unverified_txs(self):
'''Returns a map from tx hash to transaction height'''
diff --git a/electrum/channel_db.py b/electrum/channel_db.py
@@ -35,7 +35,7 @@ import threading
from .sql_db import SqlDB, sql
-from . import constants
+from . import constants, util
from .util import bh2u, profiler, get_headers_dir, bfh, is_ip_address, list_enabled_bits
from .logging import Logger
from .lnutil import (LNPeerAddr, format_short_channel_id, ShortChannelID,
@@ -269,8 +269,8 @@ class ChannelDB(SqlDB):
self.num_nodes = len(self._nodes)
self.num_channels = len(self._channels)
self.num_policies = len(self._policies)
- self.network.trigger_callback('channel_db', self.num_nodes, self.num_channels, self.num_policies)
- self.network.trigger_callback('ln_gossip_sync_progress')
+ util.trigger_callback('channel_db', self.num_nodes, self.num_channels, self.num_policies)
+ util.trigger_callback('ln_gossip_sync_progress')
def get_channel_ids(self):
with self.lock:
diff --git a/electrum/daemon.py b/electrum/daemon.py
@@ -41,6 +41,7 @@ from jsonrpcserver import response
from jsonrpcclient.clients.aiohttp_client import AiohttpClient
from aiorpcx import TaskGroup
+from . import util
from .network import Network
from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare)
from .util import PR_PAID, PR_EXPIRED, get_request_status
@@ -181,7 +182,7 @@ class PayServer(Logger):
self.daemon = daemon
self.config = daemon.config
self.pending = defaultdict(asyncio.Event)
- self.daemon.network.register_callback(self.on_payment, ['payment_received'])
+ util.register_callback(self.on_payment, ['payment_received'])
async def on_payment(self, evt, wallet, key, status):
if status == PR_PAID:
diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py
@@ -12,6 +12,7 @@ from typing import Sequence, Optional
from aiorpcx.curio import timeout_after, TaskTimeout, TaskGroup
+from . import util
from .bitcoin import COIN
from .i18n import _
from .util import (ThreadJob, make_dir, log_exceptions,
@@ -456,8 +457,7 @@ class FxThread(ThreadJob):
ThreadJob.__init__(self)
self.config = config
self.network = network
- if self.network:
- self.network.register_callback(self.set_proxy, ['proxy_set'])
+ util.register_callback(self.set_proxy, ['proxy_set'])
self.ccy = self.get_currency()
self.history_used_spot = False
self.ccy_combo = None
@@ -567,12 +567,10 @@ class FxThread(ThreadJob):
self.exchange.read_historical_rates(self.ccy, self.cache_dir)
def on_quotes(self):
- if self.network:
- self.network.trigger_callback('on_quotes')
+ util.trigger_callback('on_quotes')
def on_history(self):
- if self.network:
- self.network.trigger_callback('on_history')
+ util.trigger_callback('on_history')
def exchange_rate(self) -> Decimal:
"""Returns the exchange rate as a Decimal"""
diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
@@ -13,6 +13,7 @@ from electrum.storage import WalletStorage, StorageReadWriteError
from electrum.wallet_db import WalletDB
from electrum.wallet import Wallet, InternalAddressCorruption, Abstract_Wallet
from electrum.plugin import run_hook
+from electrum import util
from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
format_satoshis, format_satoshis_plain, format_fee_satoshis,
PR_PAID, PR_FAILED, maybe_extract_bolt11_invoice)
@@ -50,7 +51,6 @@ from .uix.dialogs.question import Question
# delayed imports: for startup speed on android
notification = app = ref = None
-util = False
# register widget cache for keeping memory down timeout to forever to cache
# the data
@@ -565,20 +565,20 @@ class ElectrumWindow(App):
if self.network:
interests = ['wallet_updated', 'network_updated', 'blockchain_updated',
'status', 'new_transaction', 'verified']
- self.network.register_callback(self.on_network_event, interests)
- self.network.register_callback(self.on_fee, ['fee'])
- self.network.register_callback(self.on_fee_histogram, ['fee_histogram'])
- self.network.register_callback(self.on_quotes, ['on_quotes'])
- self.network.register_callback(self.on_history, ['on_history'])
- self.network.register_callback(self.on_channels, ['channels_updated'])
- self.network.register_callback(self.on_channel, ['channel'])
- self.network.register_callback(self.on_invoice_status, ['invoice_status'])
- self.network.register_callback(self.on_request_status, ['request_status'])
- self.network.register_callback(self.on_payment_failed, ['payment_failed'])
- self.network.register_callback(self.on_payment_succeeded, ['payment_succeeded'])
- self.network.register_callback(self.on_channel_db, ['channel_db'])
- self.network.register_callback(self.set_num_peers, ['gossip_peers'])
- self.network.register_callback(self.set_unknown_channels, ['unknown_channels'])
+ util.register_callback(self.on_network_event, interests)
+ util.register_callback(self.on_fee, ['fee'])
+ util.register_callback(self.on_fee_histogram, ['fee_histogram'])
+ util.register_callback(self.on_quotes, ['on_quotes'])
+ util.register_callback(self.on_history, ['on_history'])
+ util.register_callback(self.on_channels, ['channels_updated'])
+ util.register_callback(self.on_channel, ['channel'])
+ util.register_callback(self.on_invoice_status, ['invoice_status'])
+ util.register_callback(self.on_request_status, ['request_status'])
+ util.register_callback(self.on_payment_failed, ['payment_failed'])
+ util.register_callback(self.on_payment_succeeded, ['payment_succeeded'])
+ util.register_callback(self.on_channel_db, ['channel_db'])
+ util.register_callback(self.set_num_peers, ['gossip_peers'])
+ util.register_callback(self.set_unknown_channels, ['unknown_channels'])
# load wallet
self.load_wallet_by_name(self.electrum_config.get_wallet_path(use_gui_last_wallet=True))
# URI passed in config
diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py
@@ -5,6 +5,7 @@ import PyQt5.QtWidgets as QtWidgets
import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import QLabel, QLineEdit
+from electrum import util
from electrum.i18n import _
from electrum.util import bh2u, format_time
from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction
@@ -132,10 +133,10 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
self.htlc_added.connect(self.do_htlc_added)
# register callbacks for updating
- window.network.register_callback(self.ln_payment_completed.emit, ['ln_payment_completed'])
- window.network.register_callback(self.ln_payment_failed.emit, ['ln_payment_failed'])
- window.network.register_callback(self.htlc_added.emit, ['htlc_added'])
- window.network.register_callback(self.state_changed.emit, ['channel'])
+ util.register_callback(self.ln_payment_completed.emit, ['ln_payment_completed'])
+ util.register_callback(self.ln_payment_failed.emit, ['ln_payment_failed'])
+ util.register_callback(self.htlc_added.emit, ['htlc_added'])
+ util.register_callback(self.state_changed.emit, ['channel'])
# set attributes of QDialog
self.setWindowTitle(_('Channel Details'))
diff --git a/electrum/gui/qt/lightning_dialog.py b/electrum/gui/qt/lightning_dialog.py
@@ -27,6 +27,7 @@ from typing import TYPE_CHECKING
from PyQt5.QtWidgets import (QDialog, QLabel, QVBoxLayout, QPushButton)
+from electrum import util
from electrum.i18n import _
from .util import Buttons
@@ -58,9 +59,9 @@ class LightningDialog(QDialog):
b = QPushButton(_('Close'))
b.clicked.connect(self.close)
vbox.addLayout(Buttons(b))
- self.network.register_callback(self.on_channel_db, ['channel_db'])
- self.network.register_callback(self.set_num_peers, ['gossip_peers'])
- self.network.register_callback(self.set_unknown_channels, ['unknown_channels'])
+ util.register_callback(self.on_channel_db, ['channel_db'])
+ util.register_callback(self.set_num_peers, ['gossip_peers'])
+ util.register_callback(self.set_unknown_channels, ['unknown_channels'])
self.network.channel_db.update_counts() # trigger callback
self.set_num_peers('', self.network.lngossip.num_peers())
self.set_unknown_channels('', len(self.network.lngossip.unknown_ids))
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
@@ -272,7 +272,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
# 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.network.register_callback(self.on_network, interests)
+ util.register_callback(self.on_network, interests)
# set initial message
self.console.showMessage(self.network.banner)
@@ -466,8 +466,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
def load_wallet(self, wallet):
wallet.thread = TaskThread(self, self.on_error)
self.update_recently_visited(wallet.storage.path)
- if wallet.lnworker and wallet.network:
- wallet.network.trigger_callback('channels_updated', wallet)
+ if wallet.lnworker:
+ util.trigger_callback('channels_updated', wallet)
self.need_update.set()
# Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
# update menus
@@ -2889,8 +2889,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
def clean_up(self):
self.wallet.thread.stop()
- if self.network:
- self.network.unregister_callback(self.on_network)
+ util.unregister_callback(self.on_network)
self.config.set_key("is_maximized", self.isMaximized())
if not self.isMaximized():
g = self.geometry()
diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, Q
from PyQt5.QtGui import QFontMetrics
from electrum.i18n import _
-from electrum import constants, blockchain
+from electrum import constants, blockchain, util
from electrum.interface import serialize_server, deserialize_server
from electrum.network import Network
from electrum.logging import get_logger
@@ -61,7 +61,7 @@ class NetworkDialog(QDialog):
vbox.addLayout(Buttons(CloseButton(self)))
self.network_updated_signal_obj.network_updated_signal.connect(
self.on_update)
- network.register_callback(self.on_network, ['network_updated'])
+ util.register_callback(self.on_network, ['network_updated'])
def on_network(self, event, *args):
self.network_updated_signal_obj.network_updated_signal.emit(event, args)
diff --git a/electrum/gui/stdio.py b/electrum/gui/stdio.py
@@ -3,6 +3,7 @@ import getpass
import datetime
import logging
+from electrum import util
from electrum import WalletStorage, Wallet
from electrum.util import format_satoshis
from electrum.bitcoin import is_address, COIN
@@ -43,7 +44,7 @@ class ElectrumGui:
self.wallet.start_network(self.network)
self.contacts = self.wallet.contacts
- self.network.register_callback(self.on_network, ['wallet_updated', 'network_updated', 'banner'])
+ util.register_callback(self.on_network, ['wallet_updated', 'network_updated', 'banner'])
self.commands = [_("[h] - displays this help text"), \
_("[i] - display transaction history"), \
_("[o] - enter payment order"), \
diff --git a/electrum/gui/text.py b/electrum/gui/text.py
@@ -8,6 +8,7 @@ import getpass
import logging
import electrum
+from electrum import util
from electrum.util import format_satoshis
from electrum.bitcoin import is_address, COIN
from electrum.transaction import PartialTxOutput
@@ -65,8 +66,7 @@ class ElectrumGui:
self.str_fee = ""
self.history = None
- if self.network:
- self.network.register_callback(self.update, ['wallet_updated', 'network_updated'])
+ util.register_callback(self.update, ['wallet_updated', 'network_updated'])
self.tab_names = [_("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner")]
self.num_tabs = len(self.tab_names)
diff --git a/electrum/interface.py b/electrum/interface.py
@@ -548,7 +548,7 @@ class Interface(Logger):
raise GracefulDisconnect('server tip below max checkpoint')
self._mark_ready()
await self._process_header_at_tip()
- self.network.trigger_callback('network_updated')
+ util.trigger_callback('network_updated')
await self.network.switch_unwanted_fork_interface()
await self.network.switch_lagging_interface()
@@ -563,7 +563,7 @@ class Interface(Logger):
# in the simple case, height == self.tip+1
if height <= self.tip:
await self.sync_until(height)
- self.network.trigger_callback('blockchain_updated')
+ util.trigger_callback('blockchain_updated')
async def sync_until(self, height, next_height=None):
if next_height is None:
@@ -578,7 +578,7 @@ class Interface(Logger):
raise GracefulDisconnect('server chain conflicts with checkpoints or genesis')
last, height = await self.step(height)
continue
- self.network.trigger_callback('network_updated')
+ util.trigger_callback('network_updated')
height = (height // 2016 * 2016) + num_headers
assert height <= next_height+1, (height, self.tip)
last = 'catchup'
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
@@ -33,7 +33,7 @@ from aiorpcx import NetAddress
import attr
from . import ecc
-from . import constants
+from . import constants, util
from .util import bfh, bh2u, chunks, TxMinedInfo
from .bitcoin import redeem_script_to_address
from .crypto import sha256, sha256d
@@ -679,16 +679,14 @@ class Channel(AbstractChannel):
def set_frozen_for_sending(self, b: bool) -> None:
self.storage['frozen_for_sending'] = bool(b)
- if self.lnworker:
- self.lnworker.network.trigger_callback('channel', self)
+ util.trigger_callback('channel', self)
def is_frozen_for_receiving(self) -> bool:
return self.storage.get('frozen_for_receiving', False)
def set_frozen_for_receiving(self, b: bool) -> None:
self.storage['frozen_for_receiving'] = bool(b)
- if self.lnworker:
- self.lnworker.network.trigger_callback('channel', self)
+ util.trigger_callback('channel', self)
def _assert_can_add_htlc(self, *, htlc_proposer: HTLCOwner, amount_msat: int) -> None:
"""Raises PaymentFailure if the htlc_proposer cannot add this new HTLC.
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
@@ -19,7 +19,7 @@ from datetime import datetime
import aiorpcx
from .crypto import sha256, sha256d
-from . import bitcoin
+from . import bitcoin, util
from . import ecc
from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string, der_sig_from_sig_string
from . import constants
@@ -744,7 +744,7 @@ class Peer(Logger):
f'already in peer_state {chan.peer_state}')
return
chan.peer_state = PeerState.REESTABLISHING
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
# BOLT-02: "A node [...] upon disconnection [...] MUST reverse any uncommitted updates sent by the other side"
chan.hm.discard_unsigned_remote_updates()
# ctns
@@ -891,7 +891,7 @@ class Peer(Logger):
# checks done
if chan.is_funded() and chan.config[LOCAL].funding_locked_received:
self.mark_open(chan)
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
if chan.get_state() == ChannelState.CLOSING:
await self.send_shutdown(chan)
@@ -979,7 +979,7 @@ class Peer(Logger):
return
assert chan.config[LOCAL].funding_locked_received
chan.set_state(ChannelState.OPEN)
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
# peer may have sent us a channel update for the incoming direction previously
pending_channel_update = self.orphan_channel_updates.get(chan.short_channel_id)
if pending_channel_update:
diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
@@ -8,6 +8,7 @@ import asyncio
from enum import IntEnum, auto
from typing import NamedTuple, Dict
+from . import util
from .sql_db import SqlDB, sql
from .wallet_db import WalletDB
from .util import bh2u, bfh, log_exceptions, ignore_exceptions, TxMinedInfo
@@ -139,8 +140,9 @@ class LNWatcher(AddressSynchronizer):
self.config = network.config
self.channels = {}
self.network = network
- self.network.register_callback(self.on_network_update,
- ['network_updated', 'blockchain_updated', 'verified', 'wallet_updated', 'fee'])
+ util.register_callback(
+ self.on_network_update,
+ ['network_updated', 'blockchain_updated', 'verified', 'wallet_updated', 'fee'])
# status gets populated when we run
self.channel_status = {}
@@ -420,4 +422,4 @@ class LNWalletWatcher(LNWatcher):
tx_was_added = False
if tx_was_added:
self.logger.info(f'added future tx: {name}. prevout: {prevout}')
- self.network.trigger_callback('wallet_updated', self.lnworker.wallet)
+ util.trigger_callback('wallet_updated', self.lnworker.wallet)
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -21,7 +21,7 @@ import dns.resolver
import dns.exception
from aiorpcx import run_in_thread
-from . import constants
+from . import constants, util
from . import keystore
from .util import profiler
from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING
@@ -367,7 +367,7 @@ class LNGossip(LNWorker):
max_age = 14*24*3600
LOGGING_SHORTCUT = 'g'
- def __init__(self, network):
+ def __init__(self):
seed = os.urandom(32)
node = BIP32Node.from_rootseed(seed, xtype='standard')
xprv = node.to_xprv()
@@ -393,16 +393,16 @@ class LNGossip(LNWorker):
known = self.channel_db.get_channel_ids()
new = set(ids) - set(known)
self.unknown_ids.update(new)
- self.network.trigger_callback('unknown_channels', len(self.unknown_ids))
- self.network.trigger_callback('gossip_peers', self.num_peers())
- self.network.trigger_callback('ln_gossip_sync_progress')
+ util.trigger_callback('unknown_channels', len(self.unknown_ids))
+ util.trigger_callback('gossip_peers', self.num_peers())
+ util.trigger_callback('ln_gossip_sync_progress')
def get_ids_to_query(self):
N = 500
l = list(self.unknown_ids)
self.unknown_ids = set(l[N:])
- self.network.trigger_callback('unknown_channels', len(self.unknown_ids))
- self.network.trigger_callback('ln_gossip_sync_progress')
+ util.trigger_callback('unknown_channels', len(self.unknown_ids))
+ util.trigger_callback('ln_gossip_sync_progress')
return l[0:N]
def get_sync_progress_estimate(self) -> Tuple[Optional[int], Optional[int]]:
@@ -514,7 +514,7 @@ class LNWallet(LNWorker):
def peer_closed(self, peer):
for chan in self.channels_for_peer(peer.pubkey).values():
chan.peer_state = PeerState.DISCONNECTED
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
super().peer_closed(peer)
def get_settled_payments(self):
@@ -645,14 +645,14 @@ class LNWallet(LNWorker):
def channel_state_changed(self, chan):
self.save_channel(chan)
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
def save_channel(self, chan):
assert type(chan) is Channel
if chan.config[REMOTE].next_per_commitment_point == chan.config[REMOTE].current_per_commitment_point:
raise Exception("Tried to save channel with next_point == current_point, this should not happen")
self.wallet.save_db()
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
def channel_by_txo(self, txo):
with self.lock:
@@ -703,7 +703,7 @@ class LNWallet(LNWorker):
funding_sat=funding_sat,
push_msat=push_sat * 1000,
temp_channel_id=os.urandom(32))
- self.network.trigger_callback('channels_updated', self.wallet)
+ util.trigger_callback('channels_updated', self.wallet)
self.wallet.add_transaction(funding_tx) # save tx as local into the wallet
self.wallet.set_label(funding_tx.txid(), _('Open channel'))
if funding_tx.is_complete():
@@ -804,10 +804,10 @@ class LNWallet(LNWorker):
# note: path-finding runs in a separate thread so that we don't block the asyncio loop
# graph updates might occur during the computation
self.set_invoice_status(key, PR_ROUTING)
- self.network.trigger_callback('invoice_status', key)
+ util.trigger_callback('invoice_status', key)
route = await run_in_thread(self._create_route_from_invoice, lnaddr)
self.set_invoice_status(key, PR_INFLIGHT)
- self.network.trigger_callback('invoice_status', key)
+ util.trigger_callback('invoice_status', key)
payment_attempt_log = await self._pay_to_route(route, lnaddr)
except Exception as e:
log.append(PaymentAttemptLog(success=False, exception=e))
@@ -820,11 +820,11 @@ class LNWallet(LNWorker):
break
else:
reason = _('Failed after {} attempts').format(attempts)
- self.network.trigger_callback('invoice_status', key)
+ util.trigger_callback('invoice_status', key)
if success:
- self.network.trigger_callback('payment_succeeded', key)
+ util.trigger_callback('payment_succeeded', key)
else:
- self.network.trigger_callback('payment_failed', key, reason)
+ util.trigger_callback('payment_failed', key, reason)
return success
async def _pay_to_route(self, route: LNPaymentRoute, lnaddr: LnAddr) -> PaymentAttemptLog:
@@ -840,7 +840,7 @@ class LNWallet(LNWorker):
payment_hash=lnaddr.paymenthash,
min_final_cltv_expiry=lnaddr.get_min_final_cltv_expiry(),
payment_secret=lnaddr.payment_secret)
- self.network.trigger_callback('htlc_added', htlc, lnaddr, SENT)
+ util.trigger_callback('htlc_added', htlc, lnaddr, SENT)
payment_attempt = await self.await_payment(lnaddr.paymenthash)
if payment_attempt.success:
failure_log = None
@@ -1139,9 +1139,9 @@ class LNWallet(LNWorker):
f.set_result(payment_attempt)
else:
chan.logger.info('received unexpected payment_failed, probably from previous session')
- self.network.trigger_callback('invoice_status', key)
- self.network.trigger_callback('payment_failed', key, '')
- self.network.trigger_callback('ln_payment_failed', payment_hash, chan.channel_id)
+ util.trigger_callback('invoice_status', key)
+ util.trigger_callback('payment_failed', key, '')
+ util.trigger_callback('ln_payment_failed', payment_hash, chan.channel_id)
def payment_sent(self, chan, payment_hash: bytes):
self.set_payment_status(payment_hash, PR_PAID)
@@ -1155,14 +1155,14 @@ class LNWallet(LNWorker):
f.set_result(payment_attempt)
else:
chan.logger.info('received unexpected payment_sent, probably from previous session')
- self.network.trigger_callback('invoice_status', key)
- self.network.trigger_callback('payment_succeeded', key)
- self.network.trigger_callback('ln_payment_completed', payment_hash, chan.channel_id)
+ util.trigger_callback('invoice_status', key)
+ util.trigger_callback('payment_succeeded', key)
+ util.trigger_callback('ln_payment_completed', payment_hash, chan.channel_id)
def payment_received(self, chan, payment_hash: bytes):
self.set_payment_status(payment_hash, PR_PAID)
- self.network.trigger_callback('request_status', payment_hash.hex(), PR_PAID)
- self.network.trigger_callback('ln_payment_completed', payment_hash, chan.channel_id)
+ util.trigger_callback('request_status', payment_hash.hex(), PR_PAID)
+ util.trigger_callback('ln_payment_completed', payment_hash, chan.channel_id)
async def _calc_routing_hints_for_invoice(self, amount_sat):
"""calculate routing hints (BOLT-11 'r' field)"""
@@ -1251,8 +1251,8 @@ class LNWallet(LNWorker):
self.channels.pop(chan_id)
self.db.get('channels').pop(chan_id.hex())
- self.network.trigger_callback('channels_updated', self.wallet)
- self.network.trigger_callback('wallet_updated', self.wallet)
+ util.trigger_callback('channels_updated', self.wallet)
+ util.trigger_callback('wallet_updated', self.wallet)
@ignore_exceptions
@log_exceptions
@@ -1355,7 +1355,7 @@ class LNBackups(Logger):
self.channel_backups[bfh(channel_id)] = ChannelBackup(cb, sweep_address=self.sweep_address, lnworker=self)
def channel_state_changed(self, chan):
- self.network.trigger_callback('channel', chan)
+ util.trigger_callback('channel', chan)
def peer_closed(self, chan):
pass
@@ -1389,7 +1389,7 @@ class LNBackups(Logger):
d[channel_id] = cb_storage
self.channel_backups[bfh(channel_id)] = cb = ChannelBackup(cb_storage, sweep_address=self.sweep_address, lnworker=self)
self.wallet.save_db()
- self.network.trigger_callback('channels_updated', self.wallet)
+ util.trigger_callback('channels_updated', self.wallet)
self.lnwatcher.add_channel(cb.funding_outpoint.to_str(), cb.get_funding_address())
def remove_channel_backup(self, channel_id):
@@ -1399,7 +1399,7 @@ class LNBackups(Logger):
d.pop(channel_id.hex())
self.channel_backups.pop(channel_id)
self.wallet.save_db()
- self.network.trigger_callback('channels_updated', self.wallet)
+ util.trigger_callback('channels_updated', self.wallet)
@log_exceptions
async def request_force_close(self, channel_id):
diff --git a/electrum/network.py b/electrum/network.py
@@ -278,7 +278,6 @@ class Network(Logger):
# locks
self.restart_lock = asyncio.Lock()
self.bhi_lock = asyncio.Lock()
- self.callback_lock = threading.Lock()
self.recent_servers_lock = threading.RLock() # <- re-entrant
self.interfaces_lock = threading.Lock() # for mutating/iterating self.interfaces
@@ -288,8 +287,6 @@ class Network(Logger):
self.banner = ''
self.donation_address = ''
self.relay_fee = None # type: Optional[int]
- # callbacks set by the GUI
- self.callbacks = defaultdict(list) # note: needs self.callback_lock
dir_path = os.path.join(self.config.path, 'certs')
util.make_dir(dir_path)
@@ -332,7 +329,7 @@ class Network(Logger):
from . import channel_db
self.channel_db = channel_db.ChannelDB(self)
self.path_finder = lnrouter.LNPathFinder(self.channel_db)
- self.lngossip = lnworker.LNGossip(self)
+ self.lngossip = lnworker.LNGossip()
self.lngossip.start_network(self)
def run_from_another_thread(self, coro, *, timeout=None):
@@ -350,27 +347,6 @@ class Network(Logger):
return func(self, *args, **kwargs)
return func_wrapper
- def register_callback(self, callback, events):
- with self.callback_lock:
- for event in events:
- self.callbacks[event].append(callback)
-
- def unregister_callback(self, callback):
- with self.callback_lock:
- for callbacks in self.callbacks.values():
- if callback in callbacks:
- callbacks.remove(callback)
-
- def trigger_callback(self, event, *args):
- with self.callback_lock:
- callbacks = self.callbacks[event][:]
- for callback in callbacks:
- # FIXME: if callback throws, we will lose the traceback
- if asyncio.iscoroutinefunction(callback):
- asyncio.run_coroutine_threadsafe(callback(event, *args), self.asyncio_loop)
- else:
- self.asyncio_loop.call_soon_threadsafe(callback, event, *args)
-
def _read_recent_servers(self):
if not self.config.path:
return []
@@ -481,9 +457,9 @@ class Network(Logger):
def notify(self, key):
if key in ['status', 'updated']:
- self.trigger_callback(key)
+ util.trigger_callback(key)
else:
- self.trigger_callback(key, self.get_status_value(key))
+ util.trigger_callback(key, self.get_status_value(key))
def get_parameters(self) -> NetworkParameters:
host, port, protocol = deserialize_server(self.default_server)
@@ -574,7 +550,7 @@ class Network(Logger):
self.proxy = proxy
dns_hacks.configure_dns_depending_on_proxy(bool(proxy))
self.logger.info(f'setting proxy {proxy}')
- self.trigger_callback('proxy_set', self.proxy)
+ util.trigger_callback('proxy_set', self.proxy)
@log_exceptions
async def set_parameters(self, net_params: NetworkParameters):
@@ -700,12 +676,13 @@ class Network(Logger):
blockchain_updated = i.blockchain != self.blockchain()
self.interface = i
await i.taskgroup.spawn(self._request_server_info(i))
- self.trigger_callback('default_server_changed')
+ util.trigger_callback('default_server_changed')
self.default_server_changed_event.set()
self.default_server_changed_event.clear()
self._set_status('connected')
- self.trigger_callback('network_updated')
- if blockchain_updated: self.trigger_callback('blockchain_updated')
+ util.trigger_callback('network_updated')
+ if blockchain_updated:
+ util.trigger_callback('blockchain_updated')
async def _close_interface(self, interface: Interface):
if interface:
@@ -734,7 +711,7 @@ class Network(Logger):
if server == self.default_server:
self._set_status('disconnected')
await self._close_interface(interface)
- self.trigger_callback('network_updated')
+ util.trigger_callback('network_updated')
def get_network_timeout_seconds(self, request_type=NetworkTimeout.Generic) -> int:
if self.oneserver and not self.auto_connect:
@@ -767,7 +744,7 @@ class Network(Logger):
await self.switch_to_interface(server)
self._add_recent_server(server)
- self.trigger_callback('network_updated')
+ util.trigger_callback('network_updated')
def check_interface_against_healthy_spread_of_connected_servers(self, iface_to_check) -> bool:
# main interface is exempt. this makes switching servers easier
@@ -1152,7 +1129,7 @@ class Network(Logger):
self.logger.info("taskgroup stopped.")
asyncio.run_coroutine_threadsafe(main(), self.asyncio_loop)
- self.trigger_callback('network_updated')
+ util.trigger_callback('network_updated')
def start(self, jobs: Iterable = None):
"""Schedule starting the network, along with the given job co-routines.
@@ -1176,7 +1153,7 @@ class Network(Logger):
self.connecting.clear()
self.server_queue = None
if not full_shutdown:
- self.trigger_callback('network_updated')
+ util.trigger_callback('network_updated')
def stop(self):
assert self._loop_thread != threading.current_thread(), 'must not be called from network thread'
diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py
@@ -30,6 +30,7 @@ import logging
from aiorpcx import TaskGroup, run_in_thread, RPCError
+from . import util
from .transaction import Transaction, PartialTransaction
from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer
from .bitcoin import address_to_scripthash, is_address
@@ -227,7 +228,7 @@ class Synchronizer(SynchronizerBase):
self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
self.logger.info(f"received tx {tx_hash} height: {tx_height} bytes: {len(raw_tx)}")
# callbacks
- self.wallet.network.trigger_callback('new_transaction', self.wallet, tx)
+ util.trigger_callback('new_transaction', self.wallet, tx)
async def main(self):
self.wallet.set_up_to_date(False)
@@ -252,7 +253,7 @@ class Synchronizer(SynchronizerBase):
if up_to_date:
self._reset_request_counters()
self.wallet.set_up_to_date(up_to_date)
- self.wallet.network.trigger_callback('wallet_updated', self.wallet)
+ util.trigger_callback('wallet_updated', self.wallet)
class Notifier(SynchronizerBase):
diff --git a/electrum/util.py b/electrum/util.py
@@ -1130,7 +1130,7 @@ class NetworkJobOnDefaultServer(Logger):
self._restart_lock = asyncio.Lock()
self._reset()
asyncio.run_coroutine_threadsafe(self._restart(), network.asyncio_loop)
- network.register_callback(self._restart, ['default_server_changed'])
+ register_callback(self._restart, ['default_server_changed'])
def _reset(self):
"""Initialise fields. Called every time the underlying
@@ -1304,3 +1304,41 @@ def randrange(bound: int) -> int:
"""Return a random integer k such that 1 <= k < bound, uniformly
distributed across that range."""
return ecdsa.util.randrange(bound)
+
+
+class CallbackManager:
+ # callbacks set by the GUI
+ def __init__(self):
+ self.callback_lock = threading.Lock()
+ self.callbacks = defaultdict(list) # note: needs self.callback_lock
+ self.asyncio_loop = None
+
+ def register_callback(self, callback, events):
+ with self.callback_lock:
+ for event in events:
+ self.callbacks[event].append(callback)
+
+ def unregister_callback(self, callback):
+ with self.callback_lock:
+ for callbacks in self.callbacks.values():
+ if callback in callbacks:
+ callbacks.remove(callback)
+
+ def trigger_callback(self, event, *args):
+ if self.asyncio_loop is None:
+ self.asyncio_loop = asyncio.get_event_loop()
+ assert self.asyncio_loop.is_running(), "event loop not running"
+ with self.callback_lock:
+ callbacks = self.callbacks[event][:]
+ for callback in callbacks:
+ # FIXME: if callback throws, we will lose the traceback
+ if asyncio.iscoroutinefunction(callback):
+ asyncio.run_coroutine_threadsafe(callback(event, *args), self.asyncio_loop)
+ else:
+ self.asyncio_loop.call_soon_threadsafe(callback, event, *args)
+
+
+callback_mgr = CallbackManager()
+trigger_callback = callback_mgr.trigger_callback
+register_callback = callback_mgr.register_callback
+unregister_callback = callback_mgr.unregister_callback