def main(): opts = parse_args() try: config = RawConfigParser() config.read(opts.config_file) config.options("master-public-keys") except NoSectionError: print("ERROR: Non-existant configuration file {}".format( opts.config_file)) return logger = logging.getLogger('ELECTRUMPERSONALSERVER') logger, logfilename = logger_config(logger, config) logger.info('Starting Electrum Personal Server ' + str(SERVER_VERSION_NUMBER)) logger.info('Logging to ' + logfilename) logger.debug("Process ID (PID) = " + str(os.getpid())) rpc_u = None rpc_p = None cookie_path = None try: rpc_u = config.get("bitcoin-rpc", "rpc_user") rpc_p = config.get("bitcoin-rpc", "rpc_password") logger.debug("obtaining auth from rpc_user/pass") except NoOptionError: cookie_path = obtain_cookie_file_path( config.get("bitcoin-rpc", "datadir")) logger.debug("obtaining auth from .cookie") if rpc_u == None and cookie_path == None: return rpc = JsonRpc(host=config.get("bitcoin-rpc", "host"), port=int(config.get("bitcoin-rpc", "port")), user=rpc_u, password=rpc_p, cookie_path=cookie_path, wallet_filename=config.get("bitcoin-rpc", "wallet_filename").strip(), logger=logger) #TODO somewhere here loop until rpc works and fully sync'd, to allow # people to run this script without waiting for their node to fully # catch up sync'd when getblockchaininfo blocks == headers, or use # verificationprogress printed_error_msg = False while bestblockhash[0] == None: try: bestblockhash[0] = rpc.call("getbestblockhash", []) except JsonRpcError as e: if not printed_error_msg: logger.error("Error with bitcoin json-rpc: " + repr(e)) printed_error_msg = True time.sleep(5) try: rpc.call("listunspent", []) except JsonRpcError as e: logger.error(repr(e)) logger.error("Wallet related RPC call failed, possibly the " + "bitcoin node was compiled with the disable wallet flag") return test_keydata = ( "2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + "T74sszGaxaf64o5s") chain = rpc.call("getblockchaininfo", [])["chain"] try: gaplimit = 5 deterministicwallet.parse_electrum_master_public_key( test_keydata, gaplimit, rpc, chain) except ValueError as e: logger.error(repr(e)) logger.error( "Descriptor related RPC call failed. Bitcoin Core 0.20.0" + " or higher required. Exiting..") return if opts.rescan: rescan_script(logger, rpc, opts.rescan_date) return while True: logger.debug("Checking whether rescan is in progress") walletinfo = rpc.call("getwalletinfo", []) if "scanning" in walletinfo and walletinfo["scanning"]: logger.debug("Waiting for Core wallet rescan to finish") time.sleep(300) continue break import_needed, relevant_spks_addrs, deterministic_wallets = \ get_scriptpubkeys_to_monitor(rpc, config) if import_needed: if not relevant_spks_addrs and not deterministic_wallets: #import = true and no addresses means exit return 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" + " rescan, just restart this script") else: txmonitor = transactionmonitor.TransactionMonitor( rpc, deterministic_wallets, logger) if not txmonitor.build_address_history(relevant_spks_addrs): return try: run_electrum_server(rpc, txmonitor, config) except KeyboardInterrupt: logger.info('Received KeyboardInterrupt, quitting')
def main(): opts = parse_args() try: config = RawConfigParser() config.read(opts.config_file) config.options("master-public-keys") except NoSectionError: print("ERROR: Non-existant configuration file {}".format( opts.config_file)) return 1 logger = logging.getLogger('ELECTRUMPERSONALSERVER') logger, logfilename = logger_config(logger, config) logger.info('Starting Electrum Personal Server ' + str(SERVER_VERSION_NUMBER)) logger.info('Logging to ' + logfilename) logger.debug("Process ID (PID) = " + str(os.getpid())) rpc_u = None rpc_p = None cookie_path = None try: rpc_u = config.get("groestlcoin-rpc", "rpc_user") rpc_p = config.get("groestlcoin-rpc", "rpc_password") logger.debug("obtaining auth from rpc_user/pass") except NoOptionError: cookie_path = obtain_cookie_file_path( config.get("groestlcoin-rpc", "datadir")) logger.debug("obtaining auth from .cookie") if rpc_u == None and cookie_path == None: return 1 rpc = JsonRpc(host=config.get("groestlcoin-rpc", "host"), port=int(config.get("groestlcoin-rpc", "port")), user=rpc_u, password=rpc_p, cookie_path=cookie_path, wallet_filename=config.get("groestlcoin-rpc", "wallet_filename").strip(), logger=logger) #TODO somewhere here loop until rpc works and fully sync'd, to allow # people to run this script without waiting for their node to fully # catch up sync'd when getblockchaininfo blocks == headers, or use # verificationprogress printed_error_msg = False while bestblockhash[0] == None: try: bestblockhash[0] = rpc.call("getbestblockhash", []) except JsonRpcError as e: if not printed_error_msg: logger.error("Error with groestlcoin json-rpc: " + repr(e)) printed_error_msg = True time.sleep(5) try: rpc.call("listunspent", []) except JsonRpcError as e: logger.error(repr(e)) logger.error( "Wallet related RPC call failed, possibly the " + "groestlcoin node was compiled with the disable wallet flag") return 1 test_keydata = ( "2 tpubD6NzVbkrYhZ4Xh9Ybbw6cna2UyGzYXmpk6bYuvWAUsD493Eb57ssPKC74CoVyQi" + "thN9hRUKZz5c5BQqnS8zqDisJvDTA6fJgyVjhGFtSZaN tpubD6NzVbkrYhZ4XG7HrP7yo" + "9We8rEmc7RTbViHgKwYNwNN71gvuMpYDWRHCUoGMYKZvDMbF3VpWngHvbpTan9Wd3WTk9q" + "ZzZyuYKAGoPJRGjs") chain = rpc.call("getblockchaininfo", [])["chain"] try: gaplimit = 5 deterministicwallet.parse_electrum_master_public_key( test_keydata, gaplimit, rpc, chain) except ValueError as e: logger.error(repr(e)) logger.error( "Descriptor related RPC call failed. Groestlcoin Core 2.20.1" + " or higher required. Exiting..") return 1 if opts.rescan: rescan_script(logger, rpc, opts.rescan_date) return 0 while True: logger.debug("Checking whether rescan is in progress") walletinfo = rpc.call("getwalletinfo", []) if "scanning" in walletinfo and walletinfo["scanning"]: logger.debug("Waiting for Core wallet rescan to finish") time.sleep(300) continue break import_needed, relevant_spks_addrs, deterministic_wallets = \ get_scriptpubkeys_to_monitor(rpc, config) if import_needed: if not relevant_spks_addrs and not deterministic_wallets: #import = true and no addresses means exit return 0 deterministicwallet.import_addresses( rpc, relevant_spks_addrs, deterministic_wallets, change_param=-1, count=int(config.get("groestlcoin-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" + " rescan, just restart this script") else: txmonitor = transactionmonitor.TransactionMonitor( rpc, deterministic_wallets, logger) if not txmonitor.build_address_history(relevant_spks_addrs): return 1 try: run_electrum_server(rpc, txmonitor, config) except KeyboardInterrupt: logger.info('Received KeyboardInterrupt, quitting') return 1 return 0
def get_scriptpubkeys_to_monitor(rpc, config): logger = logging.getLogger('ELECTRUMPERSONALSERVER') st = time.time() try: imported_addresses = set( rpc.call("getaddressesbyaccount", [deterministicwallet.ADDRESSES_LABEL])) logger.debug("using deprecated accounts interface") except JsonRpcError: #bitcoin core 0.17 deprecates accounts, replaced with labels if deterministicwallet.ADDRESSES_LABEL in rpc.call("listlabels", []): imported_addresses = set( rpc.call("getaddressesbylabel", [deterministicwallet.ADDRESSES_LABEL]).keys()) else: #no label, no addresses imported at all imported_addresses = set() deterministic_wallets = [] for key in config.options("master-public-keys"): mpk = config.get("master-public-keys", key) gaplimit = int(config.get("bitcoin-rpc", "gap_limit")) chain = rpc.call("getblockchaininfo", [])["chain"] try: wal = deterministicwallet.parse_electrum_master_public_key( mpk, gaplimit, rpc, chain) except ValueError: raise ValueError("Bad master public key format. Get it from " + "Electrum menu `Wallet` -> `Information`") deterministic_wallets.append(wal) #check whether these deterministic wallets have already been imported import_needed = False wallets_to_import = [] TEST_ADDR_COUNT = 3 logger.info("Displaying first " + str(TEST_ADDR_COUNT) + " addresses of " + "each master public key:") for config_mpk_key, wal in zip(config.options("master-public-keys"), deterministic_wallets): first_addrs, first_spk = wal.get_addresses(change=0, from_index=0, count=TEST_ADDR_COUNT) logger.info("\n" + config_mpk_key + " =>\n\t" + "\n\t".join(first_addrs)) last_addr, last_spk = wal.get_addresses( change=0, from_index=int(config.get("bitcoin-rpc", "initial_import_count")) - 1, count=1) if not set(first_addrs + last_addr).issubset(imported_addresses): import_needed = True wallets_to_import.append(wal) logger.info("Obtaining bitcoin addresses to monitor . . .") #check whether watch-only addresses have been imported watch_only_addresses = [] for key in config.options("watch-only-addresses"): watch_only_addresses.extend( config.get("watch-only-addresses", key).split(' ')) watch_only_addresses = set(watch_only_addresses) watch_only_addresses_to_import = [] if not watch_only_addresses.issubset(imported_addresses): import_needed = True watch_only_addresses_to_import = (watch_only_addresses - imported_addresses) 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 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(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, watch_only_addresses_to_import, wallets_to_import #test # importing one det wallet and no addrs, two det wallets and no addrs # no det wallets and some addrs, some det wallets and some addrs #at this point we know we dont need to import any addresses #find which index the deterministic wallets are up to spks_to_monitor = [] for wal in deterministic_wallets: for change in [0, 1]: addrs, spks = wal.get_addresses( change, 0, int(config.get("bitcoin-rpc", "initial_import_count"))) spks_to_monitor.extend(spks) #loop until one address found that isnt imported while True: addrs, spks = wal.get_new_addresses(change, count=1) if addrs[0] not in imported_addresses: break spks_to_monitor.append(spks[0]) wal.rewind_one(change) spks_to_monitor.extend( [hashes.address_to_script(addr, rpc) for addr in watch_only_addresses]) et = time.time() logger.info("Obtained list of addresses to monitor in " + str(et - st) + "sec") return False, spks_to_monitor, deterministic_wallets
def get_scriptpubkeys_to_monitor(rpc, config): logger = logging.getLogger('ELECTRUMPERSONALSERVER') st = time.time() try: imported_addresses = set(rpc.call("getaddressesbyaccount", [transactionmonitor.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", []): imported_addresses = set(rpc.call("getaddressesbylabel", [transactionmonitor.ADDRESSES_LABEL]).keys()) else: #no label, no addresses imported at all imported_addresses = set() logger.debug("already-imported addresses = " + str(imported_addresses)) deterministic_wallets = [] for key in config.options("master-public-keys"): wal = deterministicwallet.parse_electrum_master_public_key( config.get("master-public-keys", key), int(config.get("bitcoin-rpc", "gap_limit"))) deterministic_wallets.append(wal) #check whether these deterministic wallets have already been imported import_needed = False wallets_imported = 0 spks_to_import = [] TEST_ADDR_COUNT = 3 logger.info("Displaying first " + str(TEST_ADDR_COUNT) + " addresses of " + "each master public key:") for config_mpk_key, wal in zip(config.options("master-public-keys"), deterministic_wallets): first_spks = wal.get_scriptpubkeys(change=0, from_index=0, count=TEST_ADDR_COUNT) first_addrs = [hashes.script_to_address(s, rpc) for s in first_spks] logger.info("\n" + config_mpk_key + " =>\n\t" + "\n\t".join( first_addrs)) if not set(first_addrs).issubset(imported_addresses): import_needed = True wallets_imported += 1 for change in [0, 1]: spks_to_import.extend(wal.get_scriptpubkeys(change, 0, int(config.get("bitcoin-rpc", "initial_import_count")))) logger.info("Obtaining bitcoin addresses to monitor . . .") #check whether watch-only addresses have been imported watch_only_addresses = [] for key in config.options("watch-only-addresses"): watch_only_addresses.extend(config.get("watch-only-addresses", key).split(' ')) watch_only_addresses = set(watch_only_addresses) watch_only_addresses_to_import = [] if not watch_only_addresses.issubset(imported_addresses): import_needed = True watch_only_addresses_to_import = (watch_only_addresses - imported_addresses) #if addresses need to be imported then return them if import_needed: addresses_to_import = [hashes.script_to_address(spk, rpc) for spk in spks_to_import] #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") time.sleep(5) return (True, addresses_to_import + list( watch_only_addresses_to_import), None) #test # importing one det wallet and no addrs, two det wallets and no addrs # no det wallets and some addrs, some det wallets and some addrs #at this point we know we dont need to import any addresses #find which index the deterministic wallets are up to spks_to_monitor = [] for wal in deterministic_wallets: for change in [0, 1]: spks_to_monitor.extend(wal.get_scriptpubkeys(change, 0, int(config.get("bitcoin-rpc", "initial_import_count")))) #loop until one address found that isnt imported while True: spk = wal.get_new_scriptpubkeys(change, count=1)[0] spks_to_monitor.append(spk) if hashes.script_to_address(spk, rpc) not in imported_addresses: break spks_to_monitor.pop() wal.rewind_one(change) spks_to_monitor.extend([hashes.address_to_script(addr, rpc) for addr in watch_only_addresses]) et = time.time() logger.info("Obtained list of addresses to monitor in " + str(et - st) + "sec") return False, spks_to_monitor, deterministic_wallets