Asynchronous HTTP significantly speeds up operations involving multiple
JSON-RPC calls to the server, such as tracking wallet views for wallets
with a large number of outputs.
This patch adds base-level asyncio infrastructure plus aiohttp support to all
applicable MMGen commands.
The aiohttp package is not currently supported by MSYS2, so Windows users will
have to choose one of the other backends ('curl' is the default).
Tested on: Linux, Armbian, Windows; Python 3.6, 3.7, 3.8
New user features:
- configurable RPC backends via the 'rpc_backend' option. Supported
options are 'aiohttp' (Linux-only), 'httplib', 'requests' and 'curl'
- configurable RPC queue size via the 'aiohttp_rpc_queue_len' option
The patch also includes a rewrite/redesign of large parts of the MMGen code
base, most importantly:
- rpc.py - full rewrite of RPC library, new RPCBackends class
- main_addrimport.py - full rewrite
- main_autosign.py - LED code now handled by new LEDControl class
- eth/tw.py, eth/tx.py - reworked logic for resolving token symbols and
addresses
- eth/tx.py - separate classes for signed and unsigned transactions
Testing:
# Set a backend (choose one):
$ export MMGEN_RPC_BACKEND='aiohttp' # Linux-only
$ export MMGEN_RPC_BACKEND='curl' # Windows
$ export MMGEN_RPC_BACKEND='httplib' # compare performance with 'aiohttp'
# Bitcoin:
$ test/unit_tests.py rpc btc
$ test/test.py main regtest autosign
# Ethereum:
$ test/unit_tests.py rpc eth
$ test/tooltest2.py --coin=eth --testnet=1 txview
$ test/test.py --coin=eth ethdev
# Monero wallet:
$ test/unit_tests.py rpc xmr_wallet
$ test/test-release.sh -F xmr
132 lines
4 KiB
Python
Executable file
132 lines
4 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
test/unit_tests_d/ut_tx_deserialize: TX deserialization unit test for the MMGen suite
|
|
"""
|
|
|
|
import os,json
|
|
|
|
from mmgen.common import *
|
|
from ..include.common import *
|
|
from mmgen.protocol import init_coin
|
|
from mmgen.tx import MMGenTX,DeserializedTX
|
|
from mmgen.rpc import rpc_init
|
|
from mmgen.daemon import CoinDaemon
|
|
|
|
class unit_test(object):
|
|
|
|
def _get_core_repo_root(self):
|
|
self.core_repo_root = os.getenv('CORE_REPO_ROOT')
|
|
if not self.core_repo_root:
|
|
die(1,'The environmental variable CORE_REPO_ROOT must be set before running this test')
|
|
|
|
def run_test(self,name,ut):
|
|
|
|
async def test_tx(txhex,desc,n):
|
|
|
|
def has_nonstandard_outputs(outputs):
|
|
for o in outputs:
|
|
t = o['scriptPubKey']['type']
|
|
if t in ('nonstandard','pubkey','nulldata'):
|
|
return True
|
|
return False
|
|
|
|
d = await g.rpc.call('decoderawtransaction',txhex)
|
|
|
|
if has_nonstandard_outputs(d['vout']): return False
|
|
|
|
dt = DeserializedTX(txhex)
|
|
|
|
if opt.verbose:
|
|
Msg('\n====================================================')
|
|
Msg_r('.' if opt.quiet else '{:>3}) {}\n'.format(n,desc))
|
|
if opt.verbose:
|
|
Pmsg(d)
|
|
Msg('----------------------------------------------------')
|
|
Pmsg(dt)
|
|
|
|
# metadata
|
|
assert dt['txid'] == d['txid'],'TXID does not match'
|
|
assert dt['lock_time'] == d['locktime'],'Locktime does not match'
|
|
assert dt['version'] == d['version'],'Version does not match'
|
|
|
|
# inputs
|
|
a,b = d['vin'],dt['txins']
|
|
for i in range(len(a)):
|
|
assert a[i]['txid'] == b[i]['txid'],'TxID of input {} does not match'.format(i)
|
|
assert a[i]['vout'] == b[i]['vout'],'vout of input {} does not match'.format(i)
|
|
assert a[i]['sequence'] == int(b[i]['nSeq'],16),(
|
|
'nSeq of input {} does not match'.format(i))
|
|
if 'txinwitness' in a[i]:
|
|
assert a[i]['txinwitness'] == b[i]['witness'],(
|
|
'witness of input {} does not match'.format(i))
|
|
|
|
# outputs
|
|
a,b = d['vout'],dt['txouts']
|
|
for i in range(len(a)):
|
|
A = a[i]['scriptPubKey']['addresses'][0]
|
|
B = b[i]['address']
|
|
fs = 'address of output {} does not match\nA: {}\nB: {}'
|
|
assert A == B, fs.format(i,A,B)
|
|
|
|
A = a[i]['value']
|
|
B = b[i]['amount']
|
|
fs = 'value of output {} does not match\nA: {}\nB: {}'
|
|
assert A == B, fs.format(i,A,B)
|
|
|
|
A = a[i]['scriptPubKey']['hex']
|
|
B = b[i]['scriptPubKey']
|
|
fs = 'scriptPubKey of output {} does not match\nA: {}\nB: {}'
|
|
assert A == B, fs.format(i,A,B)
|
|
|
|
return True
|
|
|
|
def print_info(fn,extra_desc):
|
|
if opt.names:
|
|
Msg_r('{} {} ({}){}'.format(
|
|
purple('Testing'),
|
|
cyan(name),
|
|
extra_desc,
|
|
'' if opt.quiet else '\n'))
|
|
else:
|
|
Msg_r('Testing transactions from {!r}'.format(fn))
|
|
if not opt.quiet: Msg('')
|
|
|
|
async def test_core_vectors():
|
|
self._get_core_repo_root()
|
|
fn_b = 'src/test/data/tx_valid.json'
|
|
fn = os.path.join(self.core_repo_root,fn_b)
|
|
data = json.loads(open(fn).read())
|
|
print_info(fn_b,'Core test vectors')
|
|
n = 1
|
|
for e in data:
|
|
if type(e[0]) == list:
|
|
await rpc_init()
|
|
await test_tx(e[1],desc,n)
|
|
n += 1
|
|
else:
|
|
desc = e[0]
|
|
Msg('OK')
|
|
|
|
async def test_mmgen_txs():
|
|
fns = ( ('btc',False,'test/ref/0B8D5A[15.31789,14,tl=1320969600].rawtx'),
|
|
('btc',True,'test/ref/0C7115[15.86255,14,tl=1320969600].testnet.rawtx'),
|
|
# ('bch',False,'test/ref/460D4D-BCH[10.19764,tl=1320969600].rawtx')
|
|
)
|
|
print_info('test/ref/*rawtx','MMGen reference transactions')
|
|
for n,(coin,tn,fn) in enumerate(fns):
|
|
init_coin(coin,tn)
|
|
g.proto.daemon_data_dir = 'test/daemons/' + g.coin.lower()
|
|
g.rpc_port = CoinDaemon(coin + ('','_tn')[tn],test_suite=True).rpc_port
|
|
await rpc_init()
|
|
await test_tx(MMGenTX(fn).hex,fn,n+1)
|
|
init_coin('btc',False)
|
|
g.rpc_port = CoinDaemon('btc',test_suite=True).rpc_port
|
|
await rpc_init()
|
|
Msg('OK')
|
|
|
|
start_test_daemons('btc','btc_tn') # ,'bch')
|
|
run_session(test_mmgen_txs(),do_rpc_init=False)
|
|
run_session(test_core_vectors(),do_rpc_init=False)
|
|
stop_test_daemons('btc','btc_tn') # ,'bch')
|
|
|
|
return True
|