226 lines
8.1 KiB
Python
226 lines
8.1 KiB
Python
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
|
|
|
from electrum.logging import get_logger
|
|
from electrum import constants
|
|
from electrum.interface import ServerAddr
|
|
from electrum.simple_config import FEERATE_DEFAULT_RELAY
|
|
|
|
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)}')
|
|
self.update_histogram(histogram)
|
|
|
|
def update_histogram(self, histogram):
|
|
if not histogram:
|
|
histogram = [[FEERATE_DEFAULT_RELAY/1000,1]]
|
|
# cap the histogram to a limited number of megabytes
|
|
bytes_limit=10*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(FEERATE_DEFAULT_RELAY/1000, item[0]), slot]) # clamped to [FEERATE_DEFAULT_RELAY/1000,inf]
|
|
|
|
# add clamping attributes for the GUI
|
|
self._fee_histogram = {
|
|
'histogram': capped_histogram,
|
|
'total': bytes_current,
|
|
'min_fee': capped_histogram[-1][0] if capped_histogram else FEERATE_DEFAULT_RELAY/1000,
|
|
'max_fee': capped_histogram[0][0] if capped_histogram else FEERATE_DEFAULT_RELAY/1000
|
|
}
|
|
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
|