You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
7.9 KiB
224 lines
7.9 KiB
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(f'fee histogram updated: {repr(histogram)}') |
|
if histogram is None: |
|
histogram = [] |
|
self.update_histogram(histogram) |
|
|
|
def update_histogram(self, histogram): |
|
# cap the histogram to a limited number of megabytes |
|
bytes_limit=25*1000*1000 |
|
bytes_current = 0 |
|
capped_histogram = [] |
|
for item in sorted(histogram, key=lambda x: x[0], reverse=True): |
|
if bytes_current >= bytes_limit: |
|
break |
|
slot = min(item[1], bytes_limit-bytes_current) |
|
bytes_current += slot |
|
capped_histogram.append([max(1, item[0]), slot]) # clamped to [1,inf] |
|
|
|
# add clamping attributes for the GUI |
|
self._fee_histogram = { |
|
'histogram': capped_histogram, |
|
'total': bytes_current, |
|
'min_fee': capped_histogram[-1][0], |
|
'max_fee': capped_histogram[0][0] |
|
} |
|
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
|
|
|