tlightning: complete moving of lightning objects, acquire net/wallet lock while answering lightning requests - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit d84eab041872d02b1b6ab37d3470a5ac33d52abd DIR parent 98f6f67c6bec987624b00a651a2af78aa4ad975b HTML Author: Janus <ysangkok@gmail.com> Date: Thu, 15 Mar 2018 17:38:02 +0100 lightning: complete moving of lightning objects, acquire net/wallet lock while answering lightning requests Diffstat: M electrum/commands.py | 19 +++++++++++++++++++ M electrum/gui/qt/__init__.py | 6 ++++++ M electrum/gui/qt/main_window.py | 8 ++++++++ M gui/kivy/uix/dialogs/lightning_cha… | 6 +++--- M gui/kivy/uix/dialogs/lightning_pay… | 8 ++++---- M lib/lightning.py | 18 +++++++++++------- 6 files changed, 51 insertions(+), 14 deletions(-) --- DIR diff --git a/electrum/commands.py b/electrum/commands.py t@@ -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 t@@ -45,6 +46,7 @@ from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED from .synchronizer import Notifier from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text from .address_synchronizer import TX_HEIGHT_LOCAL +from .import lightning if TYPE_CHECKING: from .network import Network t@@ -761,6 +763,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 t@@ -830,6 +848,7 @@ command_options = { 'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position"), 'from_height': (None, "Only show transactions that confirmed after given block height"), 'to_height': (None, "Only show transactions that confirmed before given block height"), + 'lightningargs':(None, "Arguments for an lncli subcommand, encoded as a JSON array"), } DIR diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py t@@ -52,6 +52,7 @@ from electrum.logging import Logger from .installwizard import InstallWizard, WalletAlreadyOpenInMemory +from electrum.lightning import LightningUI from .util import get_default_language, read_QIcon, ColorScheme, custom_message_box from .main_window import ElectrumWindow t@@ -139,6 +140,11 @@ class ElectrumGui(Logger): # 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 DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py t@@ -88,6 +88,7 @@ from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialo from .installwizard import WIF_HELP_TEXT from .history_list import HistoryList, HistoryModel from .update_checker import UpdateCheck, UpdateCheckThread +from .lightning_invoice_list import LightningInvoiceList class StatusBarButton(QPushButton): t@@ -174,6 +175,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): tabs.addTab(self.create_history_tab(), read_QIcon("tab_history.png"), _('History')) tabs.addTab(self.send_tab, read_QIcon("tab_send.png"), _('Send')) tabs.addTab(self.receive_tab, read_QIcon("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 t@@ -873,6 +876,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): 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): self.history_model = HistoryModel(self) self.history_list = l = HistoryList(self, self.history_model) t@@ -2076,6 +2083,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): 'wallet': self.wallet, 'network': self.network, 'plugins': self.gui_object.plugins, + 'lightning': self.gui_object.lightning, 'window': self, 'config': self.config, 'electrum': electrum, DIR diff --git a/gui/kivy/uix/dialogs/lightning_channels.py b/gui/kivy/uix/dialogs/lightning_channels.py t@@ -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 DIR diff --git a/gui/kivy/uix/dialogs/lightning_payer.py b/gui/kivy/uix/dialogs/lightning_payer.py t@@ -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): t@@ -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 DIR diff --git a/lib/lightning.py b/lib/lightning.py t@@ -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 t@@ -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 t@@ -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) t@@ -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: t@@ -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, t@@ -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: