From a0a15e2223f9701ddb8d8226efba6da47be40003 Mon Sep 17 00:00:00 2001
From: Janus <ysangkok@gmail.com>
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                       | 19 +++++++++++++++++++
 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, 51 insertions(+), 14 deletions(-)

diff --git a/electrum/commands.py b/electrum/commands.py
index 4defc0523..032ce8daf 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
@@ -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
@@ -765,6 +767,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
@@ -834,6 +852,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"),
 }
 
 
diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py
index 0fa9095b1..c5307a9fc 100644
--- a/electrum/gui/qt/__init__.py
+++ b/electrum/gui/qt/__init__.py
@@ -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
@@ -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
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index 87e3dc266..4ad53b328 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -87,6 +87,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):
@@ -176,6 +177,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
@@ -875,6 +878,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)
@@ -2071,6 +2078,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,
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: