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:
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')