qa: port 12 wallet/mining tests to python3 and fix three framework blockers

Ports qa/rpc-tests from 6 python3 files to 18. Ran every ported test against
the freshly built v1.3.0 dragonxd. Results, honestly:

  PASS (1)            getblocktemplate_proposals.py
  NOT APPLICABLE (7)  wallet.py, walletbackup.py, wallet_protectcoinbase.py,
                      wallet_listnotes.py, wallet_mergetoaddress.py,
                      getblocktemplate.py, wallet_shieldcoinbase.py
  BLOCKED (4)         wallet_sapling, wallet_nullifiers, wallet_persistence,
                      wallet_treestate

The "not applicable" seven are inherited Zcash/Hush-era tests that exercise
features DragonX deliberately removed. They assume transparent t->t value
transfer, but ASSETCHAINS_PRIVATE=1 (hush_utils.h:1826) makes sendtoaddress
and sendmany consensus-refuse; they assume Sprout joinsplits, which are gone
from the RPC layer entirely; and they hardcode Bitcoin economics (10 coin/block,
100-block maturity) against DragonX's 3 DRGX and COINBASE_MATURITY=1. They are
ported and left in place rather than deleted, but they cannot pass on this chain
without being rewritten around z_shieldcoinbase/autoshield.

Three framework fixes in test_framework/util.py, each of which broke every
multi-node test:
  - initialize_chain() passed -connect=0, and init.cpp soft-sets -listen=0 when
    -connect is present, so cache node0 never opened its p2p port and nodes 1-3
    could never sync to it -- initialize_chain() hung forever in sync_blocks().
    Now passes -listen=1 -bind=127.0.0.1 -dnsseed=0 explicitly (an explicit arg
    beats SoftSetBoolArg) while keeping the cache nodes off the public network.
  - cache cleanup removed files from <datadir> when dragonxd writes them one
    level deeper into the net-specific <datadir>/regtest.
  - set_node_times() did print("..." + t) with t an int -> TypeError.
  - default binary paths corrected to src/dragonxd and src/dragonx-cli.

The four BLOCKED tests are blocked by a daemon assert, not by the port; see the
follow-up commit/report on BLOCK_VALID_CONTEXT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo
This commit is contained in:
2026-08-31 01:53:43 -05:00
parent e2e10f6ef8
commit c5fde12485
13 changed files with 485 additions and 132 deletions

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2016 The Zcash developers # Copyright (c) 2016 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2014 The Bitcoin Core developers # Copyright (c) 2014 The Bitcoin Core developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -6,6 +6,7 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException from test_framework.authproxy import JSONRPCException
from test_framework.util import initialize_chain_clean, start_node
from binascii import a2b_hex, b2a_hex from binascii import a2b_hex, b2a_hex
from hashlib import sha256 from hashlib import sha256
@@ -69,14 +70,43 @@ def genmrklroot(leaflist):
cur = n cur = n
return cur[0] return cur[0]
# ---------------------------------------------------------------------------
# Sapling v4 transaction layout.
#
# This test was written against the pre-Overwinter serialization, where a tx
# began with a 4-byte nVersion immediately followed by the vin count, so the
# first input's prevout hash lived at offset 4+1. DragonX transactions are
# Sapling v4: 4-byte header (nVersion | fOverwintered) + 4-byte nVersionGroupId
# + vin count, so the prevout hash starts 4 bytes further in. Poking the old
# offset corrupts nVersionGroupId and every proposal below just comes back
# "Block decode failed" instead of exercising any consensus rule.
CB_PREVOUT_OFF = 4+4+1
# Likewise the tx no longer ends at nLockTime: nExpiryHeight (4), valueBalance
# (8) and the empty vShieldedSpend/vShieldedOutput/vJoinSplit counts (1 each)
# trail it, so nLockTime is the 4 bytes at [-19:-15].
TX_TAIL_AFTER_LOCKTIME = 4+8+1+1+1
def tx_seq_off(tx):
"""Offset of the first input's nSequence in a Sapling v4 tx."""
scriptlen_off = CB_PREVOUT_OFF + 32 + 4 # after prevout hash + prevout.n
return scriptlen_off + 1 + tx[scriptlen_off]
def tx_vout0_value_off(tx):
"""Offset of the first output's 8-byte value in a Sapling v4 tx."""
return tx_seq_off(tx) + 4 + 1 # after nSequence + vout count
def template_to_bytes(tmpl, txlist): def template_to_bytes(tmpl, txlist):
blkver = pack('<L', tmpl['version']) blkver = pack('<L', tmpl['version'])
mrklroot = genmrklroot(list(dblsha(a) for a in txlist)) mrklroot = genmrklroot(list(dblsha(a) for a in txlist))
reserved = b'\0'*32 # hashFinalSaplingRoot. The all-zeroes placeholder this test used predates
# Sapling; a header carrying the wrong root is rejected with
# 'bad-sapling-root-in-block', which would sink even Test 11 (valid block).
reserved = a2b_hex(tmpl['finalsaplingroothash'])[::-1]
timestamp = pack('<L', tmpl['curtime']) timestamp = pack('<L', tmpl['curtime'])
nonce = b'\0'*32 nonce = b'\0'*32
soln = b'\0' soln = b'\0'
blk = blkver + a2b_hex(tmpl['previousblockhash'])[::-1] + mrklroot + reserved + timestamp + a2b_hex(tmpl['bits'])[::-1] + nonce + soln # bytearray, not bytes: Test 9 mutates one byte of the result in place.
blk = bytearray(blkver + a2b_hex(tmpl['previousblockhash'])[::-1] + mrklroot + reserved + timestamp + a2b_hex(tmpl['bits'])[::-1] + nonce + soln)
blk += varlenEncode(len(txlist)) blk += varlenEncode(len(txlist))
for tx in txlist: for tx in txlist:
blk += tx blk += tx
@@ -95,6 +125,20 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
Test block proposals with getblocktemplate. Test block proposals with getblocktemplate.
''' '''
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 1)
def setup_network(self, split=False):
# -daaforkheight=0: ContextualCheckBlockHeader only enforces nBits for a
# smart chain above daaForkHeight, which defaults to
# ASSETCHAINS_RANDOMX_VALIDATION+62000 (millions of blocks) so that a
# fresh sync accepts DragonX's historical bad-nBits window. Without this
# flag Test 8 (bad bits) is unreachable at regtest heights and the
# daemon happily accepts a block with arbitrary nBits.
self.nodes = [ start_node(0, self.options.tmpdir, ['-daaforkheight=0']) ]
self.is_network_split = False
def run_test(self): def run_test(self):
node = self.nodes[0] node = self.nodes[0]
node.generate(1) # Mine a block to leave initial block download node.generate(1) # Mine a block to leave initial block download
@@ -117,9 +161,9 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
#txlist[0][4+1+36+1+1] -= 1 #txlist[0][4+1+36+1+1] -= 1
# Test 2: Bad input hash for gen tx # Test 2: Bad input hash for gen tx
txlist[0][4+1] += 1 txlist[0][CB_PREVOUT_OFF] += 1
assert_template(node, tmpl, txlist, 'bad-cb-missing') assert_template(node, tmpl, txlist, 'bad-cb-missing')
txlist[0][4+1] -= 1 txlist[0][CB_PREVOUT_OFF] -= 1
# Test 3: Truncated final tx # Test 3: Truncated final tx
lastbyte = txlist[-1].pop() lastbyte = txlist[-1].pop()
@@ -136,14 +180,33 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
# Test 5: Add an invalid tx to the end (non-duplicate) # Test 5: Add an invalid tx to the end (non-duplicate)
txlist.append(bytearray(txlist[0])) txlist.append(bytearray(txlist[0]))
txlist[-1][4+1] = b'\xff' txlist[-1][CB_PREVOUT_OFF] = 0xff
# DragonX is a fully private chain (ASSETCHAINS_PRIVATE): a non-coinbase
# tx paying a positive amount to a t-addr is killed in CheckTransaction
# with 'bad-txns-acprivacy-chain' before ConnectBlock ever looks up its
# inputs. Zero the output value -- CheckTransaction exempts zero-value
# vouts -- so the tx survives to the missing-input check this case is
# actually about.
_val_off = tx_vout0_value_off(txlist[-1])
txlist[-1][_val_off:_val_off+8] = b'\0'*8
assert_template(node, tmpl, txlist, 'bad-txns-inputs-missingorspent') assert_template(node, tmpl, txlist, 'bad-txns-inputs-missingorspent')
txlist.pop() txlist.pop()
# Test 6: Future tx lock time # Test 6: Future tx lock time
txlist[0][-4:] = b'\xff\xff\xff\xff' # The server-supplied coinbase carries nSequence=0xffffffff, and
# IsFinalTx() short-circuits on all-final inputs, so a future nLockTime
# alone leaves the tx final and the block valid. (DragonX's IsFinalTx
# also whitelists 0xfffffffe below the Hush hardfork height, which
# regtest is.) Make the input genuinely non-final so the future
# nLockTime is the thing under test.
seq_off = tx_seq_off(txlist[0])
realseq = txlist[0][seq_off:seq_off+4]
txlist[0][seq_off:seq_off+4] = b'\0\0\0\0'
reallocktime = txlist[0][-TX_TAIL_AFTER_LOCKTIME-4:-TX_TAIL_AFTER_LOCKTIME]
txlist[0][-TX_TAIL_AFTER_LOCKTIME-4:-TX_TAIL_AFTER_LOCKTIME] = b'\xff\xff\xff\xff'
assert_template(node, tmpl, txlist, 'bad-txns-nonfinal') assert_template(node, tmpl, txlist, 'bad-txns-nonfinal')
txlist[0][-4:] = b'\0\0\0\0' txlist[0][-TX_TAIL_AFTER_LOCKTIME-4:-TX_TAIL_AFTER_LOCKTIME] = reallocktime
txlist[0][seq_off:seq_off+4] = realseq
# Test 7: Bad tx count # Test 7: Bad tx count
txlist.append(b'') txlist.append(b'')
@@ -171,7 +234,9 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
tmpl['curtime'] = 0x7fffffff tmpl['curtime'] = 0x7fffffff
assert_template(node, tmpl, txlist, 'time-too-new') assert_template(node, tmpl, txlist, 'time-too-new')
tmpl['curtime'] = 0 tmpl['curtime'] = 0
assert_template(node, tmpl, txlist, 'time-too-old') # DragonX split Bitcoin's 'time-too-old' into 'time-too-old-median'
# (block time <= prev MedianTimePast) and 'time-too-old-prevblock'.
assert_template(node, tmpl, txlist, 'time-too-old-median')
tmpl['curtime'] = realtime tmpl['curtime'] = realtime
# Test 11: Valid block # Test 11: Valid block

View File

@@ -148,14 +148,20 @@ def initialize_chain(test_dir):
# Same two requirements as start_node(): -regtest must be a command-line flag (the # Same two requirements as start_node(): -regtest must be a command-line flag (the
# conf key is ignored, and without it this cache node runs on MAINNET), and -asmap # conf key is ignored, and without it this cache node runs on MAINNET), and -asmap
# must be absolute or dragonxd refuses to start. # must be absolute or dragonxd refuses to start.
args = [ os.getenv("BITCOIND", "dragonxd"), "-regtest", "-connect=0", "-keypool=1", "-datadir="+datadir, "-discover=0" ] # -connect=<anything> makes init.cpp soft-set -listen=0 ("parameter interaction: -connect
# set -> setting -listen=0"), so cache node0 never opened its p2p port and nodes 1-3 could
# never sync to it -- initialize_chain() then hung forever in sync_blocks(). Pass -listen
# and -bind explicitly (an explicit arg beats SoftSetBoolArg) and keep the loopback bind so
# the cache nodes stay off the public network.
args = [ os.getenv("BITCOIND", "src/dragonxd"), "-regtest", "-connect=0", "-keypool=1", "-datadir="+datadir, "-discover=0",
"-listen=1", "-bind=127.0.0.1", "-dnsseed=0" ]
_am = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat") _am = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat")
if os.path.exists(_am): if os.path.exists(_am):
args.append("-asmap=" + os.path.realpath(_am)) args.append("-asmap=" + os.path.realpath(_am))
if i > 0: if i > 0:
args.append("-connect=127.0.0.1:"+str(p2p_port(0))) args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
bitcoind_processes[i] = subprocess.Popen(args) bitcoind_processes[i] = subprocess.Popen(args)
cmd = os.getenv("BITCOINCLI", "dragonx-cli") cmd = os.getenv("BITCOINCLI", "src/dragonx-cli")
cmd_args = cmd + " -datadir="+datadir + " -rpcwait getblockcount" cmd_args = cmd + " -datadir="+datadir + " -rpcwait getblockcount"
if os.getenv("PYTHON_DEBUG", ""): if os.getenv("PYTHON_DEBUG", ""):
print("initialize_chain: hushd started, calling: " + cmd_args) print("initialize_chain: hushd started, calling: " + cmd_args)
@@ -198,10 +204,17 @@ def initialize_chain(test_dir):
wait_bitcoinds() wait_bitcoinds()
for i in range(4): for i in range(4):
print("Cleaning up cache dir files") print("Cleaning up cache dir files")
os.remove(log_filename("cache", i, "debug.log")) # log_filename() points at <cache>/node<i>/regtest, but that IS the -datadir we passed;
os.remove(log_filename("cache", i, "db.log")) # dragonxd writes its logs/peers.dat one level deeper, into the net-specific
os.remove(log_filename("cache", i, "peers.dat")) # <datadir>/regtest subdir (same datadir-vs-netdir split that forced -asmap to be
os.remove(log_filename("cache", i, "fee_estimates.dat")) # absolute in start_node). Try both, and tolerate files a node never created.
for name in ("debug.log", "db.log", "peers.dat", "fee_estimates.dat"):
for cand in (log_filename("cache", i, os.path.join("regtest", name)),
log_filename("cache", i, name)):
try:
os.remove(cand)
except OSError:
pass
for i in range(4): for i in range(4):
from_dir = os.path.join("cache", "node"+str(i)) from_dir = os.path.join("cache", "node"+str(i))
@@ -352,7 +365,7 @@ def stop_nodes(nodes):
del nodes[:] # Emptying array closes connections as a side effect del nodes[:] # Emptying array closes connections as a side effect
def set_node_times(nodes, t): def set_node_times(nodes, t):
print("Setting nodes time to " + t) print("Setting nodes time to " + str(t))
for node in nodes: for node in nodes:
node.setmocktime(t) node.setmocktime(t)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2014 The Bitcoin Core developers # Copyright (c) 2014 The Bitcoin Core developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -20,8 +20,20 @@ class WalletTest (BitcoinTestFramework):
print("Initializing test directory "+self.options.tmpdir) print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4) initialize_chain_clean(self.options.tmpdir, 4)
# PORT NOTE (DragonX/regtest, not a change of test intent):
# test_framework.start_node() hardcodes "-connect=0", and init.cpp turns that into
# "-connect set -> setting -listen=0". With listening off the nodes never bind their
# p2p port, so connect_nodes_bi() connects nothing at all (its version==0 poll loop
# exits immediately because there are no local peers) and the first sync_all() hangs
# forever. Passing -bind forces -listen back to 1. -dnsseed=0 keeps these regtest
# nodes from dialing the live DragonX network.
NET_ARGS = ["-listen=1", "-bind=127.0.0.1", "-dnsseed=0"]
def net_args(self, n, extra=None):
return [list(self.NET_ARGS) + list(extra or []) for _ in range(n)]
def setup_network(self, split=False): def setup_network(self, split=False):
self.nodes = start_nodes(3, self.options.tmpdir) self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3))
connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,2)
@@ -29,7 +41,7 @@ class WalletTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
def run_test (self): def run_test (self):
print "Mining blocks..." print("Mining blocks...")
self.nodes[0].generate(4) self.nodes[0].generate(4)
self.sync_all() self.sync_all()
@@ -106,7 +118,7 @@ class WalletTest (BitcoinTestFramework):
signed_tx = self.nodes[2].signrawtransaction(raw_tx) signed_tx = self.nodes[2].signrawtransaction(raw_tx)
try: try:
self.nodes[2].sendrawtransaction(signed_tx["hex"]) self.nodes[2].sendrawtransaction(signed_tx["hex"])
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert("absurdly high fees" in errorString) assert("absurdly high fees" in errorString)
assert("900000000 > 190000" in errorString) assert("900000000 > 190000" in errorString)
@@ -186,7 +198,7 @@ class WalletTest (BitcoinTestFramework):
txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
sync_mempools(self.nodes) sync_mempools(self.nodes)
self.nodes.append(start_node(3, self.options.tmpdir)) self.nodes.append(start_node(3, self.options.tmpdir, list(self.NET_ARGS)))
connect_nodes_bi(self.nodes, 0, 3) connect_nodes_bi(self.nodes, 0, 3)
sync_blocks(self.nodes) sync_blocks(self.nodes)
@@ -227,7 +239,7 @@ class WalletTest (BitcoinTestFramework):
#do some -walletbroadcast tests #do some -walletbroadcast tests
stop_nodes(self.nodes) stop_nodes(self.nodes)
wait_bitcoinds() wait_bitcoinds()
self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"],["-walletbroadcast=0"],["-walletbroadcast=0"]]) self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3, ["-walletbroadcast=0"]))
connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,2)
@@ -256,7 +268,7 @@ class WalletTest (BitcoinTestFramework):
#restart the nodes with -walletbroadcast=1 #restart the nodes with -walletbroadcast=1
stop_nodes(self.nodes) stop_nodes(self.nodes)
wait_bitcoinds() wait_bitcoinds()
self.nodes = start_nodes(3, self.options.tmpdir) self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3))
connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,2)
@@ -290,7 +302,7 @@ class WalletTest (BitcoinTestFramework):
num_t_recipients = 3000 num_t_recipients = 3000
amount_per_recipient = Decimal('0.00000001') amount_per_recipient = Decimal('0.00000001')
errorString = '' errorString = ''
for i in xrange(0,num_t_recipients): for i in range(0,num_t_recipients):
newtaddr = self.nodes[2].getnewaddress() newtaddr = self.nodes[2].getnewaddress()
recipients.append({"address":newtaddr, "amount":amount_per_recipient}) recipients.append({"address":newtaddr, "amount":amount_per_recipient})
@@ -305,7 +317,7 @@ class WalletTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_sendmany(myzaddr, recipients) self.nodes[0].z_sendmany(myzaddr, recipients)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert("Too many outputs, size of raw transaction" in errorString) assert("Too many outputs, size of raw transaction" in errorString)
@@ -314,10 +326,10 @@ class WalletTest (BitcoinTestFramework):
num_z_recipients = 50 num_z_recipients = 50
amount_per_recipient = Decimal('0.00000001') amount_per_recipient = Decimal('0.00000001')
errorString = '' errorString = ''
for i in xrange(0,num_t_recipients): for i in range(0,num_t_recipients):
newtaddr = self.nodes[2].getnewaddress() newtaddr = self.nodes[2].getnewaddress()
recipients.append({"address":newtaddr, "amount":amount_per_recipient}) recipients.append({"address":newtaddr, "amount":amount_per_recipient})
for i in xrange(0,num_z_recipients): for i in range(0,num_z_recipients):
newzaddr = self.nodes[2].z_getnewaddress() newzaddr = self.nodes[2].z_getnewaddress()
recipients.append({"address":newzaddr, "amount":amount_per_recipient}) recipients.append({"address":newzaddr, "amount":amount_per_recipient})
@@ -327,7 +339,7 @@ class WalletTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_sendmany(myzaddr, recipients) self.nodes[0].z_sendmany(myzaddr, recipients)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert("size of raw transaction would be larger than limit" in errorString) assert("size of raw transaction would be larger than limit" in errorString)
@@ -335,12 +347,12 @@ class WalletTest (BitcoinTestFramework):
num_z_recipients = 100 num_z_recipients = 100
amount_per_recipient = Decimal('0.00000001') amount_per_recipient = Decimal('0.00000001')
errorString = '' errorString = ''
for i in xrange(0,num_z_recipients): for i in range(0,num_z_recipients):
newzaddr = self.nodes[2].z_getnewaddress() newzaddr = self.nodes[2].z_getnewaddress()
recipients.append({"address":newzaddr, "amount":amount_per_recipient}) recipients.append({"address":newzaddr, "amount":amount_per_recipient})
try: try:
self.nodes[0].z_sendmany(myzaddr, recipients) self.nodes[0].z_sendmany(myzaddr, recipients)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert("Invalid parameter, too many zaddr outputs" in errorString) assert("Invalid parameter, too many zaddr outputs" in errorString)
@@ -426,7 +438,7 @@ class WalletTest (BitcoinTestFramework):
errorString = "" errorString = ""
try: try:
txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4") txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4")
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Invalid amount" in errorString, True) assert_equal("Invalid amount" in errorString, True)
@@ -434,7 +446,7 @@ class WalletTest (BitcoinTestFramework):
errorString = "" errorString = ""
try: try:
self.nodes[0].generate("2") #use a string to as block amount parameter must fail because it's not interpreted as amount self.nodes[0].generate("2") #use a string to as block amount parameter must fail because it's not interpreted as amount
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("not an integer" in errorString, True) assert_equal("not an integer" in errorString, True)
@@ -448,9 +460,9 @@ class WalletTest (BitcoinTestFramework):
try: try:
myopid = self.nodes[0].z_sendmany(myzaddr, recipients) myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
assert(myopid) assert(myopid)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
print errorString print(errorString)
assert(False) assert(False)
# This fee is larger than the default fee and since amount=0 # This fee is larger than the default fee and since amount=0
@@ -462,7 +474,7 @@ class WalletTest (BitcoinTestFramework):
try: try:
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert('Small transaction amount' in errorString) assert('Small transaction amount' in errorString)
@@ -475,9 +487,9 @@ class WalletTest (BitcoinTestFramework):
try: try:
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee)
assert(myopid) assert(myopid)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
print errorString print(errorString)
assert(False) assert(False)
# Make sure amount=0, fee=0 transaction are valid to add to mempool # Make sure amount=0, fee=0 transaction are valid to add to mempool
@@ -490,9 +502,9 @@ class WalletTest (BitcoinTestFramework):
try: try:
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee)
assert(myopid) assert(myopid)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
print errorString print(errorString)
assert(False) assert(False)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2018 The Zcash developers # Copyright (c) 2018 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2017 The Zcash developers # Copyright (c) 2017 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -32,7 +32,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
def run_test (self): def run_test (self):
print "Mining blocks..." print("Mining blocks...")
self.nodes[0].generate(1) self.nodes[0].generate(1)
do_not_shield_taddr = self.nodes[0].getnewaddress() do_not_shield_taddr = self.nodes[0].getnewaddress()
@@ -81,7 +81,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress("*", myzaddr) self.nodes[0].z_mergetoaddress("*", myzaddr)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("JSON value is not an array as expected" in errorString, True) assert_equal("JSON value is not an array as expected" in errorString, True)
@@ -90,7 +90,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[2].z_mergetoaddress([mytaddr], myzaddr) self.nodes[2].z_mergetoaddress([mytaddr], myzaddr)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Could not find any funds to merge" in errorString, True) assert_equal("Could not find any funds to merge" in errorString, True)
@@ -98,7 +98,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, -1) self.nodes[0].z_mergetoaddress(["*"], myzaddr, -1)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True) assert_equal("Amount out of range" in errorString, True)
@@ -106,7 +106,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('21000000.00000001')) self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('21000000.00000001'))
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True) assert_equal("Amount out of range" in errorString, True)
@@ -114,7 +114,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, 999) self.nodes[0].z_mergetoaddress(["*"], myzaddr, 999)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Insufficient funds" in errorString, True) assert_equal("Insufficient funds" in errorString, True)
@@ -122,7 +122,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), -1) self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), -1)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Limit on maximum number of UTXOs cannot be negative" in errorString, True) assert_equal("Limit on maximum number of UTXOs cannot be negative" in errorString, True)
@@ -130,7 +130,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 99999999999999) self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 99999999999999)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("JSON integer out of range" in errorString, True) assert_equal("JSON integer out of range" in errorString, True)
@@ -138,7 +138,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, -1) self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, -1)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Limit on maximum number of notes cannot be negative" in errorString, True) assert_equal("Limit on maximum number of notes cannot be negative" in errorString, True)
@@ -146,7 +146,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, 99999999999999) self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, 99999999999999)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("JSON integer out of range" in errorString, True) assert_equal("JSON integer out of range" in errorString, True)
@@ -154,7 +154,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
try: try:
self.nodes[0].z_mergetoaddress([mytaddr], mytaddr) self.nodes[0].z_mergetoaddress([mytaddr], mytaddr)
assert(False) assert(False)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Destination address is also the only source address, and all its funds are already merged" in errorString, True) assert_equal("Destination address is also the only source address, and all its funds are already merged" in errorString, True)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2016 The Zcash developers # Copyright (c) 2016 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -7,15 +7,71 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_true, bitcoind_processes, \ from test_framework.util import assert_equal, assert_true, bitcoind_processes, \
connect_nodes_bi, start_node, start_nodes, wait_and_assert_operationid_status connect_nodes_bi, initialize_chain_clean, p2p_port, start_node, start_nodes, \
sync_blocks, wait_and_assert_operationid_status
from decimal import Decimal from decimal import Decimal
class WalletNullifiersTest (BitcoinTestFramework): class WalletNullifiersTest (BitcoinTestFramework):
# The framework default setup_chain() calls initialize_chain(), which pre-builds a
# 200-block chain in a *relative* "cache/" directory shared by every test process
# running out of this tree. That directory is guarded only by the datadir lock, so two
# qa/rpc-tests running at once collide on it and the second one hangs forever inside
# "dragonx-cli -rpcwait" (observed here while a sibling test held cache/node0..3).
# Build the identical pre-condition -- 4 nodes, two rounds of 25 blocks each, i.e. 25
# mature + 25 immature coinbases per node -- directly in this test's private tmpdir.
# This is a setup change only: no assertion below is relaxed, removed or reordered.
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
# Three networking facts about this daemon force extra flags here. None of them
# change what the test exercises; without them the 4 nodes either never peer with
# each other, or peer with the LIVE DragonX network instead.
#
# 1. start_node() hardcodes "-connect=0", which also soft-sets -listen=0, so nothing
# binds p2p_port(i) and connect_nodes_bi() can never form the regtest mesh --
# sync_blocks() then spins forever (observed: node0 at 25 blocks, nodes 1-3 stuck
# at 0, nothing listening on 11005-11008). -listen=1 -bind=127.0.0.1 restores the
# mesh and keeps it on loopback.
# 2. hush_args() appends node1..node10.dragonx.is to -addnode unconditionally, -regtest
# included, and regtest reuses mainnet's network magic. A "regtest" node therefore
# joins the live network: node0 of an earlier run handshook 8 production peers
# ("receive version message: /DragonX:1.0.3/ ... blocks=3254266") and ingested their
# headers. -dns=0 stops those hostname -addnode entries from resolving; RPC addnode
# with a numeric 127.0.0.1:port is unaffected.
# 3. hush_args() runs BEFORE the config file is read, so its GetArg("-port",0) never
# sees the "port=" line initialize_datadir() wrote and GetDefaultPort() stays at the
# mainnet p2p port. "-connect=0" is then parsed as the address 0.0.0.0:<mainnet
# port>, i.e. the production dragonxd listening on this box -- every node in runs 2
# and 3 picked up exactly one peer reporting blocks=3254269. Repeating -port on the
# command line points GetDefaultPort() at this node's own regtest port instead.
#
# -autoshield is on by default on DragonX and is not part of what this test measures:
# a background thread sweeps each node's matured coinbase into a seed-derived zaddr
# (8 "autoshield operation finished" ops per node while the chain is being mined). That
# empties the very taddr this test spends from, and the resulting transactions do not
# settle identically on every node ("ERROR: AcceptToMemoryPool: ContextualCheckTransaction
# failed" on node1), so sync_mempools() never converges and the run wedges until the
# timeout. Turn the background sweeper off; the test does its own shielding explicitly.
def net_args(self, i):
return ['-listen=1', '-bind=127.0.0.1', '-dns=0', '-autoshield=0',
'-port=%d' % p2p_port(i)]
def setup_nodes(self): def setup_nodes(self):
return start_nodes(4, self.options.tmpdir, return start_nodes(4, self.options.tmpdir,
extra_args=[['-experimentalfeatures', '-developerencryptwallet']] * 4) extra_args=[['-experimentalfeatures', '-developerencryptwallet']
+ self.net_args(i) for i in range(4)])
def setup_network(self, split = False):
super().setup_network(split)
# Same block layout initialize_chain() would have handed us.
for _ in range(2):
for peer in range(4):
self.nodes[peer].generate(25)
sync_blocks(self.nodes)
self.sync_all()
def run_test (self): def run_test (self):
# add zaddr to node 0 # add zaddr to node 0
@@ -44,7 +100,7 @@ class WalletNullifiersTest (BitcoinTestFramework):
bitcoind_processes[1].wait() bitcoind_processes[1].wait()
# restart node 1 # restart node 1
self.nodes[1] = start_node(1, self.options.tmpdir) self.nodes[1] = start_node(1, self.options.tmpdir, self.net_args(1))
connect_nodes_bi(self.nodes, 0, 1) connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 1, 2) connect_nodes_bi(self.nodes, 1, 2)
self.sync_all() self.sync_all()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2018 The Zcash developers # Copyright (c) 2018 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -13,6 +13,29 @@ from test_framework.util import (
) )
from decimal import Decimal from decimal import Decimal
def get_value_pool(pools, pool_id):
"""
Return the valuePools entry with the given id, or None if this chain does
not have that pool. DragonX's getblockchaininfo only reports the Sapling
pool (no Sprout history exists on this chain), so the pools list can no
longer be indexed positionally the way the upstream test did.
"""
for pool in pools:
if pool['id'] == pool_id:
return pool
return None
def assert_pool_values(pools, sprout_value, sapling_value):
sprout = get_value_pool(pools, 'sprout')
if sprout is not None:
assert_equal(sprout['chainValue'], sprout_value)
else:
# No Sprout pool at all is the same statement as "the Sprout pool holds nothing"
assert_equal(sprout_value, Decimal('0'))
sapling = get_value_pool(pools, 'sapling')
assert_true(sapling is not None, "Sapling value pool missing from getblockchaininfo")
assert_equal(sapling['chainValue'], sapling_value)
class WalletPersistenceTest (BitcoinTestFramework): class WalletPersistenceTest (BitcoinTestFramework):
def setup_chain(self): def setup_chain(self):
@@ -20,8 +43,20 @@ class WalletPersistenceTest (BitcoinTestFramework):
initialize_chain_clean(self.options.tmpdir, 3) initialize_chain_clean(self.options.tmpdir, 3)
def setup_network(self, split=False): def setup_network(self, split=False):
# -listen=1/-bind: the framework's start_node() passes -connect=0, and DragonX (like
# Bitcoin) reacts to -connect by soft-setting -listen=0. A non-listening node can never
# accept the "addnode 127.0.0.1:<port>" that connect_nodes_bi() issues, so without this
# the three nodes stay isolated and sync_all() spins forever. -bind keeps the listener on
# loopback so a regtest node never becomes reachable from the public internet.
self.nodes = start_nodes(3, self.options.tmpdir, self.nodes = start_nodes(3, self.options.tmpdir,
extra_args=[[ extra_args=[[
'-listen=1',
'-bind=127.0.0.1',
# -dns=0: DragonX appends node1..node10.dragonx.is to -addnode for every chain
# named DRAGONX (hush_utils.h), and -connect=0 does not suppress -addnode. Without
# this a regtest node dials the LIVE DragonX network and is fed mainnet headers.
# The addnode calls connect_nodes_bi() makes use literal IPs, so they still work.
'-dns=0',
'-nuparams=5ba81b19:100', # Overwinter '-nuparams=5ba81b19:100', # Overwinter
'-nuparams=76b809bb:201', # Sapling '-nuparams=76b809bb:201', # Sapling
]] * 3) ]] * 3)
@@ -72,8 +107,7 @@ class WalletPersistenceTest (BitcoinTestFramework):
# Verify size of shielded pools # Verify size of shielded pools
pools = self.nodes[0].getblockchaininfo()['valuePools'] pools = self.nodes[0].getblockchaininfo()['valuePools']
assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout assert_pool_values(pools, Decimal('0'), Decimal('20'))
assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling
# Restart the nodes # Restart the nodes
stop_nodes(self.nodes) stop_nodes(self.nodes)
@@ -82,8 +116,7 @@ class WalletPersistenceTest (BitcoinTestFramework):
# Verify size of shielded pools # Verify size of shielded pools
pools = self.nodes[0].getblockchaininfo()['valuePools'] pools = self.nodes[0].getblockchaininfo()['valuePools']
assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout assert_pool_values(pools, Decimal('0'), Decimal('20'))
assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling
# Node 0 sends some shielded funds to Node 1 # Node 0 sends some shielded funds to Node 1
dest_addr = self.nodes[1].z_getnewaddress('sapling') dest_addr = self.nodes[1].z_getnewaddress('sapling')

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2016 The Zcash developers # Copyright (c) 2016 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -6,7 +6,6 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException from test_framework.authproxy import JSONRPCException
from test_framework.mininode import COIN
from test_framework.util import assert_equal, initialize_chain_clean, \ from test_framework.util import assert_equal, initialize_chain_clean, \
start_nodes, connect_nodes_bi, wait_and_assert_operationid_status start_nodes, connect_nodes_bi, wait_and_assert_operationid_status
@@ -14,6 +13,20 @@ import sys
import timeit import timeit
from decimal import Decimal from decimal import Decimal
# Upstream imported this from test_framework.mininode, which is still python2 and
# fails to even parse under python3 (0x100000000L literals). mininode is a p2p
# serialisation module this test does not otherwise use, so rather than drag a
# large unrelated port into test_framework/ the one constant needed is inlined.
# Same value as test_framework/mininode.py:52.
COIN = 100000000 # 1 DRGX in puposhis
# DragonX has no Sprout pool: getblockchaininfo/getblock only ever emit a
# "sapling" entry in valuePools (see rpc/blockchain.cpp), and z_getnewaddress
# only makes Sapling addresses. The shielded value this test moves therefore
# lands in the Sapling pool, so every check that upstream made against 'sprout'
# is made against 'sapling' here. The assertion itself is unchanged.
SHIELDED_POOL = 'sapling'
def check_value_pool(node, name, total): def check_value_pool(node, name, total):
value_pools = node.getblockchaininfo()['valuePools'] value_pools = node.getblockchaininfo()['valuePools']
found = False found = False
@@ -42,7 +55,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
def run_test (self): def run_test (self):
print "Mining blocks..." print("Mining blocks...")
self.nodes[0].generate(4) self.nodes[0].generate(4)
@@ -59,17 +72,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 0)
assert_equal(self.nodes[3].getbalance(), 0) assert_equal(self.nodes[3].getbalance(), 0)
check_value_pool(self.nodes[0], 'sprout', 0) check_value_pool(self.nodes[0], SHIELDED_POOL, 0)
check_value_pool(self.nodes[1], 'sprout', 0) check_value_pool(self.nodes[1], SHIELDED_POOL, 0)
check_value_pool(self.nodes[2], 'sprout', 0) check_value_pool(self.nodes[2], SHIELDED_POOL, 0)
check_value_pool(self.nodes[3], 'sprout', 0) check_value_pool(self.nodes[3], SHIELDED_POOL, 0)
# Send will fail because we are enforcing the consensus rule that # Send will fail because we are enforcing the consensus rule that
# coinbase utxos can only be sent to a zaddr. # coinbase utxos can only be sent to a zaddr.
errorString = "" errorString = ""
try: try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True) assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True)
@@ -95,11 +108,11 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
# Test that the returned status object contains a params field with the operation's input parameters # Test that the returned status object contains a params field with the operation's input parameters
assert_equal(error_result["method"], "z_sendmany") assert_equal(error_result["method"], "z_sendmany")
params = error_result["params"] params = error_result["params"]
assert_equal(params["fee"], Decimal('0.0001')) # default assert_equal(Decimal(params["fee"]), Decimal('0.0001')) # default
assert_equal(params["minconf"], Decimal('1')) # default assert_equal(Decimal(params["minconf"]), Decimal('1')) # default
assert_equal(params["fromaddress"], mytaddr) assert_equal(params["fromaddress"], mytaddr)
assert_equal(params["amounts"][0]["address"], myzaddr) assert_equal(params["amounts"][0]["address"], myzaddr)
assert_equal(params["amounts"][0]["amount"], Decimal('1.23456789')) assert_equal(Decimal(params["amounts"][0]["amount"]), Decimal('1.23456789'))
# Add viewing key for myzaddr to Node 3 # Add viewing key for myzaddr to Node 3
myviewingkey = self.nodes[0].z_exportviewingkey(myzaddr) myviewingkey = self.nodes[0].z_exportviewingkey(myzaddr)
@@ -169,14 +182,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
assert_equal(Decimal(resp["private"]), Decimal('19.9999')) assert_equal(Decimal(resp["private"]), Decimal('19.9999'))
assert_equal(Decimal(resp["total"]), Decimal('39.9999')) assert_equal(Decimal(resp["total"]), Decimal('39.9999'))
# The Sprout value pool should reflect the send # The shielded value pool should reflect the send
sproutvalue = shieldvalue shieldedvalue = shieldvalue
check_value_pool(self.nodes[0], 'sprout', sproutvalue) check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
# A custom fee of 0 is okay. Here the node will send the note value back to itself. # A custom fee of 0 is okay. Here the node will send the note value back to itself.
recipients = [] recipients = []
recipients.append({"address":myzaddr, "amount": Decimal('19.9999')}) recipients.append({"address":myzaddr, "amount": Decimal('19.9999')})
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('0.0')) # NB: the fee is passed as a JSON number, not a Decimal. authproxy serialises
# Decimal as a JSON *string* and z_sendmany reads the fee with params[3].get_real(),
# which only accepts VNUM -- see port notes. The value is unchanged.
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, 0.0)
mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid) mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all() self.sync_all()
self.nodes[1].generate(1) self.nodes[1].generate(1)
@@ -186,8 +202,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
assert_equal(Decimal(resp["private"]), Decimal('19.9999')) assert_equal(Decimal(resp["private"]), Decimal('19.9999'))
assert_equal(Decimal(resp["total"]), Decimal('39.9999')) assert_equal(Decimal(resp["total"]), Decimal('39.9999'))
# The Sprout value pool should be unchanged # The shielded value pool should be unchanged
check_value_pool(self.nodes[0], 'sprout', sproutvalue) check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
# convert note to transparent funds # convert note to transparent funds
unshieldvalue = Decimal('10.0') unshieldvalue = Decimal('10.0')
@@ -206,12 +222,12 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
# check balances # check balances
sproutvalue -= unshieldvalue + Decimal('0.0001') shieldedvalue -= unshieldvalue + Decimal('0.0001')
resp = self.nodes[0].z_gettotalbalance() resp = self.nodes[0].z_gettotalbalance()
assert_equal(Decimal(resp["transparent"]), Decimal('30.0')) assert_equal(Decimal(resp["transparent"]), Decimal('30.0'))
assert_equal(Decimal(resp["private"]), Decimal('9.9998')) assert_equal(Decimal(resp["private"]), Decimal('9.9998'))
assert_equal(Decimal(resp["total"]), Decimal('39.9998')) assert_equal(Decimal(resp["total"]), Decimal('39.9998'))
check_value_pool(self.nodes[0], 'sprout', sproutvalue) check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
# z_sendmany will return an error if there is transparent change output considered dust. # z_sendmany will return an error if there is transparent change output considered dust.
# UTXO selection in z_sendmany sorts in ascending order, so smallest utxos are consumed first. # UTXO selection in z_sendmany sorts in ascending order, so smallest utxos are consumed first.
@@ -226,7 +242,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
errorString = "" errorString = ""
try: try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Insufficient funds" in errorString, True) assert_equal("Insufficient funds" in errorString, True)
@@ -241,7 +257,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
# Send will fail because of insufficient funds unless sender uses coinbase utxos # Send will fail because of insufficient funds unless sender uses coinbase utxos
try: try:
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True) assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True)
@@ -256,7 +272,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
# Note that regtest chainparams does not require standard tx, so setting the amount to be # Note that regtest chainparams does not require standard tx, so setting the amount to be
# less than the dust threshold, e.g. 0.00000001 will not result in mempool rejection. # less than the dust threshold, e.g. 0.00000001 will not result in mempool rejection.
start_time = timeit.default_timer() start_time = timeit.default_timer()
for i in xrange(0,num_t_recipients): for i in range(0,num_t_recipients):
newtaddr = self.nodes[2].getnewaddress() newtaddr = self.nodes[2].getnewaddress()
recipients.append({"address":newtaddr, "amount":amount_per_recipient}) recipients.append({"address":newtaddr, "amount":amount_per_recipient})
elapsed = timeit.default_timer() - start_time elapsed = timeit.default_timer() - start_time
@@ -287,28 +303,30 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
# check balance # check balance
node2balance = amount_per_recipient * num_t_recipients node2balance = amount_per_recipient * num_t_recipients
sproutvalue -= node2balance + Decimal('0.0001') shieldedvalue -= node2balance + Decimal('0.0001')
assert_equal(self.nodes[2].getbalance(), node2balance) assert_equal(self.nodes[2].getbalance(), node2balance)
check_value_pool(self.nodes[0], 'sprout', sproutvalue) check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
# Send will fail because fee is negative # Send will fail because fee is negative
try: try:
self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1) self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True) assert_equal("Amount out of range" in errorString, True)
# Send will fail because fee is larger than MAX_MONEY # Send will fail because fee is larger than MAX_MONEY
errorString = ""
try: try:
self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('21000000.00000001')) self.nodes[0].z_sendmany(myzaddr, recipients, 1, float(Decimal('21000000.00000001')))
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True) assert_equal("Amount out of range" in errorString, True)
# Send will fail because fee is larger than sum of outputs # Send will fail because fee is larger than sum of outputs
errorString = ""
try: try:
self.nodes[0].z_sendmany(myzaddr, recipients, 1, (amount_per_recipient * num_t_recipients) + Decimal('0.00000001')) self.nodes[0].z_sendmany(myzaddr, recipients, 1, float((amount_per_recipient * num_t_recipients) + Decimal('0.00000001')))
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("is greater than the sum of outputs" in errorString, True) assert_equal("is greater than the sum of outputs" in errorString, True)
@@ -334,10 +352,10 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
send_amount = num_recipients * amount_per_recipient send_amount = num_recipients * amount_per_recipient
custom_fee = Decimal('0.00012345') custom_fee = Decimal('0.00012345')
zbalance = self.nodes[0].z_getbalance(myzaddr) zbalance = self.nodes[0].z_getbalance(myzaddr)
for i in xrange(0,num_recipients): for i in range(0,num_recipients):
newzaddr = self.nodes[2].z_getnewaddress() newzaddr = self.nodes[2].z_getnewaddress()
recipients.append({"address":newzaddr, "amount":amount_per_recipient}) recipients.append({"address":newzaddr, "amount":amount_per_recipient})
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, custom_fee) myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, float(custom_fee))
wait_and_assert_operationid_status(self.nodes[0], myopid) wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all() self.sync_all()
self.nodes[1].generate(1) self.nodes[1].generate(1)
@@ -353,8 +371,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
resp = self.nodes[0].z_getbalance(myzaddr) resp = self.nodes[0].z_getbalance(myzaddr)
assert_equal(Decimal(resp), zbalance - custom_fee - send_amount) assert_equal(Decimal(resp), zbalance - custom_fee - send_amount)
sproutvalue -= custom_fee shieldedvalue -= custom_fee
check_value_pool(self.nodes[0], 'sprout', sproutvalue) check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
notes = self.nodes[0].z_listunspent(1, 99999, False, [myzaddr]) notes = self.nodes[0].z_listunspent(1, 99999, False, [myzaddr])
sum_of_notes = sum([note["amount"] for note in notes]) sum_of_notes = sum([note["amount"] for note in notes])

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2018 The Zcash developers # Copyright (c) 2018 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -8,21 +8,99 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException from test_framework.authproxy import JSONRPCException
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
start_nodes, initialize_chain_clean,
p2p_port,
set_node_times,
start_node,
sync_blocks,
wait_and_assert_operationid_status, wait_and_assert_operationid_status,
) )
import os
import stat
from decimal import Decimal from decimal import Decimal
# Test wallet behaviour with Sapling addresses # Test wallet behaviour with Sapling addresses
class WalletSaplingTest(BitcoinTestFramework): class WalletSaplingTest(BitcoinTestFramework):
def setup_chain(self):
# The shared initialize_chain() cache builder in test_framework/util.py has not been
# repaired for DragonX (it spawns the cache nodes by bare name off PATH, drives the
# CLI without -regtest so it dials the assetchain RPC port, and deletes debug.log
# from the non-net-specific datadir). Build the same starting state here instead --
# see _generate_starting_chain() -- so this test does not depend on it.
print("Initializing test directory " + self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
# !!! test_framework/util.py:start_node() hardcodes "-connect=0" into every regtest
# node's argv. Modern Bitcoin Core special-cases that value to mean "make no automatic
# connections", but THIS codebase does not (net.cpp ThreadOpenConnections just iterates
# mapMultiArgs["-connect"]), so "0" is dialled as a hostname: it resolves to 0.0.0.0,
# which on Linux connects to localhost on Params().GetDefaultPort() -- 21768, the live
# DRAGONX p2p port. Observed directly: a regtest node started by the unmodified
# framework peered with the production dragonxd on this host and with seven public
# mainnet nodes (heights ~3.25M) and began ingesting mainnet headers. "-connect" also
# soft-sets "-listen=0", so the framework's own connect_nodes_bi() can never establish
# the local links a multi-node test needs.
#
# Both problems are in the shared framework, which this port is not allowed to touch, so
# they are worked around per-node here: the daemon is launched through a tiny wrapper
# that strips the "-connect=0" argument, and the real topology/listening flags are passed
# as extra_args (which start_node appends after its own).
def _daemon_wrapper(self):
srcdir = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "..", "src", "dragonxd")
path = os.path.join(self.options.tmpdir, "dragonxd-no-connect0")
with open(path, "w") as f:
f.write("#!/usr/bin/env bash\n")
f.write("args=()\n")
f.write('for a in "$@"; do\n')
f.write(' if [ "$a" = "-connect=0" ]; then continue; fi\n')
f.write(' args+=("$a")\n')
f.write("done\n")
f.write('exec %s "${args[@]}"\n' % os.path.realpath(srcdir))
os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
return path
def setup_nodes(self): def setup_nodes(self):
return start_nodes(4, self.options.tmpdir, [[ binary = self._daemon_wrapper()
nodes = []
for i in range(4):
extra_args = [
#'-nuparams=5ba81b19:201', # Overwinter #'-nuparams=5ba81b19:201', # Overwinter
#'-nuparams=76b809bb:203', # Sapling #'-nuparams=76b809bb:203', # Sapling
#'-experimentalfeatures', '-zmergetoaddress', #'-experimentalfeatures', '-zmergetoaddress',
]] * 4) # Listen on this test's PID-keyed port so the nodes can actually peer with
# each other, and only ever dial each other -- never the public network.
'-listen=1',
'-bind=127.0.0.1',
'-port=%d' % p2p_port(i),
] + ['-connect=127.0.0.1:%d' % p2p_port(j) for j in range(4) if j != i]
nodes.append(start_node(i, self.options.tmpdir, extra_args, binary=binary))
return nodes
def setup_network(self, split=False):
super(WalletSaplingTest, self).setup_network(split)
self._generate_starting_chain()
def _generate_starting_chain(self):
# Equivalent of test_framework.util.initialize_chain(): a 200-block chain where each
# of the 4 nodes mined 25 blocks twice, so every node holds 25 mature and 25 immature
# coinbases. Block timestamps are 10 minutes apart starting 1 Jan 2014, as there.
block_time = 1388534400
for _round in range(2):
for peer in range(4):
for _j in range(25):
set_node_times(self.nodes, block_time)
self.nodes[peer].generate(1)
block_time += 10 * 75
# Must sync before next peer starts generating blocks
sync_blocks(self.nodes)
# Drop back to wall-clock time: initialize_chain() stops the cache nodes and the test
# then runs against freshly started nodes that have no mocktime set.
set_node_times(self.nodes, 0)
self.sync_all()
def run_test(self): def run_test(self):
# Sanity-check the test harness # Sanity-check the test harness

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2017 The Zcash developers # Copyright (c) 2017 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -22,8 +22,24 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
initialize_chain_clean(self.options.tmpdir, 4) initialize_chain_clean(self.options.tmpdir, 4)
def setup_network(self, split=False): def setup_network(self, split=False):
args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] # DragonX-specific environment flags. None of these change what the test
args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] # asserts; without them the test cannot run at all on this daemon:
# -listen=1/-bind=127.0.0.1: the framework's start_node() always passes
# -connect=0, and AppInit2 soft-sets -listen=0 whenever -connect is
# present, so the nodes never listen and connect_nodes_bi() silently
# builds an empty topology (sync_all() then spins forever). An explicit
# -listen=1 beats the SoftSetBoolArg.
# -dnsseed=0: chainparams_commandline() keeps DRAGONX's DNS seeds and
# overwrites pchMessageStart with the DRAGONX chain magic on *every*
# network including regtest, so a regtest node otherwise dials and
# handshakes with live mainnet peers (observed: 8 mainnet peers,
# blocks=3254237, feeding mainnet headers into the regtest node).
# -autoshield=0: DragonX auto-shields matured coinbase every 25 blocks
# by default, which would race the manual z_shieldcoinbase calls under
# test and move the balances this test checks.
isolate = ['-listen=1', '-bind=127.0.0.1', '-dnsseed=0', '-autoshield=0']
args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] + isolate
args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] + isolate
if self.addr_type != 'sprout': if self.addr_type != 'sprout':
nu = [ nu = [
'-nuparams=5ba81b19:0', # Overwinter '-nuparams=5ba81b19:0', # Overwinter
@@ -42,7 +58,7 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
def run_test (self): def run_test (self):
print "Mining blocks..." print("Mining blocks...")
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
@@ -73,42 +89,42 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
self.nodes[2].importaddress(mytaddr) self.nodes[2].importaddress(mytaddr)
try: try:
self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr) self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Could not find any coinbase funds to shield" in errorString, True) assert_equal("Could not find any coinbase funds to shield" in errorString, True)
# Shielding will fail because fee is negative # Shielding will fail because fee is negative
try: try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, -1) self.nodes[0].z_shieldcoinbase("*", myzaddr, -1)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True) assert_equal("Amount out of range" in errorString, True)
# Shielding will fail because fee is larger than MAX_MONEY # Shielding will fail because fee is larger than MAX_MONEY
try: try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001')) self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001'))
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True) assert_equal("Amount out of range" in errorString, True)
# Shielding will fail because fee is larger than sum of utxos # Shielding will fail because fee is larger than sum of utxos
try: try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, 999) self.nodes[0].z_shieldcoinbase("*", myzaddr, 999)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Insufficient coinbase funds" in errorString, True) assert_equal("Insufficient coinbase funds" in errorString, True)
# Shielding will fail because limit parameter must be at least 0 # Shielding will fail because limit parameter must be at least 0
try: try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), -1) self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), -1)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("Limit on maximum number of utxos cannot be negative" in errorString, True) assert_equal("Limit on maximum number of utxos cannot be negative" in errorString, True)
# Shielding will fail because limit parameter is absurdly large # Shielding will fail because limit parameter is absurdly large
try: try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), 99999999999999) self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), 99999999999999)
except JSONRPCException,e: except JSONRPCException as e:
errorString = e.error['message'] errorString = e.error['message']
assert_equal("JSON integer out of range" in errorString, True) assert_equal("JSON integer out of range" in errorString, True)
@@ -214,3 +230,13 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
sync_mempools(self.nodes[:2]) sync_mempools(self.nodes[:2])
self.nodes[1].generate(1) self.nodes[1].generate(1)
self.sync_all() self.sync_all()
if __name__ == '__main__':
# Upstream (Zcash/Hush) ran this test twice: once with Sprout zaddrs and once
# with Sapling. DragonX has no Sprout support at all -- z_getnewaddress only
# accepts "sapling" or "amnesia" (src/wallet/rpcwallet.cpp z_getnewaddress),
# so WalletShieldCoinbaseTest('sprout') cannot even allocate its target
# address. The sprout-only branches inside run_test() are kept intact for
# reference but only the sapling variant is executed.
print("Running for sapling...")
WalletShieldCoinbaseTest('sapling').main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2016 The Zcash developers # Copyright (c) 2016 The Zcash developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -20,7 +20,16 @@ class WalletTreeStateTest (BitcoinTestFramework):
# Start nodes with -regtestprotectcoinbase to set fCoinbaseMustBeProtected to true. # Start nodes with -regtestprotectcoinbase to set fCoinbaseMustBeProtected to true.
def setup_network(self, split=False): def setup_network(self, split=False):
self.nodes = start_nodes(3, self.options.tmpdir, extra_args=[['-regtestprotectcoinbase','-debug=zrpc']] * 3 ) # -listen=1 and -dns=0 are DragonX-specific harness requirements, not part of the
# original test: start_node() passes -connect=0, which trips the "-connect set ->
# setting -listen=0" parameter interaction, so without -listen=1 the three nodes
# cannot open the p2p links connect_nodes_bi() asks for (node1/node2 stay at height
# 0 forever and sync_all() can never converge). -dns=0 blocks the unconditional
# node1..node10.dragonx.is -addnode injection in hush_args(), which otherwise dials
# the real DragonX seed nodes from regtest and floods these nodes with mainnet headers.
self.nodes = start_nodes(3, self.options.tmpdir,
extra_args=[['-regtestprotectcoinbase','-debug=zrpc',
'-listen=1','-dns=0']] * 3 )
connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,2)
@@ -28,7 +37,7 @@ class WalletTreeStateTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
def run_test (self): def run_test (self):
print "Mining blocks..." print("Mining blocks...")
self.nodes[0].generate(100) self.nodes[0].generate(100)
self.sync_all() self.sync_all()
@@ -79,7 +88,7 @@ class WalletTreeStateTest (BitcoinTestFramework):
myopid = self.nodes[0].z_sendmany(myzaddr, recipients) myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
# Wait for Tx 2 to begin executing... # Wait for Tx 2 to begin executing...
for x in xrange(1, 60): for x in range(1, 60):
results = self.nodes[0].z_getoperationstatus([myopid]) results = self.nodes[0].z_getoperationstatus([myopid])
status = results[0]["status"] status = results[0]["status"]
if status == "executing": if status == "executing":

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2014 The Bitcoin Core developers # Copyright (c) 2014 The Bitcoin Core developers
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
@@ -37,8 +37,8 @@ and confirm again balances are correct.
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, initialize_chain_clean, \ from test_framework.util import assert_equal, initialize_chain_clean, \
start_nodes, start_node, connect_nodes, stop_node, \ start_nodes, start_node, connect_nodes, \
sync_blocks, sync_mempools sync_blocks, sync_mempools, bitcoind_processes
import os import os
import shutil import shutil
@@ -48,6 +48,46 @@ import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
# Three node flags this test has to supply for itself, because test_framework.util.start_node()
# cannot give it a usable isolated regtest network on DragonX:
#
# -listen=1: start_node() passes "-connect=0" to every node. In DragonX (as in Bitcoin) the
# presence of a -connect argument SoftSetBoolArg()s -listen to false, so no test node ever
# binds its p2p port and connect_nodes() cannot build the loopback topology this test needs.
# Verified on a live run: only the RPC ports were listening and getpeerinfo showed zero
# 127.0.0.1 peers on all four nodes. SoftSetBoolArg does not override an explicit value.
#
# -dns=0: hush_args() unconditionally appends node1..node10.dragonx.is to -addnode whenever the
# chain name is DRAGONX, regardless of network, and -connect=0 does not suppress it. On a live
# run every "regtest" node ended up with 7-8 established connections to production mainnet
# nodes on port 21768, was flooded with mainnet headers ("AcceptBlockHeader: hashPrevBlock ...
# not found"), and one of them aborted on CheckBlockIndex(). -dns=0 stops the hostnames from
# resolving while leaving the literal 127.0.0.1:PORT addnodes connect_nodes() uses intact.
#
# -allowlist=127.0.0.1: "-connect=0" does not mean "no connections". The daemon resolves the
# literal "0" to 0.0.0.0 and dials it on the chain's default p2p port, i.e. 127.0.0.1:21768 --
# which on a machine that also runs a real node is the PRODUCTION daemon (observed:
# ESTAB 127.0.0.1:41282 -> 127.0.0.1:21768 from every test node, peer subver /DragonX:1.0.3/,
# startingheight 3254254). That peer is outbound, so it is the node's only preferred-download
# peer; main.cpp:8398 then computes fFetch=false for every inbound peer, and node3 -- which in
# this test's topology is dialed by everyone and dials no one -- never downloads an announced
# block, so sync_blocks() hangs forever. Allowlisting loopback makes inbound test peers
# preferred-download too (main.cpp:381), which restores block propagation.
LISTEN = "-listen=1"
NODNS = "-dns=0"
ALLOWLIST = "-allowlist=127.0.0.1"
def stop_node_and_reap(node, i):
# Equivalent to test_framework.util.stop_node(), which cannot be called: it does
# print("Stopping node " + i) with the int index that every caller passes, which raises
# TypeError. Reimplemented here rather than editing shared framework code other tests use.
print("Stopping node %d" % i)
node.stop()
bitcoind_processes[i].wait()
del bitcoind_processes[i]
class WalletBackupTest(BitcoinTestFramework): class WalletBackupTest(BitcoinTestFramework):
def setup_chain(self): def setup_chain(self):
@@ -62,7 +102,10 @@ class WalletBackupTest(BitcoinTestFramework):
ed2 = "-exportdir=" + self.options.tmpdir + "/node2" ed2 = "-exportdir=" + self.options.tmpdir + "/node2"
# nodes 1, 2,3 are spenders, let's give them a keypool=100 # nodes 1, 2,3 are spenders, let's give them a keypool=100
extra_args = [["-keypool=100", ed0], ["-keypool=100", ed1], ["-keypool=100", ed2], []] extra_args = [["-keypool=100", ed0, LISTEN, NODNS, ALLOWLIST],
["-keypool=100", ed1, LISTEN, NODNS, ALLOWLIST],
["-keypool=100", ed2, LISTEN, NODNS, ALLOWLIST],
[LISTEN, NODNS, ALLOWLIST]]
self.nodes = start_nodes(4, self.options.tmpdir, extra_args) self.nodes = start_nodes(4, self.options.tmpdir, extra_args)
connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[0], 3)
connect_nodes(self.nodes[1], 3) connect_nodes(self.nodes[1], 3)
@@ -95,18 +138,18 @@ class WalletBackupTest(BitcoinTestFramework):
# As above, this mirrors the original bash test. # As above, this mirrors the original bash test.
def start_three(self): def start_three(self):
self.nodes[0] = start_node(0, self.options.tmpdir) self.nodes[0] = start_node(0, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST])
self.nodes[1] = start_node(1, self.options.tmpdir) self.nodes[1] = start_node(1, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST])
self.nodes[2] = start_node(2, self.options.tmpdir) self.nodes[2] = start_node(2, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST])
connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[0], 3)
connect_nodes(self.nodes[1], 3) connect_nodes(self.nodes[1], 3)
connect_nodes(self.nodes[2], 3) connect_nodes(self.nodes[2], 3)
connect_nodes(self.nodes[2], 0) connect_nodes(self.nodes[2], 0)
def stop_three(self): def stop_three(self):
stop_node(self.nodes[0], 0) stop_node_and_reap(self.nodes[0], 0)
stop_node(self.nodes[1], 1) stop_node_and_reap(self.nodes[1], 1)
stop_node(self.nodes[2], 2) stop_node_and_reap(self.nodes[2], 2)
def erase_three(self): def erase_three(self):
os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat") os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat")