URI: 
       tremote watchtower: initial commit - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 680b129b4a92f619e6be85ce75901aef291eeff8
   DIR parent 94a10e6307c25499f4fd556987f8d4d0c2e14b53
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Fri, 12 Oct 2018 14:53:22 +0200
       
       remote watchtower: initial commit
       
       Diffstat:
         M electrum/daemon.py                  |      15 ++++++++++++---
         M electrum/jsonrpc.py                 |      37 ++++++++++++++++++++++++++-----
         M electrum/lnwatcher.py               |      24 +++++++++++++++++++++++-
       
       3 files changed, 67 insertions(+), 9 deletions(-)
       ---
   DIR diff --git a/electrum/daemon.py b/electrum/daemon.py
       t@@ -33,7 +33,7 @@ from typing import Dict, Optional, Tuple
        
        import jsonrpclib
        
       -from .jsonrpc import VerifyingJSONRPCServer
       +from .jsonrpc import SimpleJSONRPCServer, PasswordProtectedJSONRPCServer
        from .version import ELECTRUM_VERSION
        from .network import Network
        from .util import (json_decode, DaemonThread, to_string,
       t@@ -147,6 +147,8 @@ class Daemon(DaemonThread):
                self.server = None
                if listen_jsonrpc:
                    self.init_server(config, fd)
       +        if config.get('watchtower_host'):
       +            self.init_watchtower()
                self.start()
        
            def init_server(self, config: SimpleConfig, fd):
       t@@ -154,8 +156,9 @@ class Daemon(DaemonThread):
                port = config.get('rpcport', 0)
                rpc_user, rpc_password = get_rpc_credentials(config)
                try:
       -            server = VerifyingJSONRPCServer((host, port), logRequests=False,
       -                                            rpc_user=rpc_user, rpc_password=rpc_password)
       +            server = PasswordProtectedJSONRPCServer(
       +                (host, port), logRequests=False,
       +                rpc_user=rpc_user, rpc_password=rpc_password)
                except Exception as e:
                    self.logger.error(f'cannot initialize RPC server on host {host}: {repr(e)}')
                    self.server = None
       t@@ -173,6 +176,12 @@ class Daemon(DaemonThread):
                    server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
                server.register_function(self.run_cmdline, 'run_cmdline')
        
       +    def init_watchtower(self):
       +        host = self.config.get('watchtower_host')
       +        port = self.config.get('watchtower_port', 12345)
       +        server = SimpleJSONRPCServer((host, port), logRequests=False)
       +        server.register_function(self.network.lnwatcher, 'add_sweep_tx')
       +
            def ping(self):
                return True
        
   DIR diff --git a/electrum/jsonrpc.py b/electrum/jsonrpc.py
       t@@ -1,3 +1,4 @@
       +
        #!/usr/bin/env python3
        #
        # Electrum - lightweight Bitcoin client
       t@@ -48,12 +49,10 @@ class RPCAuthUnsupportedType(Exception):
        
        
        # based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke
       -class VerifyingJSONRPCServer(SimpleJSONRPCServer, Logger):
       +class AuthenticatedJSONRPCServer(SimpleJSONRPCServer, Logger):
        
       -    def __init__(self, *args, rpc_user, rpc_password, **kargs):
       +    def __init__(self, *args, **kargs):
                Logger.__init__(self)
       -        self.rpc_user = rpc_user
       -        self.rpc_password = rpc_password
        
                class VerifyingRequestHandler(SimpleJSONRPCRequestHandler):
                    def parse_request(myself):
       t@@ -73,11 +72,21 @@ class VerifyingJSONRPCServer(SimpleJSONRPCServer, Logger):
                                self.logger.exception('')
                                myself.send_error(500, repr(e))
                        return False
       -
                SimpleJSONRPCServer.__init__(
                    self, requestHandler=VerifyingRequestHandler, *args, **kargs)
        
            def authenticate(self, headers):
       +        raise Exception('undefined')
       +
       +
       +class PasswordProtectedJSONRPCServer(AuthenticatedJSONRPCServer):
       +
       +    def __init__(self, *args, rpc_user, rpc_password, **kargs):
       +        self.rpc_user = rpc_user
       +        self.rpc_password = rpc_password
       +        AuthenticatedJSONRPCServer.__init__(self, *args, **kargs)
       +
       +    def authenticate(self, headers):
                if self.rpc_password == '':
                    # RPC authentication is disabled
                    return
       t@@ -97,3 +106,21 @@ class VerifyingJSONRPCServer(SimpleJSONRPCServer, Logger):
                        and util.constant_time_compare(password, self.rpc_password)):
                    time.sleep(0.050)
                    raise RPCAuthCredentialsInvalid()
       +
       +
       +class AccountsJSONRPCServer(AuthenticatedJSONRPCServer):
       +    """ user accounts """
       +
       +    def __init__(self, *args, **kargs):
       +        self.users = {}
       +        AuthenticatedJSONRPCServer.__init__(self, *args, **kargs)
       +        self.register_function(self.add_user, 'add_user')
       +
       +    def authenticate(self, headers):
       +        # todo: verify signature
       +        return
       +
       +    def add_user(self, pubkey):
       +        user_id = len(self.users)
       +        self.users[user_id] = pubkey
       +        return user_id
   DIR diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
       t@@ -2,6 +2,8 @@ import threading
        from typing import NamedTuple, Iterable
        import os
        from collections import defaultdict
       +import asyncio
       +import jsonrpclib
        
        from .util import PrintError, bh2u, bfh, NoDynamicFeeEstimates, aiosafe
        from .lnutil import EncumberedTransaction, Outpoint
       t@@ -20,7 +22,7 @@ class LNWatcher(PrintError):
        
            def __init__(self, network):
                self.network = network
       -
       +        self.config = network.config
                path = os.path.join(network.config.path, "watcher_db")
                storage = WalletStorage(path)
                self.addr_sync = AddressSynchronizer(storage)
       t@@ -41,6 +43,25 @@ class LNWatcher(PrintError):
        
                self.network.register_callback(self.on_network_update,
                                               ['network_updated', 'blockchain_updated', 'verified', 'wallet_updated'])
       +        # remote watchtower
       +        watchtower_url = self.config.get('watchtower_url')
       +        self.watchtower = jsonrpclib.Server(watchtower_url) if watchtower_url else None
       +        self.watchtower_queue = asyncio.Queue()
       +        asyncio.run_coroutine_threadsafe(self.watchtower_task(), self.network.asyncio_loop)
       +
       +    def with_watchtower(func):
       +        def wrapper(self, *args, **kwargs):
       +            if self.watchtower:
       +                self.watchtower_queue.put_nowait((func.__name__, args, kwargs))
       +            return func(self, *args, **kwargs)
       +        return wrapper
       +
       +    async def watchtower_task(self):
       +        while True:
       +            name, args, kwargs = await self.watchtower_queue.get()
       +            self.print_error('sending to watchtower', name, args)
       +            func = getattr(self.watchtower, name)
       +            func(*args, **kwargs)
        
            def write_to_disk(self):
                # FIXME: json => every update takes linear instead of constant disk write
       t@@ -151,6 +172,7 @@ class LNWatcher(PrintError):
                                             .format(num_conf, e_tx.csv_delay, funding_outpoint, ctx.txid()))
                return keep_watching_this
        
       +    @with_watchtower
            def add_sweep_tx(self, funding_outpoint: str, ctx_txid: str, encumbered_sweeptx: EncumberedTransaction):
                if encumbered_sweeptx is None:
                    return