electrum-personal-server

Maximally lightweight electrum server for a single user
git clone https://git.parazyd.org/electrum-personal-server
Log | Files | Refs | README

commit 196e1452bb3f36469c7b55c4b68d1788027e0ca5
parent f506a9ebd4a0e9dbfafd72d81228ae149fb51be8
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date:   Thu, 25 Apr 2019 19:21:21 +0100

Add option for broadcasting txes via a system call

By configuring in the config.ini file, unconfirmed transactions
can now be broadcast by invoking an arbitrary system command.
For example this could be used to broadcast transactions via
SMS or radio.

Diffstat:
Mconfig.ini_sample | 10+++++++++-
Melectrumpersonalserver/server/common.py | 91+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
2 files changed, 65 insertions(+), 36 deletions(-)

diff --git a/config.ini_sample b/config.ini_sample @@ -18,6 +18,7 @@ # this example is a 2-of-3 multisig wallet #multisig_wallet = 2 xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF + [bitcoin-rpc] host = 127.0.0.1 port = 8332 @@ -45,7 +46,6 @@ initial_import_count = 1000 # number of unused addresses kept at the head of the wallet gap_limit = 25 - [electrum-server] # 0.0.0.0 to accept connections from any IP #127.0.0.1 to accept from only localhost @@ -69,6 +69,14 @@ keyfile = certs/cert.key # This is useful on low powered devices at times when the node mempool is large disable_mempool_fee_histogram = false +# Parameter for broadcasting unconfirmed transactions +# Options are: +# * own-node (broadcast using the connected full node) +# * system <cmd> %s (save transaction to file, and invoke system command +# with file path as parameter %s) +broadcast_method = own-node + + [watch-only-addresses] #Add individual addresses to this section, for example paper wallets #Dont use this section for adding entire wallets, instead use the diff --git a/electrumpersonalserver/server/common.py b/electrumpersonalserver/server/common.py @@ -4,7 +4,7 @@ from collections import defaultdict import traceback, sys, platform from ipaddress import ip_network, ip_address import logging -from tempfile import gettempdir +import tempfile from electrumpersonalserver.server.jsonrpc import JsonRpc, JsonRpcError import electrumpersonalserver.server.hashes as hashes @@ -92,7 +92,8 @@ def on_disconnect(txmonitor): subscribed_to_headers[0] = False txmonitor.unsubscribe_all_addresses() -def handle_query(sock, line, rpc, txmonitor, disable_mempool_fee_histogram): +def handle_query(sock, line, rpc, txmonitor, disable_mempool_fee_histogram, + broadcast_method): logger = logging.getLogger('ELECTRUMPERSONALSERVER') logger.debug("=> " + line) try: @@ -226,14 +227,34 @@ def handle_query(sock, line, rpc, txmonitor, disable_mempool_fee_histogram): headers_hex, n = get_block_headers_hex(rpc, start_height, count) send_response(sock, query, headers_hex) elif method == "blockchain.transaction.broadcast": - if not rpc.call("getnetworkinfo", [])["localrelay"]: - result = "Broadcast disabled when using blocksonly" - logger.info("Transaction broadcasting disabled when blocksonly") + txhex = query["params"][0] + result = None + txreport = rpc.call("testmempoolaccept", [[txhex]])[0] + if not txreport["allowed"]: + result = txreport["reject-reason"] else: - try: - result = rpc.call("sendrawtransaction", [query["params"][0]]) - except JsonRpcError as e: - result = str(e) + result = txreport["txid"] + if broadcast_method == "own-node": + if not rpc.call("getnetworkinfo", [])["localrelay"]: + result = "Broadcast disabled when using blocksonly" + logger.warning("Transaction broadcasting disabled when " + + "blocksonly") + else: + try: + rpc.call("sendrawtransaction", [txhex]) + except JsonRpcError as e: + pass + elif broadcast_method.startswith("system "): + with tempfile.NamedTemporaryFile() as fd: + system_line = broadcast_method[7:].replace("%s", fd.name) + fd.write(txhex.encode()) + fd.flush() + logger.debug("running command: " + system_line) + os.system(system_line) + else: + logger.error("Unrecognized broadcast method = " + + broadcast_method) + result = "Unrecognized broadcast method" send_response(sock, query, result) elif method == "mempool.get_fee_histogram": if disable_mempool_fee_histogram: @@ -407,11 +428,30 @@ def create_server_socket(hostport): logger.info("Listening for Electrum Wallet on " + str(hostport)) return server_sock -def run_electrum_server(rpc, txmonitor, hostport, ip_whitelist, - poll_interval_listening, poll_interval_connected, certfile, keyfile, - disable_mempool_fee_histogram): +def run_electrum_server(rpc, txmonitor, config): logger = logging.getLogger('ELECTRUMPERSONALSERVER') logger.info("Starting electrum server") + + hostport = (config.get("electrum-server", "host"), + int(config.get("electrum-server", "port"))) + ip_whitelist = [] + for ip in config.get("electrum-server", "ip_whitelist").split(" "): + if ip == "*": + #matches everything + ip_whitelist.append(ip_network("0.0.0.0/0")) + ip_whitelist.append(ip_network("::0/0")) + else: + ip_whitelist.append(ip_network(ip, strict=False)) + poll_interval_listening = int(config.get("bitcoin-rpc", + "poll_interval_listening")) + poll_interval_connected = int(config.get("bitcoin-rpc", + "poll_interval_connected")) + certfile, keyfile = get_certs(config) + disable_mempool_fee_histogram = config.getboolean("electrum-server", + "disable_mempool_fee_histogram", fallback=False) + broadcast_method = config.get("electrum-server", "broadcast_method", + fallback="own-node") + server_sock = create_server_socket(hostport) server_sock.settimeout(poll_interval_listening) while True: @@ -450,7 +490,8 @@ def run_electrum_server(rpc, txmonitor, hostport, ip_whitelist, recv_buffer = recv_buffer[lb + 1:] lb = recv_buffer.find(b'\n') handle_query(sock, line.decode("utf-8"), rpc, - txmonitor, disable_mempool_fee_histogram) + txmonitor, disable_mempool_fee_histogram, + broadcast_method) except socket.timeout: on_heartbeat_connected(sock, rpc, txmonitor) except (IOError, EOFError) as e: @@ -634,7 +675,7 @@ def logger_config(logger, config): logger.addHandler(logstream) filename = config.get("logging", "log_file_location", fallback="") if len(filename.strip()) == 0: - filename= gettempdir() + "/electrumpersonalserver.log" + filename= tempfile.gettempdir() + "/electrumpersonalserver.log" logfile = logging.FileHandler(filename, mode=('a' if config.get("logging", "append_log", fallback="false") else 'w')) logfile.setFormatter(formatter) @@ -712,28 +753,8 @@ def main(): deterministic_wallets, logger) if not txmonitor.build_address_history(relevant_spks_addrs): return - hostport = (config.get("electrum-server", "host"), - int(config.get("electrum-server", "port"))) - ip_whitelist = [] - for ip in config.get("electrum-server", "ip_whitelist").split(" "): - if ip == "*": - #matches everything - ip_whitelist.append(ip_network("0.0.0.0/0")) - ip_whitelist.append(ip_network("::0/0")) - else: - ip_whitelist.append(ip_network(ip, strict=False)) - poll_interval_listening = int(config.get("bitcoin-rpc", - "poll_interval_listening")) - poll_interval_connected = int(config.get("bitcoin-rpc", - "poll_interval_connected")) - certfile, keyfile = get_certs(config) - disable_mempool_fee_histogram = config.getboolean("electrum-server", - "disable_mempool_fee_histogram", fallback=False) try: - run_electrum_server(rpc, txmonitor, hostport, ip_whitelist, - poll_interval_listening, - poll_interval_connected, certfile, keyfile, - disable_mempool_fee_histogram) + run_electrum_server(rpc, txmonitor, config) except KeyboardInterrupt: logger.info('Received KeyboardInterrupt, quitting')