URI: 
       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: