from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum import constants from electrum.interface import ServerAddr from .util import QtEventListener, event_listener from .qeserverlistmodel import QEServerListModel class QENetwork(QObject, QtEventListener): _logger = get_logger(__name__) networkUpdated = pyqtSignal() blockchainUpdated = pyqtSignal() heightChanged = pyqtSignal([int], arguments=['height']) defaultServerChanged = pyqtSignal() proxySet = pyqtSignal() proxyChanged = pyqtSignal() statusChanged = pyqtSignal() feeHistogramUpdated = pyqtSignal() chaintipsChanged = pyqtSignal() isLaggingChanged = pyqtSignal() gossipUpdated = pyqtSignal() # shared signal for static properties dataChanged = pyqtSignal() _height = 0 _status = "" _chaintips = 1 _islagging = False _fee_histogram = [] _gossipPeers = 0 _gossipUnknownChannels = 0 _gossipDbNodes = 0 _gossipDbChannels = 0 _gossipDbPolicies = 0 def __init__(self, network, qeconfig, parent=None): super().__init__(parent) self.network = network self._qeconfig = qeconfig self._serverListModel = None self._height = network.get_local_height() # init here, update event can take a while self.register_callbacks() self._qeconfig.useGossipChanged.connect(self.on_gossip_setting_changed) @event_listener def on_event_network_updated(self, *args): self.networkUpdated.emit() @event_listener def on_event_blockchain_updated(self): if self._height != self.network.get_local_height(): self._height = self.network.get_local_height() self._logger.debug('new height: %d' % self._height) self.heightChanged.emit(self._height) self.blockchainUpdated.emit() @event_listener def on_event_default_server_changed(self, *args): self.defaultServerChanged.emit() @event_listener def on_event_proxy_set(self, *args): self._logger.debug('proxy set') self.proxySet.emit() self.proxyTorChanged.emit() @event_listener def on_event_status(self, *args): self._logger.debug('status updated: %s' % self.network.connection_status) if self._status != self.network.connection_status: self._status = self.network.connection_status self.statusChanged.emit() chains = len(self.network.get_blockchains()) if chains != self._chaintips: self._logger.debug('chain tips # changed: %d', chains) self._chaintips = chains self.chaintipsChanged.emit() server_lag = self.network.get_local_height() - self.network.get_server_height() if self._islagging ^ (server_lag > 1): self._logger.debug('lagging changed: %s', str(server_lag > 1)) self._islagging = server_lag > 1 self.isLaggingChanged.emit() @event_listener def on_event_fee_histogram(self, histogram): self._logger.debug('fee histogram updated') self._fee_histogram = histogram if histogram else [] self.feeHistogramUpdated.emit() @event_listener def on_event_channel_db(self, num_nodes, num_channels, num_policies): self._logger.debug(f'channel_db: {num_nodes} nodes, {num_channels} channels, {num_policies} policies') self._gossipDbNodes = num_nodes self._gossipDbChannels = num_channels self._gossipDbPolicies = num_policies self.gossipUpdated.emit() @event_listener def on_event_gossip_peers(self, num_peers): self._logger.debug(f'gossip peers {num_peers}') self._gossipPeers = num_peers self.gossipUpdated.emit() @event_listener def on_event_unknown_channels(self, unknown): if unknown == 0 and self._gossipUnknownChannels == 0: # TODO: backend sends a lot of unknown=0 events return self._logger.debug(f'unknown channels {unknown}') self._gossipUnknownChannels = unknown self.gossipUpdated.emit() #self.lightning_gossip_num_queries = unknown def on_gossip_setting_changed(self): if not self.network: return if self._qeconfig.useGossip: self.network.start_gossip() else: self.network.run_from_another_thread(self.network.stop_gossip()) @pyqtProperty(int, notify=heightChanged) def height(self): return self._height @pyqtProperty(str, notify=defaultServerChanged) def server(self): return str(self.network.get_parameters().server) @server.setter def server(self, server): net_params = self.network.get_parameters() try: server = ServerAddr.from_str_with_inference(server) if not server: raise Exception("failed to parse") except Exception: return net_params = net_params._replace(server=server, auto_connect=self._qeconfig.autoConnect, oneserver=not self._qeconfig.autoConnect) self.network.run_from_another_thread(self.network.set_parameters(net_params)) @pyqtProperty(str, notify=statusChanged) def status(self): return self._status @pyqtProperty(int, notify=chaintipsChanged) def chaintips(self): return self._chaintips @pyqtProperty(bool, notify=isLaggingChanged) def isLagging(self): return self._islagging @pyqtProperty(bool, notify=dataChanged) def isTestNet(self): return constants.net.TESTNET @pyqtProperty(str, notify=dataChanged) def networkName(self): return constants.net.__name__.replace('Bitcoin','') @pyqtProperty('QVariantMap', notify=proxyChanged) def proxy(self): net_params = self.network.get_parameters() return net_params.proxy if net_params.proxy else {} @proxy.setter def proxy(self, proxy_settings): net_params = self.network.get_parameters() if not proxy_settings['enabled']: proxy_settings = None net_params = net_params._replace(proxy=proxy_settings) self.network.run_from_another_thread(self.network.set_parameters(net_params)) self.proxyChanged.emit() proxyTorChanged = pyqtSignal() @pyqtProperty(bool, notify=proxyTorChanged) def isProxyTor(self): return self.network.tor_proxy @pyqtProperty('QVariant', notify=feeHistogramUpdated) def feeHistogram(self): return self._fee_histogram @pyqtProperty('QVariantMap', notify=gossipUpdated) def gossipInfo(self): return { 'peers': self._gossipPeers, 'unknown_channels': self._gossipUnknownChannels, 'db_nodes': self._gossipDbNodes, 'db_channels': self._gossipDbChannels , 'db_policies': self._gossipDbPolicies } serverListModelChanged = pyqtSignal() @pyqtProperty(QEServerListModel, notify=serverListModelChanged) def serverListModel(self): if self._serverListModel is None: self._serverListModel = QEServerListModel(self.network) return self._serverListModel