From df910ec1de5063eac871f754a6af9ee9f016a634 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 --- electrum/commands.py | 21 ++++++++++++++++++++- 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 +++++++++++------- 6 files changed, 52 insertions(+), 15 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 40a8142cf..422849b79 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 @@ -46,6 +47,7 @@ from .storage import WalletStorage from . import keystore from .wallet import Wallet, Imported_Wallet, Abstract_Wallet from .mnemonic import Mnemonic +from .import lightning if TYPE_CHECKING: from .network import Network @@ -744,6 +746,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) + def eval_bool(x: str) -> bool: if x == 'false': return False @@ -809,7 +827,8 @@ command_options = { 'show_fiat': (None, "Show fiat value of transactions"), '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") + 'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position"), + 'lightningargs':(None, "Arguments for an lncli subcommand, encoded as a JSON array"), } diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 9e9612244..37d4ecbdf 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -47,6 +47,7 @@ from electrum.util import (UserCancelled, PrintError, from .installwizard import InstallWizard +from electrum.lightning import LightningUI try: from . import icons_rc @@ -129,6 +130,11 @@ class ElectrumGui(PrintError): # 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 2d214dce7..779dc3266 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -73,6 +73,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): @@ -159,6 +160,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 @@ -801,6 +804,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) @@ -1958,6 +1965,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): 'wallet': self.wallet, 'network': self.network, 'plugins': self.gui_object.plugins, + 'lightning': self.gui_object.lightning, 'window': self, 'config': self.config, 'electrum': electrum, diff --git a/gui/kivy/uix/dialogs/lightning_channels.py b/gui/kivy/uix/dialogs/lightning_channels.py index dced1c9c2..f9383ed16 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 1235aa853..e69dd9778 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 8606d6631..4e2390427 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: