Skip to content

Commit

Permalink
Add option for broadcasting txes via a system call
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chris-belcher committed Apr 25, 2019
1 parent f506a9e commit 196e145
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 36 deletions.
10 changes: 9 additions & 1 deletion config.ini_sample
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
91 changes: 56 additions & 35 deletions electrumpersonalserver/server/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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')

Expand Down

0 comments on commit 196e145

Please sign in to comment.