URI:
       tUse RPC call importmulti to import addresses - electrum-personal-server - Maximally lightweight electrum server for a single user
  HTML git clone https://git.parazyd.org/electrum-personal-server
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit c6b4b370e7ea0bb3643bc679dd1a6dc4625da48e
   DIR parent a63d0545013b41351fa77d09f1849cecc77db5bb
  HTML Author: chris-belcher <chris-belcher@users.noreply.github.com>
       Date:   Wed,  6 May 2020 17:41:55 +0100
       
       Use RPC call importmulti to import addresses
       
       Imports of wallets are now much faster because the EC calculations
       are done in optimized C code in Bitcoin Core. This does not apply
       tto the old-style-seed wallets, so they will still be slow.
       
       Diffstat:
         M electrumpersonalserver/server/__in… |       5 +++--
         M electrumpersonalserver/server/comm… |      34 ++++++++++++++-----------------
         M electrumpersonalserver/server/dete… |      44 +++++++++++++++++++++++++++++++
         M electrumpersonalserver/server/tran… |      31 ++-----------------------------
       
       4 files changed, 64 insertions(+), 50 deletions(-)
       ---
   DIR diff --git a/electrumpersonalserver/server/__init__.py b/electrumpersonalserver/server/__init__.py
       t@@ -21,12 +21,13 @@ from electrumpersonalserver.server.hashes import (
        )
        from electrumpersonalserver.server.transactionmonitor import (
            TransactionMonitor,
       -    import_addresses,
       -    ADDRESSES_LABEL,
        )
        from electrumpersonalserver.server.deterministicwallet import (
            parse_electrum_master_public_key,
            DeterministicWallet,
       +    DescriptorDeterministicWallet,
       +    import_addresses,
       +    ADDRESSES_LABEL,
        )
        from electrumpersonalserver.server.socks import (
            socksocket,
   DIR diff --git a/electrumpersonalserver/server/common.py b/electrumpersonalserver/server/common.py
       t@@ -161,13 +161,13 @@ def get_scriptpubkeys_to_monitor(rpc, config):
            st = time.time()
            try:
                imported_addresses = set(rpc.call("getaddressesbyaccount",
       -            [transactionmonitor.ADDRESSES_LABEL]))
       +            [deterministicwallet.ADDRESSES_LABEL]))
                logger.debug("using deprecated accounts interface")
            except JsonRpcError:
                #bitcoin core 0.17 deprecates accounts, replaced with labels
       -        if transactionmonitor.ADDRESSES_LABEL in rpc.call("listlabels", []):
       +        if deterministicwallet.ADDRESSES_LABEL in rpc.call("listlabels", []):
                    imported_addresses = set(rpc.call("getaddressesbylabel",
       -                [transactionmonitor.ADDRESSES_LABEL]).keys())
       +                [deterministicwallet.ADDRESSES_LABEL]).keys())
                else:
                    #no label, no addresses imported at all
                    imported_addresses = set()
       t@@ -188,8 +188,7 @@ def get_scriptpubkeys_to_monitor(rpc, config):
        
            #check whether these deterministic wallets have already been imported
            import_needed = False
       -    wallets_imported = 0
       -    addresses_to_import = []
       +    wallets_to_import = []
            TEST_ADDR_COUNT = 3
            logger.info("Displaying first " + str(TEST_ADDR_COUNT) + " addresses of " +
                "each master public key:")
       t@@ -203,11 +202,7 @@ def get_scriptpubkeys_to_monitor(rpc, config):
                    config.get("bitcoin-rpc", "initial_import_count")) - 1, count=1)
                if not set(first_addrs + last_addr).issubset(imported_addresses):
                    import_needed = True
       -            wallets_imported += 1
       -            for change in [0, 1]:
       -                addrs, spks = wal.get_addresses(change, 0,
       -                    int(config.get("bitcoin-rpc", "initial_import_count")))
       -                addresses_to_import.extend(addrs)
       +            wallets_to_import.append(wal)
            logger.info("Obtaining bitcoin addresses to monitor . . .")
            #check whether watch-only addresses have been imported
            watch_only_addresses = []
       t@@ -224,18 +219,17 @@ def get_scriptpubkeys_to_monitor(rpc, config):
            if len(deterministic_wallets) == 0 and len(watch_only_addresses) == 0:
                logger.error("No master public keys or watch-only addresses have " +
                    "been configured at all. Exiting..")
       -        #import = true and no addresses means exit
       -        return (True, [], None)
       +        #import = true and none other params means exit
       +        return (True, None, None)
        
            #if addresses need to be imported then return them
            if import_needed:
                #TODO minus imported_addresses
       -        logger.info("Importing " + str(wallets_imported) + " wallets and " +
       -            str(len(watch_only_addresses_to_import)) + " watch-only " +
       -            "addresses into the Bitcoin node")
       +        logger.info("Importing " + str(len(wallets_to_import))
       +            + " wallets and " + str(len(watch_only_addresses_to_import))
       +            + " watch-only addresses into the Bitcoin node")
                time.sleep(5)
       -        return (True, addresses_to_import + list(
       -            watch_only_addresses_to_import), None)
       +        return True, watch_only_addresses_to_import, wallets_to_import
        
            #test
            # importing one det wallet and no addrs, two det wallets and no addrs
       t@@ -423,10 +417,12 @@ def main():
            import_needed, relevant_spks_addrs, deterministic_wallets = \
                get_scriptpubkeys_to_monitor(rpc, config)
            if import_needed:
       -        if len(relevant_spks_addrs) == 0:
       +        if not relevant_spks_addrs and not deterministic_wallets:
                    #import = true and no addresses means exit
                    return
       -        transactionmonitor.import_addresses(rpc, relevant_spks_addrs)
       +        deterministicwallet.import_addresses(rpc, relevant_spks_addrs,
       +            deterministic_wallets, change_param=-1,
       +            count=int(config.get("bitcoin-rpc", "initial_import_count")))
                logger.info("Done.\nIf recovering a wallet which already has existing" +
                    " transactions, then\nrun the rescan script. If you're confident" +
                    " that the wallets are new\nand empty then there's no need to" +
   DIR diff --git a/electrumpersonalserver/server/deterministicwallet.py b/electrumpersonalserver/server/deterministicwallet.py
       t@@ -1,4 +1,6 @@
        
       +import logging
       +
        import electrumpersonalserver.bitcoin as btc
        from electrumpersonalserver.server.hashes import bh2u, hash_160, bfh, sha256,\
            address_to_script, script_to_address
       t@@ -9,6 +11,48 @@ from electrumpersonalserver.server.jsonrpc import JsonRpcError
        #and
        #https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
        
       +ADDRESSES_LABEL = "electrum-watchonly-addresses"
       +
       +def import_addresses(rpc, watchonly_addrs, wallets, change_param, count,
       +        logger=None):
       +    """
       +    change_param = 0 for receive, 1 for change, -1 for both
       +    """
       +    logger = logger if logger else logging.getLogger('ELECTRUMPERSONALSERVER')
       +    logger.debug("Importing " + str(len(watchonly_addrs)) + " watch-only "
       +        + "address[es] and " + str(len(wallets)) + " wallet[s] into label \""
       +        + ADDRESSES_LABEL + "\"")
       +
       +    watchonly_addr_param = [{"scriptPubKey": {"address": addr}, "label":
       +        ADDRESSES_LABEL, "watchonly": True, "timestamp": "now"}
       +        for addr in watchonly_addrs]
       +    rpc.call("importmulti", [watchonly_addr_param, {"rescan": False}])
       +
       +    for i, wal in enumerate(wallets):
       +        logger.info("Importing wallet " + str(i+1) + "/" + str(len(wallets)))
       +        if isinstance(wal, DescriptorDeterministicWallet):
       +            if change_param in (0, -1):
       +                #import receive addrs
       +                rpc.call("importmulti", [[{"desc": wal.descriptors[0], "range":
       +                    [0, count-1], "label": ADDRESSES_LABEL, "watchonly": True,
       +                    "timestamp": "now"}], {"rescan": False}])
       +            if change_param in (1, -1):
       +                #import change addrs
       +                rpc.call("importmulti", [[{"desc": wal.descriptors[1], "range":
       +                    [0, count-1], "label": ADDRESSES_LABEL, "watchonly": True,
       +                    "timestamp": "now"}], {"rescan": False}])
       +        else:
       +            #old-style-seed wallets
       +            logger.info("importing an old-style-seed wallet, will be slow...")
       +            for change in [0, 1]:
       +                addrs, spks = wal.get_addresses(change, 0, count)
       +                addr_param = [{"scriptPubKey": {"address": a}, "label":
       +                    ADDRESSES_LABEL, "watchonly": True, "timestamp": "now"}
       +                    for a in addrs]
       +                rpc.call("importmulti", [addr_param, {"rescan": False}])
       +    logger.debug("Importing done")
       +
       +
        def is_string_parsable_as_hex_int(s):
            try:
                int(s, 16)
   DIR diff --git a/electrumpersonalserver/server/transactionmonitor.py b/electrumpersonalserver/server/transactionmonitor.py
       t@@ -13,6 +13,7 @@ from electrumpersonalserver.server.hashes import (
            script_to_scripthash,
            script_to_address
        )
       +from electrumpersonalserver.server.deterministicwallet import import_addresses
        
        #internally this code uses scriptPubKeys, it only converts to bitcoin addresses
        # when importing to bitcoind or checking whether enough addresses have been
       t@@ -20,40 +21,12 @@ from electrumpersonalserver.server.hashes import (
        #the electrum protocol uses sha256(scriptpubkey) as a key for lookups
        # this code calls them scripthashes
        
       -#code will generate the first address from each deterministic wallet
       -# and check whether they have been imported into the bitcoin node
       -# if no then initial_import_count addresses will be imported, then exit
       -# if yes then initial_import_count addresses will be generated and extra
       -# addresses will be generated one-by-one, each time checking whether they have
       -# been imported into the bitcoin node
       -# when an address has been reached that has not been imported, that means
       -# we've reached the end, then rewind the deterministic wallet index by one
       -
        #when a transaction happens paying to an address from a deterministic wallet
        # lookup the position of that address, if its less than gap_limit then
        # import more addresses
        
       -ADDRESSES_LABEL = "electrum-watchonly-addresses"
        CONFIRMATIONS_SAFE_FROM_REORG = 100
        
       -def import_addresses(rpc, addrs, logger=None):
       -    logger = logger if logger else logging.getLogger('ELECTRUMPERSONALSERVER')
       -    logger.debug("importing addrs = " + str(addrs))
       -    logger.debug("into label \"" + ADDRESSES_LABEL + "\"")
       -    logger.info("Importing " + str(len(addrs)) + " addresses in total")
       -    addr_i = iter(addrs)
       -    notifications = 20
       -    for i in range(notifications):
       -        pc = int(100.0 * i / notifications)
       -        sys.stdout.write("[" + str(pc) + "%]... ")
       -        sys.stdout.flush()
       -        for j in range(int(len(addrs) / notifications)):
       -            rpc.call("importaddress", [next(addr_i), ADDRESSES_LABEL, False])
       -    for a in addr_i: #import the reminder of addresses
       -        rpc.call("importaddress", [a, ADDRESSES_LABEL, False])
       -    print("[100%]")
       -    logger.info("Importing done")
       -
        class TransactionMonitor(object):
            """
            Class which monitors the bitcoind wallet for new transactions
       t@@ -507,7 +480,7 @@ class TransactionMonitor(object):
                                        spk)] =  {'history': [], 'subscribed': False}
                                logger.debug("importing " + str(len(spks)) +
                                    " into change=" + str(change))
       -                        import_addresses(self.rpc, new_addrs, logger)
       +                        import_addresses(self.rpc, new_addrs, [], -1, 0, logger)
        
                    updated_scripthashes.extend(matching_scripthashes)
                    new_history_element = self.generate_new_history_element(tx, txd)