From 702a2919ec442fe7eb74cfaf8789bdeeda796c85 Mon Sep 17 00:00:00 2001 From: Janus Date: Thu, 15 Mar 2018 17:38:02 +0100 Subject: [PATCH] lightning: complete moving of lightning objects, acquire net/wallet lock while answering lightning requests --- .gitignore | 7 +++++++ electrum/commands.py | 20 ++++++++++++++++++++ electrum/gui/qt/__init__.py | 6 ++++++ electrum/gui/qt/main_window.py | 8 ++++++++ gui/kivy/uix/dialogs/lightning_channels.py | 6 +++--- gui/kivy/uix/dialogs/lightning_payer.py | 8 ++++---- lib/lightning.py | 18 +++++++++++------- 7 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 484619da..e8d5ebb8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,10 @@ bin/ # tox files .cache/ .coverage + +# kivy +gui/kivy/theming/light-0.png +gui/kivy/theming/light.atlas + +# lightning +lib/ln/ diff --git a/electrum/commands.py b/electrum/commands.py index a95d74ed..0d4d4d36 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -23,6 +23,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import queue import sys import datetime import copy @@ -41,6 +42,7 @@ from .i18n import _ from .transaction import Transaction, multisig_script from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED from .plugin import run_hook +from .import lightning known_commands = {} @@ -680,6 +682,22 @@ class Commands: # for the python console return sorted(known_commands.keys()) + @command("wn") + def lightning(self, lcmd, lightningargs=None): + q = queue.Queue() + class FakeQtSignal: + def emit(self, data): + q.put(data) + class MyConsole: + new_lightning_result = FakeQtSignal() + self.wallet.network.lightningrpc.setConsole(MyConsole()) + if lightningargs: + lightningargs = json_decode(lightningargs) + else: + lightningargs = [] + lightning.lightningCall(self.wallet.network.lightningrpc, lcmd)(*lightningargs) + return q.get(block=True, timeout=600) + param_descriptions = { 'privkey': 'Private key. Type \'?\' to get a prompt.', 'destination': 'Bitcoin address, contact or alias', @@ -734,6 +752,8 @@ command_options = { 'year': (None, "Show history for a given year"), 'fee_method': (None, "Fee estimation method to use"), 'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position") + 'lightningargs':(None, "Arguments for an lncli subcommand, encoded as a JS + ON array"), } diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 7fa7f4e5..3cc9a186 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -51,6 +51,7 @@ from electrum.util import (UserCancelled, print_error, from .installwizard import InstallWizard +from electrum.lightning import LightningUI try: from . import icons_rc @@ -133,6 +134,11 @@ class ElectrumGui: # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) + self.lightning = LightningUI(self.set_console_and_return_lightning) + + def set_console_and_return_lightning(self): + self.windows[0].wallet.network.lightningrpc.setConsole(self.windows[0].console) + return self.windows[0].wallet.network.lightningrpc def build_tray_menu(self): # Avoid immediate GC of old menu when window closed via its action diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index ac2c5282..9b68313b 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -60,6 +60,7 @@ from .transaction_dialog import show_transaction from .fee_slider import FeeSlider from .util import * from .installwizard import WIF_HELP_TEXT +from .lightning_invoice_list import LightningInvoiceList class StatusBarButton(QPushButton): @@ -140,6 +141,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): tabs.addTab(self.create_history_tab(), QIcon(":icons/tab_history.png"), _('History')) tabs.addTab(self.send_tab, QIcon(":icons/tab_send.png"), _('Send')) tabs.addTab(self.receive_tab, QIcon(":icons/tab_receive.png"), _('Receive')) + self.lightning_invoices_tab = self.create_lightning_invoices_tab(wallet) + tabs.addTab(self.lightning_invoices_tab, _("Lightning Invoices")) def add_optional_tab(tabs, tab, icon, description, name): tab.tab_icon = icon @@ -764,6 +767,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.invoice_list.update() self.update_completions() + def create_lightning_invoices_tab(self, wallet): + self.lightning_invoice_list = LightningInvoiceList(self, wallet.network.lightningworker, wallet.network.lightningrpc) + return self.lightning_invoice_list + def create_history_tab(self): from .history_list import HistoryList self.history_list = l = HistoryList(self) @@ -1914,6 +1921,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'plugins' : self.gui_object.plugins, + 'lightning' : self.gui_object.lightning, 'window': self}) console.updateNamespace({'util' : util, 'bitcoin':bitcoin}) diff --git a/gui/kivy/uix/dialogs/lightning_channels.py b/gui/kivy/uix/dialogs/lightning_channels.py index dced1c9c..f9383ed1 100644 --- a/gui/kivy/uix/dialogs/lightning_channels.py +++ b/gui/kivy/uix/dialogs/lightning_channels.py @@ -31,12 +31,12 @@ class LightningChannelsDialog(Factory.Popup): super(LightningChannelsDialog, self).open(*args, **kwargs) for i in self.clocks: i.cancel() self.clocks.append(Clock.schedule_interval(self.fetch_channels, 10)) - self.app.wallet.lightning.subscribe(self.rpc_result_handler) + self.app.wallet.network.lightningrpc.subscribe(self.rpc_result_handler) def dismiss(self, *args, **kwargs): super(LightningChannelsDialog, self).dismiss(*args, **kwargs) - self.app.wallet.lightning.clearSubscribers() + self.app.wallet.network.lightningrpc.clearSubscribers() def fetch_channels(self, dw): - lightning.lightningCall(self.app.wallet.lightning, "listchannels")() + lightning.lightningCall(self.app.wallet.network.lightningrpc, "listchannels")() def rpc_result_handler(self, res): if isinstance(res, Exception): raise res diff --git a/gui/kivy/uix/dialogs/lightning_payer.py b/gui/kivy/uix/dialogs/lightning_payer.py index 1235aa85..e69dd977 100644 --- a/gui/kivy/uix/dialogs/lightning_payer.py +++ b/gui/kivy/uix/dialogs/lightning_payer.py @@ -47,11 +47,11 @@ class LightningPayerDialog(Factory.Popup): def emit(self2, data): self.app.show_info(data) class MyConsole: - newResult = FakeQtSignal() - self.app.wallet.lightning.setConsole(MyConsole()) + new_lightning_result = FakeQtSignal() + self.app.wallet.network.lightningrpc.setConsole(MyConsole()) def dismiss(self, *args, **kwargs): super(LightningPayerDialog, self).dismiss(*args, **kwargs) - self.app.wallet.lightning.setConsole(None) + self.app.wallet.network.lightningrpc.setConsole(None) def do_paste_sample(self): self.invoice_data = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w" def do_paste(self): @@ -63,6 +63,6 @@ class LightningPayerDialog(Factory.Popup): def do_clear(self): self.invoice_data = "" def do_pay(self): - lightning.lightningCall(self.app.wallet.lightning, "sendpayment")("--pay_req=" + self.invoice_data) + lightning.lightningCall(self.app.wallet.network.lightningrpc, "sendpayment")("--pay_req=" + self.invoice_data) def on_lightning_qr(self): self.app.show_info("Lightning Invoice QR scanning not implemented") #TODO diff --git a/lib/lightning.py b/lib/lightning.py index 8606d663..4e239042 100644 --- a/lib/lightning.py +++ b/lib/lightning.py @@ -626,7 +626,7 @@ class LightningRPC: traceback.print_exc() for i in self.subscribers: applyMethodName(i)(e) if self.console: - self.console.newResult.emit(json.dumps(toprint, indent=4)) + self.console.new_lightning_result.emit(json.dumps(toprint, indent=4)) threading.Thread(target=lightningRpcNetworkRequestThreadTarget, args=(qitem, )).start() def setConsole(self, console): self.console = console @@ -686,7 +686,9 @@ class LightningWorker: NETWORK = self.network() CONFIG = self.config() + netAndWalLock.acquire() synced, local, server = isSynced() + netAndWalLock.release() if not synced: await asyncio.sleep(5) continue @@ -702,14 +704,14 @@ class LightningWorker: writer.write(b"MAGIC") writer.write(privateKeyHash[:6]) await asyncio.wait_for(writer.drain(), 5) - while is_running(): - obj = await readJson(reader, is_running) + while True: + obj = await readJson(reader) if not obj: continue if "id" not in obj: print("Invoice update?", obj) for i in self.subscribers: i(obj) continue - await asyncio.wait_for(readReqAndReply(obj, writer), 10) + await asyncio.wait_for(readReqAndReply(obj, writer, netAndWalLock), 10) except: traceback.print_exc() await asyncio.sleep(5) @@ -717,9 +719,9 @@ class LightningWorker: def subscribe(self, notifyFunction): self.subscribers.append(functools.partial(notifyFunction, "LightningWorker")) -async def readJson(reader, is_running): +async def readJson(reader): data = b"" - while is_running(): + while True: newlines = sum(1 if x == b"\n"[0] else 0 for x in data) if newlines > 1: print("Too many newlines in Electrum/lightning.py!", data) try: @@ -731,7 +733,7 @@ async def readJson(reader, is_running): except TimeoutError: continue -async def readReqAndReply(obj, writer): +async def readReqAndReply(obj, writer, netAndWalLock): methods = [ # SecretKeyRing DerivePrivKey, @@ -760,10 +762,12 @@ async def readReqAndReply(obj, writer): if method.__name__ == obj["method"]: params = obj["params"][0] print("calling method", obj["method"], "with", params) + netAndWalLock.acquire() if asyncio.iscoroutinefunction(method): result = await method(params) else: result = method(params) + netAndWalLock.release() found = True break except BaseException as e: