commit 19e60f00bbb1571ee266e51eb1a246c17c451a16
parent 7bb4ea150f8cbc9da3edc589c94883c195a72d9a
Author: ThomasV <thomasv@electrum.org>
Date: Wed, 30 Jan 2019 17:24:43 +0100
add watchtower_window
Diffstat:
5 files changed, 121 insertions(+), 8 deletions(-)
diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py
@@ -111,6 +111,7 @@ class ElectrumGui(Logger):
self.timer.setInterval(500) # msec
self.nd = None
+ self.watchtower_window = None
self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
self._num_wizards_in_progress = 0
self._num_wizards_lock = threading.Lock()
@@ -149,8 +150,12 @@ class ElectrumGui(Logger):
else:
m = self.tray.contextMenu()
m.clear()
+ if self.watchtower_window:
+ submenu = m.addMenu(_("watchtower"))
+ submenu.addAction(_("Show/Hide"), self.watchtower_window.show_or_hide)
for window in self.windows:
- submenu = m.addMenu(window.wallet.basename())
+ name = window.wallet.basename()
+ submenu = m.addMenu(name)
submenu.addAction(_("Show/Hide"), window.show_or_hide)
submenu.addAction(_("Close"), window.close)
m.addAction(_("Dark/Light"), self.toggle_tray_icon)
@@ -180,11 +185,20 @@ class ElectrumGui(Logger):
def close(self):
for window in self.windows:
window.close()
+ if self.watchtower_window:
+ self.watchtower_window.close()
def new_window(self, path, uri=None):
# Use a signal as can be called from daemon thread
self.app.new_window_signal.emit(path, uri)
+ def create_watchtower_window(self):
+ from .watchtower_window import WatchTowerWindow
+ self.watchtower_window = WatchTowerWindow(self)
+
+ def show_watchtower_dialog(self, parent):
+ self.watchtower_window.bring_to_top()
+
def show_network_dialog(self, parent):
if not self.daemon.network:
parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
@@ -326,6 +340,9 @@ class ElectrumGui(Logger):
self.logger.exception('')
return
self.timer.start()
+ # todo: create this only if channels need it
+ self.create_watchtower_window()
+
self.config.open_last_wallet()
path = self.config.get_wallet_path()
if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
@@ -338,7 +355,7 @@ class ElectrumGui(Logger):
return
# check if a wizard is in progress
with self._num_wizards_lock:
- if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
+ if self._num_wizards_in_progress > 0 or len(self.windows) > 0 or self.watchtower_window:
return
self.app.quit()
self.app.setQuitOnLastWindowClosed(False) # so _we_ can decide whether to quit
diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
@@ -9,7 +9,7 @@ from electrum.i18n import _
from electrum.lnchan import Channel
from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError
-from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton
+from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel
from .amountedit import BTCAmountEdit
from .channel_details import ChannelDetailsDialog
@@ -100,7 +100,6 @@ class ChannelsList(MyTreeView):
h = QHBoxLayout()
h.addWidget(self.status)
h.addStretch()
- h.addWidget(EnterButton(_('Statistics'), self.statistics_dialog))
h.addWidget(EnterButton(_('Open Channel'), self.new_channel_dialog))
return h
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
@@ -627,6 +627,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
# Settings / Preferences are all reserved keywords in macOS using this as work around
tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
tools_menu.addAction(_("&Network"), lambda: self.gui_object.show_network_dialog(self))
+ tools_menu.addAction(_("&Watchtower"), lambda: self.gui_object.show_watchtower_dialog(self))
tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
tools_menu.addSeparator()
tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
diff --git a/electrum/gui/qt/watchtower_window.py b/electrum/gui/qt/watchtower_window.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import socket
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import *
+import PyQt5.QtCore as QtCore
+
+from electrum.i18n import _
+from .util import *
+
+help_wt = _("""A watchtower is a process that monitors your channels while you are offline, and prevents the other party from stealing funds in the channel.""")
+help_local = _("""Electrum runs a watchtower on your computer. This process will persist after you close your wallet. It will not persist if you exit Electrum from the tray menu""")
+help_remote = _("""To run a remote watchtower, start an electrum daemon on a computer that is always connected to the Internet, and set 'watchtower_host' and 'watchtower_port' in its config""")
+
+class WatchTowerWindow(QDialog):
+
+ def __init__(self, gui_object):
+ QDialog.__init__(self)
+ self.gui_object = gui_object
+ self.lnwatcher = gui_object.daemon.network.lnwatcher
+ self.wallet = self.lnwatcher
+ self.config = gui_object.config
+ self.setWindowTitle(_('Watchtower'))
+ self.setMinimumSize(600, 20)
+ vbox = QVBoxLayout(self)
+ watchtower_url = self.config.get('watchtower_url')
+ self.watchtower_e = QLineEdit(watchtower_url)
+ self.channel_list = QTreeWidget(self)
+ self.channel_list.setHeaderLabels([_('Node ID'), _('Amount')])
+
+ vbox.addWidget(WWLabel(help_wt))
+ vbox.addStretch(1)
+ vbox.addWidget(HelpLabel(_('Local Watchtower') + ':', help_local))
+ vbox.addWidget(self.channel_list)
+ vbox.addStretch(1)
+ g = QGridLayout()
+ g.addWidget(HelpLabel(_('Remote Watchtower') + ':', help_remote), 1, 0)
+ g.addWidget(self.watchtower_e, 1, 1)
+ vbox.addLayout(g)
+ vbox.addStretch(1)
+ b = QPushButton(_('Close'))
+ b.clicked.connect(self.on_close)
+ vbox.addLayout(Buttons(b))
+
+ def update(self):
+ pass
+
+ def on_close(self):
+ url = self.watchtower_e.text()
+ if url:
+ self.lnwatcher.set_remote_watchtower()
+ self.hide()
+
+ def is_hidden(self):
+ return self.isMinimized() or self.isHidden()
+
+ def show_or_hide(self):
+ if self.is_hidden():
+ self.bring_to_top()
+ else:
+ self.hide()
+
+ def bring_to_top(self):
+ self.show()
+ self.raise_()
+
+ def closeEvent(self, event):
+ self.gui_object.watchtower_window = None
+ event.accept()
diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
@@ -58,14 +58,16 @@ class LNWatcher(AddressSynchronizer):
self.network.register_callback(self.on_network_update,
['network_updated', 'blockchain_updated', 'verified', 'wallet_updated'])
- # remote watchtower
- watchtower_url = self.config.get('watchtower_url')
- self.watchtower = jsonrpclib.Server(watchtower_url) if watchtower_url else None
- self.watchtower_queue = asyncio.Queue()
+ self.set_remote_watchtower()
# this maps funding_outpoints to ListenerItems, which have an event for when the watcher is done,
# and a queue for seeing which txs are being published
self.tx_progress = {} # type: Dict[str, ListenerItem]
+ def set_remote_watchtower(self):
+ watchtower_url = self.config.get('watchtower_url')
+ self.watchtower = jsonrpclib.Server(watchtower_url) if watchtower_url else None
+ self.watchtower_queue = asyncio.Queue()
+
def with_watchtower(func):
def wrapper(self, *args, **kwargs):
if self.watchtower: