Skip to content

Commit

Permalink
full proxy support, proper argument parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
laanwj committed Jun 24, 2015
1 parent 29ce80a commit b7f931a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 49 deletions.
35 changes: 25 additions & 10 deletions README.md
Expand Up @@ -11,15 +11,28 @@ Usage

Usage:

bitcoin-submittx <mainnet|testnet|regtest> TXHEX[,TXHEX...] NODE [NODE...]
usage: bitcoin-submittx [-h] [--proxy PROXY] [--timeout TIMEOUT]
NETWORK TXHEX NODES [NODES ...]

- `<mainnet|testnet|regtest>` selects the network to use. This also determines the default port
- `TXHEX` is the serialized transaction, encoded as hexadecimal
- `NODE` is one or more node addresses (`host` or `host:port`) to submit the transaction to
Transaction submission tool

The tool will connect to the provided nodes and inv the transactions. If the
nodes subsequently request them within the timeout (currently hardcoded to 10
seconds), they are sent.
positional arguments:
NETWORK Network to connect to (mainnet, regtest, testnet).
This also determines the default port
TXHEX Serialized transactions to broadcast, separated by
commas
NODES Nodes to connect to, denoted either host or host:port

optional arguments:
-h, --help show this help message and exit
--proxy PROXY, -p PROXY
SOCKS5 proxy to connect through
--timeout TIMEOUT, -t TIMEOUT
Number of seconds to wait before disconnecting from
nodes (default is 10)

The tool will connect to the provided nodes and announce the transactions. If the
nodes subsequently request them within the timeout, they are sent.

The return status of the program will be 0 if at least one node requested the transaction, and 1
otherwise.
Expand Down Expand Up @@ -50,12 +63,14 @@ $ bitcoin-submittx mainnet '010000000...' 127.0.0.1
```
(normally one would not submit the transaction to the localhost node, but this is just an illustrative example)

TODO
------
TODOs and contribution ideas
-----------------------------

- SOCKS5 proxy support (port from [test_framework/socks5.py](https://github.com/bitcoin/bitcoin/blob/master/qa/rpc-tests/test_framework/socks5.py))
- IPv6 support
- Provide feedback if the transaction is rejected
- Tor stream isolation (like `-proxyrandomize` in Bitcoin Core)
- Load node lists / transactions from file
- Multi-hop proxies, different proxy types?

Dependencies
--------------
Expand Down
94 changes: 56 additions & 38 deletions bitcoin-submittx
Expand Up @@ -16,6 +16,7 @@ import sys
import threading
from io import BytesIO
from binascii import unhexlify
import argparse

import bitcoin
from bitcoin.core import CTransaction, b2lx
Expand Down Expand Up @@ -50,17 +51,32 @@ class AddressType:
DOMAINNAME = 0x03
IPV6 = 0x04

class SOCKS5Proxy(object):
class AbstractConnector(object):
def connect(self, sock, dst):
raise NotImplemented

class DirectConnection(AbstractConnector):
'''
Dummy object representing a direct socket connection
'''
def connect(self, sock, dst):
sock.connect(dst)

def __repr__(self):
return 'DirectConnection'

class SOCKS5Proxy(AbstractConnector):
'''
SOCKS5 proxy, as described in RFC1928
'''
def __init__(self, host, port):
self.dst = (host, port)
def __init__(self, dst):
self.dst = dst

def connect(self, sock, host, port):
def connect(self, sock, dst):
'''SOCKS5 negotiation.
sock must already be connected to proxy.
'''
(host, port) = dst
# version, nmethods, support unauthenticated
sock.sendall(bytearray([0x05, 0x01, 0x00]))
# receive version, chosen method
Expand Down Expand Up @@ -123,26 +139,17 @@ class NodeConn(threading.Thread):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.log.write("connecting to %s through %s\n" % (self.dstname, self.proxy))
if self.proxy is not None:
firsthop = self.proxy.dst
hops = [(DirectConnection(), self.proxy.dst), (self.proxy, self.dst)]
else:
firsthop = self.dst
hops = [(DirectConnection(), self.dst)]

# connect directly to first hop
try:
self.sock.connect(firsthop)
except Exception as e:
self.log.write("error connecting to %s:%i\n" % firsthop)
self.handle_close()
return

# connect through proxy to second hop, if appropriate
try:
if self.proxy is not None:
self.proxy.connect(self.sock, self.dst[0], self.dst[1])
except Exception as e:
self.log.write("error connecting through proxy to %s: %s\n" % (self.dstname, str(e)))
self.handle_close()
return
for method,dst in hops:
try:
method.connect(self.sock, dst)
except Exception as e:
self.log.write("error connecting to %s:%i through %s\n" % (dst[0], dst[1], method))
self.handle_close()
return

# stuff version msg into sendbuf
vt = msg_version()
Expand Down Expand Up @@ -304,6 +311,7 @@ class PeerManager(object):
def tx_broadcasted(self, txhash):
self.stats[txhash] += 1

# Miscelleneous utility functions #
def join_all(threads, timeout):
'''
Join a bunch of threads, with timeout.
Expand All @@ -328,46 +336,56 @@ def parse_host_port(node, default_port):
if port:
port = int(port)
else:
if default_port is None:
raise ValueError('Must provide port in %s' % node)
port = default_port
return (host,port)

def main():
if len(sys.argv) < 4:
print("Usage: %s <mainnet|testnet|regtest> TXHEX[,TXHEX...] NODE [NODE...]" % sys.argv[0])
sys.exit(1)
# Main program logic #

chain = sys.argv[1]
nodes = sys.argv[3:]
timeout = 10
verbose = True
def parse_args():
parser = argparse.ArgumentParser(description="Transaction submission tool")
parser.add_argument('network', metavar='NETWORK', help='Network to connect to (mainnet, regtest, testnet). This also determines the default port')
parser.add_argument('transactions', metavar='TXHEX', help='Serialized transactions to broadcast, separated by commas')
parser.add_argument('nodes', metavar='NODES', help='Nodes to connect to, denoted either host or host:port', nargs='+')
parser.add_argument('--proxy', '-p', help='SOCKS5 proxy to connect through', default=None)
parser.add_argument('--timeout', '-t', help='Number of seconds to wait before disconnecting from nodes (default is 10)', type=int, default=10)
return parser.parse_args()

def main():
args = parse_args()

timeout = args.timeout
verbose = True # XXX flag=False doesn't avoid log output
if args.proxy is None:
proxy = None
else:
proxy = SOCKS5Proxy(parse_host_port(args.proxy, None))
log = sys.stdout

try:
bitcoin.SelectParams(chain)
bitcoin.SelectParams(args.network)
except:
log.write("invalid network\n")
sys.exit(1)
params = bitcoin.params

# build transactions list
transactions = {}
for txdata in sys.argv[2].split(','):
for txdata in args.transactions.split(','):
transactions[Hash(txdata)] = CTransaction.deserialize(unhexlify(txdata))

# parse nodes list
nodes = [parse_host_port(node, params.DEFAULT_PORT) for node in args.nodes]

if verbose:
print("Attempting broadcast of %i transactions to %i peers in %i seconds" % (len(transactions), len(nodes), timeout))

#proxy = SOCKS5Proxy('127.0.0.1', 9050)
proxy = None

peermgr = PeerManager(log, proxy, params, transactions)

threads = []

# connect to specified remote node(s)
for node in nodes:
(host, port) = parse_host_port(node, params.DEFAULT_PORT)
for host,port in nodes:
c = peermgr.add(host, port)
threads.append(c)

Expand Down
2 changes: 1 addition & 1 deletion test_submittx2.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# testnet nodes on onion:
./bitcoin-submittx testnet \
./bitcoin-submittx --proxy 127.0.0.1:9050 testnet \
01000000024826c9bc925bfa719c0db9df88b0a0c70b58429de573254adf36a4342c6e525e010000006b4830450221009cfb2677a4cc6ef99779ce3c91ff23a7ade7e4d0b68b5293e29e741f5be5c64d02206a6a7f8a7e71128de7c4db5d20d9834709f9e74f31f47a10836310259a5fda94012102cbb765d57d5390a1b8e82783d8ace916649ea85d145c2a385bd4c077be738981feffffff3b3cfcb16d086236aa72d39851b039717757eb65370e722dad219a49a669086e000000006a47304402204a775cbfa2315bf32b60569d80bc18377ce02634261c2f8a545b6017585b111a0220228913e81521f81498dc13cd0155556fe2a0ce0f085db89e66be79850074a7d7012103dd168d39c03fbd04a7bacc88a793905e35133dbe78ba457490ff61ea6ecba4b1feffffff0280969800000000001976a9140db855a62bbc4539705dad90a974af4d18ddbade88ac9aa21600000000001976a9140367c53c0fbb0fb8b4b1699443e66152e6d28c5588ac91620700 \
nkf5e6b7pl4jfd4a.onion 4zhkir2ofl7orfom.onion t6xj6wilh4ytvcs7.onion i6y6ivorwakd7nw3.onion ubqj4rsu3nqtxmtp.onion

0 comments on commit b7f931a

Please sign in to comment.